C++封装的高性能异步日志,cout实现方式

时间:2021-05-02 21:55:28

请移步:http://blog.csdn.net/cstringw/article/details/79605143

  1. 异步写入日志———-基于windows对文件投递“重叠IO”操作实现的 异步写入。
  2. 多线程安全————-对每个“主调线程”创建一个“字符串流对象”,所以多线程并行操作互不影响。
  3. 无线程同步的开销—–因为每个线程都有自己的“流对象”所以不需要线程同步所造成的CPU时钟周期浪费。
  4. 支持UNICODE
  5. 支持ANSI
  6. 支持C/C++常用类型、对象直接写入
  7. 操作简单就像使用C++ std::cout

使用方法如下:

    //包含头文件 #include "_MyLog.h" //使用名称空间 using namespace _Log; //定义对象 _MyLog LogOut; //打开文件 LogOut.Open(); //异步输出到日志 LogOut << time << table << id << TEXT("这是一条日志消息") << endl; //关闭日志文件 LogOut.Close();

这是输出效果
2016-06-25 14:58:56 Test Output:00000000000
2016-06-25 14:58:56 1C6C 这是一条日志消息


就是这么简单,由于异步写入以及无同步的开销所以具有相当良好的性能。
这个类我已经用在了我的服务器上。
哈哈,是不是心动啦,是不是在看那儿有下载连接,好了不废话先上代码,技术上的部分基本上都在注释里了。


文件:_MyLog.h

/*包含头文件...略*/ /继承重叠机构的IO结构,用于投递异步写入重叠操作
typedef struct _LogIO : public OVERLAPPED
{
    string  m_str;
    //构造函数初始化OVERLAPPED结构
    _LogIO(){ memset( this, 0, sizeof(OVERLAPPED) ); }
}*P_LogIO;

//定义一些类型别名 管理不同字符集的对象版本
//UNICODE(宽字节字符集)、ANSI(多字节字符集) //如stringstream C++ 字符串流等对象 //定义了UNICODE表示当前编译环境是UNICODE #ifdef UNICODE #define degug_out wcout typedef wstringstream _stream; typedef wstringstream* p_stream; #else #define degug_out cout typedef stringstream _stream; typedef stringstream* p_stream; #endif //C++映射(map)对象,unsigned long 用于线程的ID 类型为DWORD typedef map<unsigned long , p_stream > ThreadMap; //定义在名称空间中,为了避免与其他函数混淆 如:time、endl namespace _Log { class _MyLog { private: //语言环境名 CString m_LocaleName; //日期格式 CString m_DateFormat; //日志文件句柄 HANDLE m_LogFile; //模块路径 CString m_ModulePath; //日志文件路径 CString m_LogFilePath; //文件下一次的偏移量 LARGE_INTEGER m_liFileNextOffset; //完成端口类实例 CIOCP m_ciop; ThreadMap m_ThreadMap; public: _MyLog(void); ~_MyLog(void); void Init( CString ModulePath, CString LocaleName, CString DateFormat ); void Open( void ); void Close( void ); template<typename T> _MyLog & operator << ( T info ); _MyLog & operator << ( _MyLog &(__cdecl * pfn)( _MyLog & ) ); //定义友元函数 friend _MyLog & time( _MyLog & Log ); friend _MyLog & id( _MyLog & Log ); friend _MyLog & table( _MyLog & Log ); friend _MyLog & space( _MyLog & Log ); friend _MyLog & endl( _MyLog & Log ); private: bool IsValid(); template<typename T> bool WriteLog( T info ); bool CompleteHandle(); p_stream GetThreadStream(); _MyLog & _endl(); }; //引用在CPP文件中定义的全局对象,引用该对象需要_Log::OutLog extern _MyLog OutLog; }

文件:_MyLog.cpp

namespace _Log
{
    //全局对象output log
    _MyLog OutLog;

    _MyLog::_MyLog(void)
    {
        TCHAR pTempBuff[MAX_PATH];

        //获得当前模块的绝对路径
        GetModuleFileName( NULL, (PTCHAR)pTempBuff, MAX_PATH);
        m_ModulePath.Format( TEXT("%s"), pTempBuff );

        //去除文件名只留下路径
        int nIndex = m_ModulePath.ReverseFind( TEXT('\\') );
        if ( nIndex != -1 ) { m_ModulePath.Format( TEXT("%s"), m_ModulePath.Left( nIndex + 1 ) ); }

        //日志文件名 m_LogFilePath = m_ModulePath + TEXT("ServiceLog.log"); //日期格式 m_DateFormat = TEXT("%Y-%m-%d %H:%M:%S"); //初始化语言环境为 中文 m_LocaleName = TEXT("chinese"); //文件句柄初始为 无效句柄值 m_LogFile = INVALID_HANDLE_VALUE; //下一次文件偏移量 m_liFileNextOffset.QuadPart = 0; //创建完成端口,设置最大并行数量为1 m_ciop.Create(1); } _MyLog::~_MyLog(void) { } void _MyLog::Init( CString LogFilePath, CString LocaleName, CString DateFormat ) { m_LogFilePath = LogFilePath; m_LocaleName = LocaleName; m_DateFormat = DateFormat; } void _MyLog::Open( void ) { m_LogFile = CreateFile( m_LogFilePath, GENERIC_WRITE, FILE_SHARE_READ, //共享读取 NULL, OPEN_ALWAYS, //打开文件若不存在则创建 FILE_FLAG_OVERLAPPED, //使用重叠IO 0); //将文件关联到完成端口 m_ciop.AssociateDevice(m_LogFile, 0 ); //获得文件的大小 GetFileSizeEx( m_LogFile, &m_liFileNextOffset ); //千万要加入这一句否则将导致无法输出带中文的字符串 //调试了几个小时 void* pBuff; #ifdef UNICODE pBuff = _UnicodeToANSI( m_LocaleName.GetBuffer() ); #else pBuff = m_LocaleName.GetBuffer(); #endif //语言本地化 degug_out.imbue( std::locale( (char*)pBuff ) ); #ifdef UNICODE if(pBuff){free(pBuff); pBuff = NULL;} #endif //可以说是测试输出,也可以说是让编译器生成 泛型模版与调用参数对应的方法, //否则在外部文件调用可能会出现连接错误 *this << time << table << TEXT("Test Output:") << (int)0 << (short)0 << (long)0 << (unsigned int)0 << (unsigned short)0 << (unsigned long)0 << (float)0 << (double)0 #ifdef UNICODE << (wchar_t*)L"0" << (const wchar_t*)L"0" << (wchar_t)0x30 #else << (char*)"0" << (const char*)"0" << (char)0x30 #endif << endl; } //关闭并释放相关资源 void _MyLog::Close( void ) { //关闭文件释放资源,这样会导致完成端口中所有未决的 //操作全部返回 if( m_LogFile != INVALID_HANDLE_VALUE ) { CloseHandle(m_LogFile); m_LogFile = INVALID_HANDLE_VALUE; } //关闭完成端口,这样做同上面一样,完成端口返回所有操作 m_ciop.Close(); //调用完成处理释放资源 CompleteHandle(); //释放所有资源 ThreadMap::iterator itera = m_ThreadMap.begin(); for( ; itera != m_ThreadMap.end(); ) { //释放创建的流 if( itera->second ){ delete itera->second; itera->second = NULL; } //删除映射中的对象 itera = m_ThreadMap.erase( itera ); } } //获得与线程相关联的流 p_stream _MyLog::GetThreadStream() { p_stream p_str = NULL; unsigned long id = GetCurrentThreadId(); ThreadMap::iterator itera = m_ThreadMap.find( id ); if ( itera == m_ThreadMap.end() ) { //该线程id无记录,创建一个流插入 p_str = new _stream; m_ThreadMap.insert( ThreadMap::value_type( id, p_str ) ); }else //查找到,返回相对应的流 { p_str = itera->second; } return p_str; } //写入日志 template<typename T> bool _MyLog::WriteLog( T info ) { //类型T必须支持<<操作符 if( IsValid() ) { p_stream p_str = GetThreadStream(); //向流中写入信息 *p_str << info; return true; } return false; } //完成处理、释放资源 bool _MyLog::CompleteHandle() { DWORD wNumBytes = 0; ULONG_PTR CompKey = 0; P_LogIO pIO = NULL; BOOL bResult = false; while(1) { //获得完成队列,如果pio不为null那么就将其释放 bResult = m_ciop.GetStatus( &wNumBytes, &CompKey, (OVERLAPPED**)&pIO, 0 ); if( pIO != NULL ) { delete pIO; pIO = NULL; } else { break; } } return true; } _MyLog& _MyLog::_endl() { if( this->IsValid() ) { //处理已完成的IO请求,主要用来释放资源 this->CompleteHandle(); //获得当前线程的流 p_stream p_str = GetThreadStream(); //插入换行符 *p_str << TEXT("\r\n"); #ifdef UNICODE //将流从宽字节转换为多字节 char * pBuff = _UnicodeToANSI( p_str->str().c_str() ); #else char * pBuff = (char *)malloc( p_str->str().length() + 1 ); strcpy_s( pBuff, p_str->str().length() + 1, p_str->str().c_str() ); #endif //将流清空 p_str->str( TEXT("") ); #ifdef DEBUG_OUT //是否输出到控制台 degug_out << pBuff; #endif //新建IO结构 P_LogIO pIO = new _LogIO; pIO->m_str = pBuff; //释放资源 free( pBuff ); //设置重叠结构的文件偏移量 pIO->Offset = this->m_liFileNextOffset.LowPart; pIO->OffsetHigh = this->m_liFileNextOffset.HighPart; //设置下一次文件偏移量 this->m_liFileNextOffset.QuadPart += pIO->m_str.length(); //投递重叠IO ::WriteFile( this->m_LogFile, pIO->m_str.c_str(), pIO->m_str.length(), NULL, pIO ); } return *this; } //文件是否有效 bool _MyLog::IsValid() { return ( m_LogFile == INVALID_HANDLE_VALUE ) ? false : true; } //重载操作符模版接口 template<typename T> _MyLog & _MyLog::operator << ( T info ) { WriteLog( info ); return *this; } //重载的操作符函数结构 _MyLog & _MyLog::operator << ( _MyLog &(__cdecl * pfn)( _MyLog & ) ) { return ((*pfn)(*this)); } //当前时间格式由m_DateFormat 指定 _MyLog & time( _MyLog & Log ) { //获得当前的时间,并写入日志文件 CString currenttime = CTime::GetCurrentTime().Format( Log.m_DateFormat ); Log.WriteLog( (LPCTSTR)currenttime ); return Log; } //线程id _MyLog & id( _MyLog & Log ) { //格式化线程ID #ifdef UNICODE wchar_t pBuff[12] = { 0 }; swprintf( pBuff, sizeof(pBuff), TEXT("%- 8X"), GetCurrentThreadId() ); #else char pBuff[12] = { 0 }; sprintf( pBuff, TEXT("%- 8X"), GetCurrentThreadId() ); #endif Log.WriteLog( pBuff ); return Log; } //制表符 _MyLog & table( _MyLog & Log ) { Log.WriteLog( TEXT("\t") ); return Log; } //空格符 _MyLog & space( _MyLog & Log ) { if(Log.IsValid()) { Log.WriteLog( TEXT(" ") ); } return Log; } //换行并刷新缓冲区所有数据写入对象中 _MyLog & endl( _MyLog & Log ) { return Log._endl(); } }

好了 基本就是这样了,欢迎各位交流指证,交流才会进步不是么?