Use of the general log component for Windows C++ applications

  • 2020-04-01 23:28:14
  • OfStack

The introduction

There are generally three options for how to log a program:

1, the use of Log4CXX and other public open source log components: this kind of log components are characterized by cross-platform and relatively powerful functions, such as the log can be sent to another server or recorded to the database medium; In addition, high configurability, the log can be a lot of personalized Settings through the configuration file or program code. But from another point of view, because these advantages often lead to the use of disadvantages. First, for the average application, they don't require much functionality, usually just logging to a file or feeding back to the application, which makes it tedious for the user to use and loads of code that's never used. Secondly, this kind of logging component is usually a cross-platform, not only for Windows or VC application, therefore always felt a bit awkward to use, such as their characters are in the char type, each write a log for a Unicode program to do character conversion is a very happy thing, ever use Log4Cpp this in many years ago, when the program execution always have a memory leak report logging component, though is likely to be false positives, but always feel uncomfortable to use.

2. Write a few simple classes or functions to log yourself: this method is really simple and usually doesn't require a line or two of code. However, this method usually lacks standardization and universality. When other programs need to record similar but somewhat different logs, the usual method is copy-pastel-modify. In addition, this type of approach probably does not take into account performance or concurrency issues, and is usually written directly in the worker thread, which is absolutely not allowed for applications with high performance requirements.

3. Don't log anything at all: indeed, many programs today don't log anything for a variety of reasons. However, if a program is useful, has certain functions, and needs to run continuously for a long period of time, then logging is necessary; Otherwise, consider carefully whether the program is necessary.
 

design

To sum up, writing a generic logging component should focus on three areas: functionality, availability, and performance. The following section details the considerations for these aspects when designing the log component:

1. Functionality: the purpose of this logging component is to meet the logging requirements of most applications -- to output logs to files or send them to applications without providing complex but infrequently used functionality. The functions of this log component include:

      Outputs log information to the specified file

      Generate one log file per day

      For GUI programs, log information can be sent to the specified window

      For the Console application, you can send log information to standard output (STD ::cout)

      Support for MBCS/UNICODE, Console/GUI programs

      Supports dynamic loading and static loading of log component DLLS

      Support the DEBUG/TRACE/INFO/WARN/ERROR/FATAL multiple log level

2. Usability: this log component focuses on usability to make it easy and comfortable for users:

      Simplicity and purity: not dependent on any library or framework

      The interface is simple to use and requires no complex configuration or setup work

      The CStaticLogger and CDynamicLogger wrapper classes are provided for static or dynamic loading and operation log components without the user having to pay attention to the loading details

      A program that logs multiple log files simply creates a CStaticLogger or CDynamicLogger object for each log file

      Just call the Log ()/Debug ()/Trace ()/Info/Warn () ()/Error ()/Fatal Log () method

      The logging method supports variable parameters

      Log output format: < time > < Thread ID > < The level of logging > < Log contents >

3. Performance: performance is a hard indicator of whether a component is worth using. Performance optimization is taken into consideration in the process from design to coding:

      Support for multi-threaded simultaneous log writing requests

      Using a separate thread to write logs in the background does not affect the normal execution of the worker thread

      Batch logging in batch mode


< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201305/2013050810404414.jpg ">
< img Alt = "" border = 0 SRC =" / / files.jb51.net/file_images/article/201305/2013050810404415.jpg ">


interface

1. ILogger: logging component object interface


/******************************************************************************
 Module:  Logger.h
 Notices: Copyright (c) 2012 Bruce Liang - http://www.cnblogs.com/ldcsaa/

 Purpose:  Log the program. 
         1.  Outputs log information to the specified file 
         2.  for  GUI  Procedures, can send log information to the specified window 
         3.  for Console Application that can send log information to standard output  (std::cout)

 Desc:
         1 And features: 
         --------------------------------------------------------------------------------------
         a)  Outputs log information to the specified file 
         b)  Generate one log file per day 
         c)  for  GUI  Procedures, can send log information to the specified window 
         d)  for Console Application that can send log information to standard output  (std::cout)
         e)  support  MBCS / UNICODE . Console / GUI  The program 
         f)  Support for dynamic and static loading of log components  DLL
         g)  support  DEBUG/TRACE/INFO/WARN/ERROR/FATAL  Wait for multiple log levels 

         2 Availability: 
         --------------------------------------------------------------------------------------
         a)  Simplicity and purity: not dependent on any library or framework 
         b)  The interface is simple to use and requires no complex configuration or setup work 
         c)  provide  CStaticLogger  and  CDynamicLogger  Wrapper classes are used for static or dynamic loading and for manipulating log components without the user having to pay attention to the loading details 
         d)  A program that logs multiple log files simply creates a corresponding log file for each log file  CStaticLogger  or  CDynamicLogger  object 
         e)  Just call  Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal()  Methods such as logging 
         f)  The logging method supports variable parameters 
         g)  Log output format: < time > < thread ID> < The level of logging > < Log contents >

         3 And performance: 
         --------------------------------------------------------------------------------------
         a)  Support for multi-threaded simultaneous log writing requests 
         b)  Using a separate thread to write logs in the background does not affect the normal execution of the worker thread 
         c)  Batch logging in batch mode 

 Usage:
          Method 1: static loading  Logger DLL ) 
         --------------------------------------------------------------------------------------
         0.  The application contains  StaticLogger.h  The header file 
         1.  create  CStaticLogger  Object (usually global) 
         2.  call  CStaticLogger->Init(...)  Initialize the log component 
         3.  use  CStaticLogger->Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal()  Methods such as writing a log 
         4.  call  CStaticLogger->UnInit(...)  Clean log component ( CStaticLogger  Log components are also automatically cleaned when object destructions occur.) 

          Method 2: dynamic loading  Logger DLL ) 
         --------------------------------------------------------------------------------------
         0.  The application contains  DynamicLogger.h  The header file 
         1.  create  CDynamicLogger  Object (usually global) 
         2.  call  CDynamicLogger->Init(...)  Initialize the log component 
         3.  use  CDynamicLogger->Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal()  Methods such as writing a log 
         4.  call  CDynamicLogger->UnInit(...)  Clean log component ( CDynamicLogger  Log components are also automatically cleaned when object destructions occur.) 

          Method 3: load directly with the derived function  Logger DLL ) 
         --------------------------------------------------------------------------------------
         0.  The application contains  Logger.h  The header file 
         1.  Manually call  ILoger_Create()  and  ILoger_Create()  Export function creation and destruction  ILogger  object  
          Note: if dynamic loading, manual call  ::LoadLibrary()/::FreeLibrary()  A series of  API  Function load and unload  Logger DLL ) 

         [
             *****  For those who want to receive log information through a window  GUI  The program  *****

             A.  Invoked after successful initialization of the log component  SetGUIWindow(HWND)  Set a window to receive logs 
             B.  The window must respond to processing  LOG_MESSAGE  The message 
             C.  processed  LOG_MESSAGE  After the message, the call  ILogger::FreeLogMsg()  Destroy the received  TLogMsg 
         ]

 Environment:
         1. Windows 2000 or later (_WIN32_WINNT >= 0x0500)
         2. VC++ 2010 or later

 Release:
         1. Logger_C.dll     - Console/MBCS/Release
         2. Logger_CD.dll    - Console/MBCS/Debug
         3. Logger_CU.dll    - Console/Unicode/Release
         4. Logger_CUD.dll   - Console/Unicode/Debug
         5. Logger.dll       - GUI/MBCS/Release
         6. Logger_D.dll     - GUI/MBCS/Debug
         7. Logger_U.dll     - GUI/Unicode/Release
         8. Logger_UD.dll    - GUI/Unicode/Debug

 Examples:
         1. TestGUILogger        - GUI  Version test program         (static loading) 
         2. TestDynamicLogger    - GUI  Version test program         (dynamic loading) 
         3. TestConsoleLogger    - Console  Version test program    (static loading) 

 ******************************************************************************/

 #pragma once

 
 /********** imports / exports Logger.dll **********/

 #ifdef LOGGER_EXPORTS
     #define LOGGER_API __declspec(dllexport)
     //#define TRY_INLINE    inline
 #else
     #define LOGGER_API __declspec(dllimport)
     //#define TRY_INLINE
 #endif

 
 

 class LOGGER_API ILogger
 {
 public:
     
     enum LogLevel
     {
         LL_NONE     = 0XFF,
         LL_DEBUG    = 1,
         LL_TRACE    = 2,
         LL_INFO     = 3,
         LL_WARN     = 4,
         LL_ERROR    = 5,
         LL_FATAL    = 6
     };

     
     enum ErrorCode
     {
         //No error
         EC_OK    = NO_ERROR,
         //Error related to file operation
         EC_FILE_GENERIC,
         EC_FILE_FILENOTFOUND,
         EC_FILE_BADPATH,
         EC_FILE_TOMANYOPERFILES,
         EC_FILE_ACCESSDENIED,
         EC_FILE_INVALIDFILE,
         EC_FILE_REMOVECURRENTDIR,
         EC_FILE_DIRECTORYFULL,
         EC_FILE_BADSEEK,
         EC_FILE_HARDIO,
         EC_FILE_SHARINGVIOLATION,
         EC_FILE_LOCKVIOLATION,
         EC_FILE_DISKFULL,
         EC_FILE_ENDOFFILE,
         //Other errors
         EC_INVALID_STATE,
         EC_INIT_LOGLEVEL,
         EC_INIT_PRINTFLAG,
         EC_INIT_CREATE_LOG_THREAD_FAIL
     };

     
     struct TLogMsg
     {
         DWORD       m_dwSize;       //Structure size - changes dynamically with message length
         LogLevel    m_logLevel;     //The level of logging
         UINT        m_uiThreadID;   //Thread ID
         SYSTEMTIME  m_stMsgTime;    //Recording time
         TCHAR       m_psMsg[1];     //The message content
     };

 public:
     ILogger(void);
     virtual ~ILogger(void);
 private:
     ILogger(const ILogger&);
     ILogger& operator = (const ILogger&);

 public:
     //Log component initialization method
     virtual BOOL Init(
                         LPCTSTR logFile  = NULL                  //Log file. Default: {AppPath}/logs/{AppName}- yyyymdd. Log
                       , LogLevel ll      = DEFAULT_LOG_LEVEL     //The level of logging.  The default : [Debug -> LL_DEBUG] / [Release -> LL_INFO]
                       , int printFlag    = DEFAULT_PRINT_FLAG    //Output mask. Output to file and/or screen. Default: output to file only
                      )       = 0;
     //Log component cleanup method
     virtual BOOL UnInit()    = 0;

 public:
     //Write the log method: pass in the log content string (this method is most efficient for log text that does not need to be formatted)
     virtual void Log_0  (LogLevel ll, LPCTSTR msg) = 0;
     virtual void Debug_0(LPCTSTR msg);
     virtual void Trace_0(LPCTSTR msg);
     virtual void Info_0 (LPCTSTR msg);
     virtual void Warn_0 (LPCTSTR msg);
     virtual void Error_0(LPCTSTR msg);
     virtual void Fatal_0(LPCTSTR msg);

     //Write the log method: pass in the format string and the parameter stack pointer (usually only used within the component)
     virtual void LogV   (LogLevel ll, LPCTSTR format, va_list arg_ptr);

     //Logging method: pass in a formatted string and variable parameters (very flexible and easy)
     virtual void Log     (LogLevel ll, LPCTSTR format, ...);
     virtual void Debug   (LPCTSTR format, ...);
     virtual void Trace   (LPCTSTR format, ...);
     virtual void Info    (LPCTSTR format, ...);
     virtual void Warn    (LPCTSTR format, ...);
     virtual void Error   (LPCTSTR format, ...);
     virtual void Fatal   (LPCTSTR format, ...);

     //Logging methods: pass in a formatted string and mutable parameters (similar to the previous set of methods, but check the logging level before doing anything)
     virtual void TryLog     (LogLevel ll, LPCTSTR format, ...);
     virtual void TryDebug   (LPCTSTR format, ...);
     virtual void TryTrace   (LPCTSTR format, ...);
     virtual void TryInfo    (LPCTSTR format, ...);
     virtual void TryWarn    (LPCTSTR format, ...);
     virtual void TryError   (LPCTSTR format, ...);
     virtual void TryFatal   (LPCTSTR format, ...);

     //General auxiliary method
     virtual BOOL HasInited           ()        const    = 0;        //Whether already initialized & NBSP; & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent
     virtual BOOL IsPrint2File        ()        const    = 0;        //Whether to output the log to the file & NBSP; & have spent & have spent
     virtual BOOL IsPrint2Screen      ()        const    = 0;        //Whether to output the log to the screen window & NBSP; & have spent & have spent
     virtual int    GetPrintFlag      ()        const    = 0;        //Print sign & NBSP; & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent & have spent
     virtual LogLevel    GetLogLevel  ()        const    = 0;        //The level of logging        
     virtual LPCTSTR        GetLogFile()        const    = 0;        //The log file
     virtual ErrorCode    GetLastError()        const    = 0;        //Current operation error code

 
 #ifdef _WINDOWS
     public:
         //Set the window to receive log information, hWndGUI == NULL will cancel the receive
         virtual void SetGUIWindow(HWND hWndGUI)    = 0;
         //Gets the window to receive log information
         virtual HWND GetGUIWindow()                = 0;

         //Destroy the TLogMsg object that was created dynamically when the LOG_MESSAGE message was sent
         virtual void FreeLogMsg(const TLogMsg* pLogMsg);

         //Virtual window handle mask: used as the sending source identity when sending a LOG_MESSAGE message to a GUI window
         static const int LOGGER_FAKE_WINDOW_BASE = 0X80001111;
         //Custom log message: this message sends the log to the GUI window
         //Where: WPARAM -> ILogger object pointer, LPARAM -> TLogMsg structure pointer
         static const int LOG_MESSAGE = WM_USER | (0x7FFF & LOGGER_FAKE_WINDOW_BASE);
 #endif

 public:
     static const int PRINT_FLAG_FILE            = 0x00000001;            //Print to file
     static const int PRINT_FLAG_SCREEN          = 0x00000002;            //Print to screen
     static const int DEFAULT_PRINT_FLAG         = PRINT_FLAG_FILE;        //Default log mask
     static const LogLevel DEFAULT_LOG_LEVEL     = 
 #ifdef _DEBUG
                 LL_DEBUG
 #else
                 LL_INFO
 #endif

 };

 
 

 //Create an ILogger object
 EXTERN_C LOGGER_API ILogger* ILogger_Create();
 //Destroy the ILogger object
 EXTERN_C LOGGER_API void ILogger_Destroy(ILogger* p);

 //Gets a literal description of each log level
 EXTERN_C LOGGER_API LPCTSTR    ILogger_GetLogLevelDesc (ILogger::LogLevel ll);
 //Gets a literal description of each operation error code
 EXTERN_C LOGGER_API LPCTSTR    ILogger_GetErrorDesc    (ILogger::ErrorCode ec);

 

  The comments in the code basically explain how to use the log component, but here are some simple generalizations:

Version: The log component is provided in the form of a DLL, which has been compiled into 8 versions of Debug/Release, MBCS/Unicode, and GUI/Console

Testing: Three test programs, TestGUILogger, TestDynamicLogger, and TestConsoleLogger, are used to test all versions. Among them, TestDynamicLogger loads the Logger DLL dynamically

Usage:

The application contains the logger.h header file
1. Call the ILogger_Create() export function to create the ILogger object
2. Call ILogger - > Init (...). Initialize the log component
3. Use the ILogger - > The Log ()/Debug ()/Trace ()/Info/Warn () ()/Error ()/Fatal writing Log () method
4. Call ILogger - > UnInit (...). Clean up log component
5. Call the ILogger_Destroy() export function to destroy the ILogger object
 

2. CStaticLogger: ILogger wrapper (smart pointer) -- used for statically loading Logger DLL


#pragma once

 #include "Logger.h"

 
 /********* http://www.cnblogs.com/ldcsaa/ *********/
 
 

 class LOGGER_API CStaticLogger
 {
 public:
     //Constructor: if bCreate is TRUE, create the ILogger object while building the CStaticLogger instance
     CStaticLogger(BOOL bCreate = TRUE);
     //The destructor
     ~CStaticLogger();
 private:
     CStaticLogger(const CStaticLogger&);
     CStaticLogger& operator = (const CStaticLogger&);

 public:
     inline void Reset           (ILogger* pLogger);     //Resets its encapsulated ILogger pointer
     inline BOOL IsValid         ()    const;            //Determines whether the ILogger pointer it encapsulates is non-null
     inline ILogger* Get         ()    const;            //Gets the ILogger pointer
     inline ILogger& operator *  ()    const;            //Gets the ILogger reference
     inline ILogger* operator -> ()    const;            //Gets the ILogger pointer
     inline operator ILogger*    ()    const;            //Convert to ILogger pointer

 private:
     ILogger* m_pLogger;
 };

CStaticLogger is designed to simplify the use of logging components for static Logger DLL loading situations. Usage:

The application contains the staticlogger.h header file
1. Create a CStaticLogger object (usually a global object)
2. Call CStaticLogger - > Init (...). Initialize the log component
3. Use the CStaticLogger - >The Log ()/Debug ()/Trace ()/Info/Warn () ()/Error ()/Fatal writing Log () method
4. Call CStaticLogger - > UnInit (...). Clean up the log component (CStaticLogger object destructions also clean up the log component automatically)

 

3. CDynamicLogger: ILogger wrapper (smart pointer) -- used to dynamically load Logger DLL


#pragma once

 #include "Logger.h"

 
 /********* http://www.cnblogs.com/ldcsaa/ *********/
 

 #ifdef _DEBUG
     #ifdef _UNICODE
         #ifdef _WINDOWS
             #define DEF_LOGGER_DLL_FILE_PATH    _T("Logger_UD.dll")
         #else
             #define DEF_LOGGER_DLL_FILE_PATH    _T("Logger_CUD.dll")
         #endif
     #else
         #ifdef _WINDOWS
             #define DEF_LOGGER_DLL_FILE_PATH    _T("Logger_D.dll")
         #else
             #define DEF_LOGGER_DLL_FILE_PATH    _T("Logger_CD.dll")
         #endif
     #endif
 #else
     #ifdef _UNICODE
         #ifdef _WINDOWS
         #define DEF_LOGGER_DLL_FILE_PATH        _T("Logger_U.dll")
         #else
             #define DEF_LOGGER_DLL_FILE_PATH    _T("Logger_CU.dll")
         #endif
     #else
         #ifdef _WINDOWS
             #define DEF_LOGGER_DLL_FILE_PATH    _T("Logger.dll")
         #else
             #define DEF_LOGGER_DLL_FILE_PATH    _T("Logger_C.dll")
         #endif
     #endif
 #endif

 
 

 //Create an ILogger object
 typedef ILogger*            (*FN_ILogger_Create)            ();
 //Destroy the ILogger object
 typedef void                (*FN_ILogger_Destroy)           (ILogger* p);
 //Gets a literal description of each log level
 typedef LPCTSTR             (*FN_ILogger_GetLogLevelDesc)   (ILogger::LogLevel ll);
 //Gets a literal description of each operation error code
 typedef LPCTSTR             (*FN_ILogger_GetErrorDesc)      (ILogger::ErrorCode ec);

 
 
 

 class CDynamicLogger
 {
 public:
     //Constructor: if bLoad is TRUE, an ILogger object is created while building the CDynamicLogger example
     CDynamicLogger(BOOL bLoad = TRUE, LPCTSTR lpszFilePath = DEF_LOGGER_DLL_FILE_PATH)
     {
         Reset();

         if(bLoad)
             Load(lpszFilePath);
     }

     //The destructor
     ~CDynamicLogger()
     {
         Free();
     }

 private:
     CDynamicLogger(const CDynamicLogger&);
     CDynamicLogger& operator = (const CDynamicLogger&);

 public:
     //Create an ILogger object
     ILogger* ILogger_Create()
         {return m_fnILoggerCreate();}
     //Destroy the ILogger object
     void ILogger_Destroy(ILogger* p)
         {m_fnILoggerDestroy(p);}
     //Gets a literal description of each log level
     LPCTSTR    ILogger_GetLogLevelDesc(ILogger::LogLevel ll)
         {return m_fnILoggerGetLogLevelDesc(ll);}
     //Gets a literal description of each operation error code
     LPCTSTR    ILogger_GetErrorDesc(ILogger::ErrorCode ec)
         {return m_fnILoggerGetErrorDesc(ec);}

     //Load the Logger DLL
     BOOL Load(LPCTSTR lpszFilePath = DEF_LOGGER_DLL_FILE_PATH)
     {
         if(IsValid())
             return FALSE;

         BOOL isOK = FALSE;
         m_hLogger = ::LoadLibrary(lpszFilePath);

         if(m_hLogger)
         {
             m_fnILoggerCreate            = (FN_ILogger_Create)            ::GetProcAddress(m_hLogger, "ILogger_Create");
             m_fnILoggerDestroy           = (FN_ILogger_Destroy)           ::GetProcAddress(m_hLogger, "ILogger_Destroy");
             m_fnILoggerGetLogLevelDesc   = (FN_ILogger_GetLogLevelDesc)   ::GetProcAddress(m_hLogger, "ILogger_GetLogLevelDesc");
             m_fnILoggerGetErrorDesc      = (FN_ILogger_GetErrorDesc)      ::GetProcAddress(m_hLogger, "ILogger_GetErrorDesc");

             if(m_fnILoggerCreate && m_fnILoggerDestroy)
             {
                 m_pLogger   = ILogger_Create();
                 isOK        = (m_pLogger != NULL);
             }
         }

         if(!isOK)
             Free();

         return isOK;
     }

     //Uninstall the Logger DLL
     BOOL Free()
     {
         if(!IsValid())
             return TRUE;

         BOOL isOK = TRUE;

         if(m_pLogger)    ILogger_Destroy(m_pLogger);
         if(m_hLogger)    isOK = ::FreeLibrary(m_hLogger);

         Reset();

         return isOK;
     }

     BOOL IsValid            ()    const    {return m_pLogger != NULL;}  //Determines whether the ILogger pointer it encapsulates is non-null
     ILogger* Get            ()    const    {return m_pLogger;}          //Gets the ILogger pointer
     ILogger& operator *     ()    const    {return *m_pLogger;}         //Gets the ILogger reference
     ILogger* operator ->    ()    const    {return m_pLogger;}          //Gets the ILogger pointer
     operator ILogger*       ()    const    {return m_pLogger;}          //Convert to ILogger pointer

 private:
     void Reset()
     {
         m_hLogger                    = NULL;
         m_pLogger                    = NULL;
         m_fnILoggerCreate            = NULL;
         m_fnILoggerDestroy           = NULL;
         m_fnILoggerGetLogLevelDesc   = NULL;
         m_fnILoggerGetErrorDesc      = NULL;
     }

 private:
     HMODULE         m_hLogger;
     ILogger*        m_pLogger;

     FN_ILogger_Create            m_fnILoggerCreate;
     FN_ILogger_Destroy           m_fnILoggerDestroy;
     FN_ILogger_GetLogLevelDesc   m_fnILoggerGetLogLevelDesc;
     FN_ILogger_GetErrorDesc      m_fnILoggerGetErrorDesc;
 };

 

CDynamicLogger is designed to simplify the use of log components and is used to dynamically load Logger DLLS. Usage:

Application contains the dynamiclogger.h header file
1. Create a CDynamicLogger object (usually a global object)
2. Call CDynamicLogger - > Init (...). Initialize the log component
3. Use the CDynamicLogger - > The Log ()/Debug ()/Trace ()/Info/Warn () ()/Error ()/Fatal writing Log () method
4. Call CDynamicLogger - > UnInit (...). Clean up the log component (CDynamicLogger will also clean up the log component automatically when destructing the object)


Related articles: