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)