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

来源:互联网 发布:我的少女时代 知乎 编辑:程序博客网 时间:2024/06/06 07:13
  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        wcouttypedef wstringstream  _stream;typedef wstringstream*   p_stream;#else#define degug_out        couttypedef stringstream   _stream;typedef stringstream*    p_stream;#endif//C++映射(map)对象,unsigned long 用于线程的ID 类型为DWORDtypedef map<unsigned long , p_stream > ThreadMap;//定义在名称空间中,为了避免与其他函数混淆 如:time、endlnamespace _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();    }}

好了 基本就是这样了,欢迎各位交流指证,交流才会进步不是么?
链接:http://download.csdn.net/detail/cstringw/9559463

2 0