シンプルで効率的なログシステム

8109 ワード

概要:簡単なログソリューションを使用して、パフォーマンスが高いソリューションを提供します.本モジュールはログ情報の一括書き込みファイルを実現し、タイミング自動flushをファイルに入れ、書き込みファイルのログレベルを動的に調整することができ、単一のログファイルサイズを構成することができ、ログファイルに循環して書き込むことができ、このように機械空間がログファイルに消耗されない.
キーワード:ログパフォーマンスログレベル
一、プログラムログは商品プログラムの中で不可欠な部分である.一般的に、ビジネス向けのプログラムでは、ログに似たような要件があります.
  • 性能要求
  • ランタイムログレベル調整可能
  • ログファイル領域使用セキュリティ問題
  • 次に,上記の問題について逐一プログラム実装を解析する.
    二、性能の問題.
    お客様のプログラムに対する要求はもちろん高いほど良いです.ログ印刷に一般的な方法を採用する場合、1つのログをファイルに書き込むと、パフォーマンスが低下します.プログラムは絶えずディスクと配信されるため、システムへの衝撃が大きく、通常のディスクIOリクエストに影響を及ぼす可能性があります.この問題に対して,一般的には,一括書き込みによる解決が行われている.ログを1つ書くたびに、ログをすぐにファイルに書き込むのではなく、バッファに先に書きます.このバッファが一定量に達すると、再び一括してファイルに書き込まれます.次のコード実装を参照してください.
        if (!strLog.IsEmpty())    {        m_strWriteStrInfo += GetCurTimeStr();        //                 if (enLevel == ENUM_LOG_LEVEL_ERROR)        {            m_strWriteStrInfo += _T("Error! ");        }        m_strWriteStrInfo += strLog;        m_strWriteStrInfo += _T("\r
    "); } if ( bForce || m_strWriteStrInfo.GetLength() > MAX_STR_LOG_INFO_LEN || m_iWriteBinLogLen > MAX_BIN_LOG_INFO_LEN/10) { // write info, WriteLogToFile(); }

    しかし、これは問題をもたらします.ログの量が少ない場合、大量に提出される量に達するには時間がかかる可能性があります.そうすると、プログラムがログを書いたことになりますが、ログライタはメッセージをバッファに書いて、ファイルの中でタイムリーに体現されていません.ログを出力するには、タイミングとタイミングの方法を使用します.プログラムは、バッファ内のログ・メッセージのタイミングを強制的にファイルにリフレッシュします.プログラムの使用の単純性を体現するために,この機能をログモジュールに配置して実現し,ログを呼び出すプログラムはタイミングを考慮せずにファイルをリフレッシュする.以下の手順を参照してください.
    CSuperLog::CSuperLog(void){    //             InitializeCriticalSection(&m_csWriteLog); //       m_strWriteStrInfo = WELCOME_LOG_INFO;    // Create the Logger thread.    m_hThread = (HANDLE)_beginthreadex( NULL, 0, &LogProcStart, NULL, 0, &m_uiThreadID );}unsigned __stdcall CSuperLog::LogProcStart( void* pArguments ){    int nCount = 1;     do     {        Sleep(300);        if (++nCount % 10 == 0 )        {            WriteLog(strTemp, ENUM_LOG_LEVEL_ERROR, true); //                  }    } while (m_bRun);}

    グローバルログクラス変数を採取し、コンストラクション関数でスレッドを開始し、スレッドは3秒おきにファイルをリフレッシュします.
    二、ログレベルの動的調整
    プログラムのログは一般的にログ分類されます.例えば、ログレベルにはデバッグログ、実行ログ、エラーログなどの分類があります.プログラムのパブリッシュ後に実行されると、通常は実行ログレベルに設定され、プログラム内のデバッグログは印刷されません.プログラムの実行中に分析問題を位置決めする必要がある場合は、ログレベルを下げ、デバッグ情報を印刷する必要があります.以下の手順を参照してください.
    int CSuperLog::WriteLog(CString &strLog,enLogInfoLevel enLevel/* = ENUM_LOG_LEVEL_RUN*/, bool bForce /*= false*/){    if (enLevel < m_iLogLevel)    {        return -1;    }    。。。}

    ログ・レベルの調整では、インプリメンテーションを呼び出し者に設定していません.このログ・レベル情報を共有メモリに保存するのではなく、ログ・レベルを調整するには、共有メモリを変更する小さなツールが必要です.実際には、設計全体でログシステムをより独立して設計し、外部呼び出しプログラムにできるだけ関連しないようにしたいと思っています.
            //      。        m_hMapLogFile = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,1024, _T("SuperLogShareMem"));        if (m_hMapLogFile != NULL)        {            //          。            m_psMapAddr = (LPTSTR)MapViewOfFile(m_hMapLogFile,FILE_MAP_ALL_ACCESS, 0,0,0);            if (m_psMapAddr != NULL)          {                _tcscpy_s(m_psMapAddr, 1024, g_pszLogLevel[m_iLogLevel]);                                  FlushViewOfFile(m_psMapAddr, _tcslen(g_pszLogLevel[m_iLogLevel]));                WriteLog(_T("                。"), ENUM_LOG_LEVEL_RUN);        }       }

    このログ・レベルに変化があるかどうかをスレッドでタイミングよくチェックし、変化がある場合は直ちに現在のレベル設定を調整します.
    三、ログファイルスペースの使用安全性の問題
    長期にわたって運行されている商品プログラムにとって、ファイルシステムの安全性の問題を考慮しなければならない.プログラムがゴミ情報を印刷し続けていると、あまり使えないので、ログファイルが大きくなる可能性があります.ユーザースペースがいっぱいになると、より深刻な問題を引き起こす可能性があります.したがって、ログファイルのサイズを制限する必要があります.プログラムではログファイルの交換を考慮し、3つのファイルを交互に書き、1ついっぱい書いた場合、1つのファイルを交換してから書き、ログファイルがディスクを消費することを考慮する必要はありません.
    CSuperLog::enLogStatus CSuperLog::OpenLogFile(void){    EnterCriticalSection(&m_csWriteLog);      for (int iRunCount = 0; iRunCount < MAX_LOG_FILE_COUNT; iRunCount++)    {        if (m_pFile == NULL)        {            m_pFile = new CStdioFile;            if (m_pFile == NULL)            {                LeaveCriticalSection(&m_csWriteLog);                return m_enStatus = ENUM_LOG_INVALID;            }              BOOL bRet = m_pFile->Open(                g_pszLogFileName[(m_iCurLogFileSeq++)%MAX_LOG_FILE_COUNT],                CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone | CFile::modeNoTruncate);            if (bRet)            {                WriteUnicodeHeadToFile(m_pFile);            }            else            {        delete m_pFile;                m_pFile = NULL;                LeaveCriticalSection(&m_csWriteLog);                return m_enStatus = ENUM_LOG_INVALID;            }        }        if (m_pFile->GetLength() > MAX_LOG_FILE_LEN)        {            m_pFile->Close();            BOOL bRet = FALSE;            //                      。            if (m_iCurLogFileSeq >= MAX_LOG_FILE_COUNT)             {                //          ,            ,                       bRet = m_pFile->Open(                    g_pszLogFileName[(m_iCurLogFileSeq++)%MAX_LOG_FILE_COUNT],                    CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone);             }            else            {                //        ,                          bRet = m_pFile->Open(                    g_pszLogFileName[(m_iCurLogFileSeq++)%MAX_LOG_FILE_COUNT],                    CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone | CFile::modeNoTruncate);            }            if (bRet)            {                WriteUnicodeHeadToFile(m_pFile);            }            else            {                delete m_pFile;                m_pFile = NULL;                LeaveCriticalSection(&m_csWriteLog);                return m_enStatus = ENUM_LOG_INVALID;            }        }        else        {            break;        }    }    m_pFile->SeekToEnd();    LeaveCriticalSection(&m_csWriteLog);    return m_enStatus = ENUM_LOG_RUN;}

    四、その他の部分
    プログラムではCStdioFileを使用してファイル書き込みを処理していますが、実装ではtextモードでファイル書き込みを開くと、中国語文字が書き込めないという問題があります.いくつかの資料を探して、文字の符号化の問題であることを発見しました.1つの解決策は、バイナリ方式で開き、ファイルの先頭にunicodeヘッダIDを書き込むことです.
    int CSuperLog::WriteUnicodeHeadToFile(CFile * pFile){    if (pFile == NULL)    {        return -1;    }    try    {   if (pFile->GetLength() == 0)        {            m_pFile->Write("\377\376", 2); //   FF FE             if (m_enStatus == ENUM_LOG_RUN)            {                m_pFile->WriteString(WELCOME_LOG_INFO);            }            m_pFile->Flush();        }    }    catch (...)    {        return -1;    }    return 0;}

    呼び出し者ができるだけ簡単であることを保証するために,プログラムはクラスインタフェースを静的メソッドとして実現し,呼び出しは直接使用できる.
    #define   WRITE_LOG           CSuperLog::WriteLog#define   LOG_LEVEL_DEBUG     CSuperLog::ENUM_LOG_LEVEL_DEBUG#define   LOG_LEVEL_RUN       CSuperLog::ENUM_LOG_LEVEL_RUN#define   LOG_LEVEL_ERROR     CSuperLog::ENUM_LOG_LEVEL_ERROR

    呼び出し元は次のように使用されます.
    //      #include "common/SuperLog.h"WRITE_LOG(_T("      ,    。"), LOG_LEVEL_ERROR);

    ログ・スレッドは、グローバル変数の構造関数で終了を通知します.この場合、ログも印刷される可能性があります.パフォーマンスを保証するために,現在の時間の文字列を取得する際に2つの静的局所変数を用いた.
    CString& CSuperLog::GetCurTimeStr(){    static CTime g_tmCurTime;    g_tmCurTime = CTime::GetCurrentTime();// time(NULL);    CString g_strTime;    g_strTime = g_tmCurTime.Format(_T("%Y-%m-%d %H:%M:%S "));    return g_strTime;}

    使用中に、終了するたびにログ印刷がある場合、プログラムは常に異常であることがわかりました.その後の解析から,静的グローバル変数は毎回グローバル変数より先に解析され,strTime解析後に無効なアクセスをもたらすことが分かった.この変数をグローバル変数回避に変えるしかない.
    CString& CSuperLog::GetCurTimeStr(){    g_tmCurTime = CTime::GetCurrentTime();// time(NULL);    g_strTime = g_tmCurTime.Format(_T("%Y-%m-%d %H:%M:%S "));    return g_strTime;}

    四、終わりの言葉
    プログラムの実現は急いで、基本的な機能はすべてデバッグし終わったが、現在はパラメータ付きの書き込みログインタフェースがまだ書かれていないし、バイナリコンテンツログ情報のインタフェースも実現していない.後続の作者はタイムリーに完成します.興味のある人は勉強しないでメールで連絡することができます.Email:[email protected]