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

请移步: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();
    }
}

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

猜你喜欢

转载自blog.csdn.net/cstringw/article/details/51759239