基于Windows C++ 应用程序通用日志组件的使用详解

引言

  在如何记录程序日志方面,通常有三种选择:

  1、采用Log4CXX等公共开源日志组件:这类日志组件的特点是跨平台且功能比较强大,例如可以把日志发往另一台服务器或记录到数据库中等;另外,可配置性较高,可以通过配置文件或程序代码对日志进行很多个性化设置。但从另外一个角度看,由于这些优点往往也导致了在使用方面的缺点。首先,对于一般应用程序来说,它们并不需要太多的功能,通常只需要把日志记录到文件或反馈到应用程序,功能太多反正让用户使用起来觉得繁琐还得背负很多从来都用不到的代码。其次,这类日志组件通常是跨平台的,并不只是针对 Windows 或 VC 的应用程序,因此使用起来总会觉得有点别扭,例如他们的字符都是用 char 类型的,对于一个 Unicode 程序来说每次写日志都要做字符转换是很不爽的事情,本座在多年前曾经使用过 Log4Cpp ,程序执行时总是报告日志组件有内存泄露,虽然有可能是误报,但是使用起来总觉得很不舒服。

  2、自己写几个简单的类或函数记录日志:这种方法的确很简单,通常都不用一两百行的代码。但这种方法通常缺乏规范性和通用性,其他程序需要记录类似的但有点差异的日志时,通常的作法是:Copy-Paste-Modify;另外,这类方法很可能也没有考虑性能或并发方面的问题,通常是直接在工作线程中写日志,对于那些性能要求较高的应用程序是绝对不允许的。

  3、干脆不记录任何日志:的确,现在很多程序由于各种原因并没有记录任何日志。但本座以为,如果一个程序是有用的,具备一定功能,并且需要连续运行较长一段时间,那么记录日志是必须的;否则,得认真考虑该程序是否有存在的必要了。

设计

  综上所述,编写一个通用的日志组件应该着重考虑三个方面:功能、可用性和性能。下面,本座详细说明在设计日志组件时对这些方面问题的考虑:

  1、功能:本日志组件的目的是满足大多数应用程序记录日志的需求 —— 把日志输出到文件或发送到应用程序中,并不提供一些复杂但不常用的功能。本日志组件的功能包括:

把日志信息输出到指定文件

每日生成一个日志文件

对于 GUI 程序,可以把日志信息发送到指定窗口

对于Console应用程序,可以把日志信息发往标准输出 (std::cout)

支持 MBCS / UNICODE,Console / GUI 程序

支持动态加载和静态加载日志组件 DLL

支持 DEBUG/TRACE/INFO/WARN/ERROR/FATAL 等多个日志级别

  2、可用性:本日志组件着重考虑了可用性,尽量让使用者用起来觉得简便、舒心:

简单纯净:不依赖任何程序库或框架

使用接口简单,不需复杂的配置或设置工作

提供 CStaticLogger 和 CDynamicLogger 包装类用于静态或动态加载以及操作日志组件,用户无需关注加载细节

程序如果要记录多个日志文件只需为每个日志文件创建相应的 CStaticLogger 或 CDynamicLogger 对象

只需调用 Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal() 等方法记录日志

日志记录方法支持可变参数

日志输出格式:<时间> <线程ID> <日志级别> <日志内容>

  3、性能:性能是组件是否值得使用的硬指标,本组件从设计到编码的过程都尽量考虑到性能优化:

支持多线程同时发送写日志请求

使用单独线程在后台写日志,不影响工作线程的正常执行

采用批处理方式批量记录日志


接口

  1、ILogger:日志组件对象接口


代码如下:

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

Purpose: 记录程序日志。
         1. 把日志信息输出到指定文件
         2. 对于 GUI 程序,可以把日志信息发送到指定窗口
         3. 对于Console应用程序,可以把日志信息发往标准输出 (std::cout)

Desc:
         1、功能:
         --------------------------------------------------------------------------------------
         a) 把日志信息输出到指定文件
         b) 每日生成一个日志文件
         c) 对于 GUI 程序,可以把日志信息发送到指定窗口
         d) 对于Console应用程序,可以把日志信息发往标准输出 (std::cout)
         e) 支持 MBCS / UNICODE,Console / GUI 程序
         f) 支持动态加载和静态加载日志组件 DLL
         g) 支持 DEBUG/TRACE/INFO/WARN/ERROR/FATAL 等多个日志级别

2、可用性:
         --------------------------------------------------------------------------------------
         a) 简单纯净:不依赖任何程序库或框架
         b) 使用接口简单,不需复杂的配置或设置工作
         c) 提供 CStaticLogger 和 CDynamicLogger 包装类用于静态或动态加载以及操作日志组件,用户无需关注加载细节
         d) 程序如果要记录多个日志文件只需为每个日志文件创建相应的 CStaticLogger 或 CDynamicLogger 对象
         e) 只需调用 Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal() 等方法记录日志
         f) 日志记录方法支持可变参数
         g) 日志输出格式:<时间> <线程ID> <日志级别> <日志内容>

3、性能:
         --------------------------------------------------------------------------------------
         a) 支持多线程同时发送写日志请求
         b) 使用单独线程在后台写日志,不影响工作线程的正常执行
         c) 采用批处理方式批量记录日志

Usage:
         方法一:(静态加载 Logger DLL)
         --------------------------------------------------------------------------------------
         0. 应用程序包含 StaticLogger.h 头文件
         1. 创建 CStaticLogger 对象(通常为全局对象)
         2. 调用 CStaticLogger->Init(...) 初始化日志组件
         3. 使用 CStaticLogger->Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal() 等方法写日志
         4. 调用 CStaticLogger->UnInit(...) 清理日志组件(CStaticLogger 对象析构时也会自动清理日志组件)

方法二:(动态加载 Logger DLL)
         --------------------------------------------------------------------------------------
         0. 应用程序包含 DynamicLogger.h 头文件
         1. 创建 CDynamicLogger 对象(通常为全局对象)
         2. 调用 CDynamicLogger->Init(...) 初始化日志组件
         3. 使用 CDynamicLogger->Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal() 等方法写日志
         4. 调用 CDynamicLogger->UnInit(...) 清理日志组件(CDynamicLogger 对象析构时也会自动清理日志组件)

方法三:(直接用导出函数加载 Logger DLL)
         --------------------------------------------------------------------------------------
         0. 应用程序包含 Logger.h 头文件
         1. 手工调用 ILoger_Create() 和 ILoger_Create() 导出函数创建和销毁 ILogger 对象
         (注:如果是动态加载,需手工调用 ::LoadLibrary()/::FreeLibrary() 系列 API 函数加载和卸载 Logger DLL)

[
             ***** 对于希望通过窗口接收日志信息的 GUI 程序 *****

A. 日志组件初始化成功后调用 SetGUIWindow(HWND) 设置收日志的窗口
             B. 窗口须响应处理 LOG_MESSAGE 消息
             C. 处理完 LOG_MESSAGE 消息后,调用 ILogger::FreeLogMsg() 销毁接收到的 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 版测试程序       (静态加载)
         2. TestDynamicLogger    - GUI 版测试程序       (动态加载)
         3. TestConsoleLogger    - Console 版测试程序  (静态加载)

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

#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
     {
         // 无错误
         EC_OK    = NO_ERROR,
         // 文件操作相关的错误
         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,
         // 其他错误
         EC_INVALID_STATE,
         EC_INIT_LOGLEVEL,
         EC_INIT_PRINTFLAG,
         EC_INIT_CREATE_LOG_THREAD_FAIL
     };

/******************************************
                     日志信息结构
     *******************************************/
     struct TLogMsg
     {
         DWORD       m_dwSize;       // 结构大小 - 跟据消息长度动态变化
         LogLevel    m_logLevel;     // 日志级别
         UINT        m_uiThreadID;   // 线程ID
         SYSTEMTIME  m_stMsgTime;    // 记录时间
         TCHAR       m_psMsg[1];     // 消息内容
     };

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

public:
     // 日志组件初始化方法
     virtual BOOL Init(
                         LPCTSTR logFile  = NULL                  // 日志文件. 默认: {AppPath}/logs/{AppName}-YYYYMMDD.log
                       , LogLevel ll      = DEFAULT_LOG_LEVEL     // 日志级别. 默认: [Debug -> LL_DEBUG] / [Release -> LL_INFO]
                       , int printFlag    = DEFAULT_PRINT_FLAG    // 输出掩码. 是否输出到文件和(或)屏幕. 默认: 只输出到文件
                      )       = 0;
     // 日志组件清理方法
     virtual BOOL UnInit()    = 0;

public:
     // 写日志方法:传入日志内容字符串(对于不需要格式化的日志文本,用本方法效率最高)
     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);

// 写日志方法:传入格式化字符串和参数栈指针(通常只在组件内部使用)
     virtual void LogV   (LogLevel ll, LPCTSTR format, va_list arg_ptr);

// 写日志方法:传入格式化字符串和可变参数(非常灵活简便)
     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, ...);

// 写日志方法:传入格式化字符串和可变参数(与上一组方法类似,但在进行任何操作前会检查日志级别)
     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, ...);

// 通用辅助方法
     virtual BOOL HasInited           ()        const    = 0;        // 是否已经初始化                           
     virtual BOOL IsPrint2File        ()        const    = 0;        // 是否把日志输出到文件   
     virtual BOOL IsPrint2Screen      ()        const    = 0;        // 是否把日志输出到屏幕窗口   
     virtual int    GetPrintFlag      ()        const    = 0;        // 打印标志                   
     virtual LogLevel    GetLogLevel  ()        const    = 0;        // 日志级别       
     virtual LPCTSTR        GetLogFile()        const    = 0;        // 日志文件
     virtual ErrorCode    GetLastError()        const    = 0;        // 当前操作错误码

/****************************** GUI ******************************/
 #ifdef _WINDOWS
     public:
         // 设置接收日志信息的窗口, hWndGUI == NULL 则取消接收
         virtual void SetGUIWindow(HWND hWndGUI)    = 0;
         // 获取接收日志信息的窗口
         virtual HWND GetGUIWindow()                = 0;

// 销毁在发送 LOG_MESSAGE 消息时动态创建的 TLogMsg 对象
         virtual void FreeLogMsg(const TLogMsg* pLogMsg);

// 虚拟窗口句柄标掩码:用于向 GUI 窗口发送 LOG_MESSAGE 消息时作为发送源标识
         static const int LOGGER_FAKE_WINDOW_BASE = 0X80001111;
         // 自定义日志消息:通过本消息向 GUI 窗口发送日志
         // 其中:WPARAM -> ILogger 对象指针,LPARAM -> TLogMsg 结构体指针
         static const int LOG_MESSAGE = WM_USER | (0x7FFF & LOGGER_FAKE_WINDOW_BASE);
 #endif

public:
     static const int PRINT_FLAG_FILE            = 0x00000001;            // 打印到文件
     static const int PRINT_FLAG_SCREEN          = 0x00000002;            // 打印到屏幕
     static const int DEFAULT_PRINT_FLAG         = PRINT_FLAG_FILE;        // 默认日志掩码
     static const LogLevel DEFAULT_LOG_LEVEL     =
 #ifdef _DEBUG
                 LL_DEBUG
 #else
                 LL_INFO
 #endif

};

/**************************************************/
 /************** Logger DLL 导出函数 ***************/

// 创建 ILogger 对象
 EXTERN_C LOGGER_API ILogger* ILogger_Create();
 // 销毁 ILogger 对象
 EXTERN_C LOGGER_API void ILogger_Destroy(ILogger* p);

// 获取各日志级别的文字描述
 EXTERN_C LOGGER_API LPCTSTR    ILogger_GetLogLevelDesc (ILogger::LogLevel ll);
 // 获取各操作错误码的文字描述
 EXTERN_C LOGGER_API LPCTSTR    ILogger_GetErrorDesc    (ILogger::ErrorCode ec);

  代码中的注释基本已经能够说明日志组件的使用方法,这里只做一些简单的概括:

  版本:日志组件以 DLL 的形式提供,已编译成 Debug/Release、MBCS/Unicode、GUI/Console 8个版本

  测试:三个测试程序 TestGUILogger、TestDynamicLogger 和 TestConsoleLogger 用于测试所有版本。其中 TestDynamicLogger 采用动态加载方式加载 Logger DLL

  使用方法:

    0. 应用程序包含 Logger.h 头文件
    1. 调用 ILogger_Create() 导出函数创建 ILogger 对象
    2. 调用 ILogger->Init(...) 初始化日志组件
    3. 使用 ILogger->Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal() 等方法写日志
    4. 调用 ILogger->UnInit(...) 清理日志组件
    5. 调用 ILogger_Destroy() 导出函数销毁 ILogger 对象

  2、CStaticLogger:ILogger 包装器(智能指针)—— 用于静态加载 Logger DLL


代码如下:

#pragma once

#include "Logger.h"

/**************************************************/
 /********* http://www.cnblogs.com/ldcsaa/ *********/
 /********** ILogger 包装器(智能指针) ***********/
 /*********** 用于静态加载 Logger DLL ************/

class LOGGER_API CStaticLogger
 {
 public:
     // 构造函数:如果 bCreate 为 TRUE,则在构建 CStaticLogger 实例的同时创建 ILogger 对象
     CStaticLogger(BOOL bCreate = TRUE);
     // 析构函数
     ~CStaticLogger();
 private:
     CStaticLogger(const CStaticLogger&);
     CStaticLogger& operator = (const CStaticLogger&);

public:
     inline void Reset           (ILogger* pLogger);     // 重设其封装的 ILogger 指针
     inline BOOL IsValid         ()    const;            // 判断其封装的 ILogger 指针是否非空
     inline ILogger* Get         ()    const;            // 获取 ILogger 指针
     inline ILogger& operator *  ()    const;            // 获取 ILogger 引用
     inline ILogger* operator -> ()    const;            // 获取 ILogger 指针
     inline operator ILogger*    ()    const;            // 转换为 ILogger 指针

private:
     ILogger* m_pLogger;
 };

  CStaticLogger 为简化日志组件使用而设计,用于静态加载 Logger DLL 的场合。使用方法:

    0. 应用程序包含 StaticLogger.h 头文件
    1. 创建 CStaticLogger 对象(通常为全局对象)
    2. 调用 CStaticLogger->Init(...) 初始化日志组件
    3. 使用 CStaticLogger->Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal() 等方法写日志
    4. 调用 CStaticLogger->UnInit(...) 清理日志组件(CStaticLogger 对象析构时也会自动清理日志组件)

  3、CDynamicLogger:ILogger 包装器(智能指针)—— 用于动态加载 Logger DLL


代码如下:

#pragma once

#include "Logger.h"

/**************************************************/
 /********* http://www.cnblogs.com/ldcsaa/ *********/
 /************** Logger DLL 默认文件名 ***************/

#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

/**************************************************/
 /*************** Logger DLL 导出函数 ***************/

// 创建 ILogger 对象
 typedef ILogger*            (*FN_ILogger_Create)            ();
 // 销毁 ILogger 对象
 typedef void                (*FN_ILogger_Destroy)           (ILogger* p);
 // 获取各日志级别的文字描述
 typedef LPCTSTR             (*FN_ILogger_GetLogLevelDesc)   (ILogger::LogLevel ll);
 // 获取各操作错误码的文字描述
 typedef LPCTSTR             (*FN_ILogger_GetErrorDesc)      (ILogger::ErrorCode ec);

/*************************************************/
 /********** ILogger 包装器(智能指针) ***********/
 /************ 用于动态加载 Logger DLL ************/

class CDynamicLogger
 {
 public:
     // 构造函数:如果 bLoad 为 TRUE,则在构建 CDynamicLogger 示例的同时创建 ILogger 对象
     CDynamicLogger(BOOL bLoad = TRUE, LPCTSTR lpszFilePath = DEF_LOGGER_DLL_FILE_PATH)
     {
         Reset();

if(bLoad)
             Load(lpszFilePath);
     }

// 析构函数
     ~CDynamicLogger()
     {
         Free();
     }

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

public:
     // 创建 ILogger 对象
     ILogger* ILogger_Create()
         {return m_fnILoggerCreate();}
     // 销毁 ILogger 对象
     void ILogger_Destroy(ILogger* p)
         {m_fnILoggerDestroy(p);}
     // 获取各日志级别的文字描述
     LPCTSTR    ILogger_GetLogLevelDesc(ILogger::LogLevel ll)
         {return m_fnILoggerGetLogLevelDesc(ll);}
     // 获取各操作错误码的文字描述
     LPCTSTR    ILogger_GetErrorDesc(ILogger::ErrorCode ec)
         {return m_fnILoggerGetErrorDesc(ec);}

// 加载 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;
     }

// 卸载 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;}  // 判断其封装的 ILogger 指针是否非空
     ILogger* Get            ()    const    {return m_pLogger;}          // 获取 ILogger 指针
     ILogger& operator *     ()    const    {return *m_pLogger;}         // 获取 ILogger 引用
     ILogger* operator ->    ()    const    {return m_pLogger;}          // 获取 ILogger 指针
     operator ILogger*       ()    const    {return m_pLogger;}          // 转换为 ILogger 指针

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 为简化日志组件使用而设计,用于动态加载 Logger DLL 的场合。使用方法:

    0. 应用程序包含 DynamicLogger.h 头文件
    1. 创建 CDynamicLogger 对象(通常为全局对象)
    2. 调用 CDynamicLogger->Init(...) 初始化日志组件
    3. 使用 CDynamicLogger->Log()/Debug()/Trace()/Info()/Warn()/Error()/Fatal() 等方法写日志
    4. 调用 CDynamicLogger->UnInit(...) 清理日志组件(CDynamicLogger 对象析构时也会自动清理日志组件)

(0)

相关推荐

  • 在C++程序中开启和禁用Windows设备的无线网卡的方法

    1.列出当前网卡:SetupDiEnumDeviceInfo 2.找出当前无线网卡的名字(用natvie wifi api) 3.卸载\安装此驱动 问题: log为:SetupDiSetClassInstallParams failed. -536870347   完整代码如下: // ControlWirelessCard.cpp : Defines the entry point for the console application. // #include "stdafx.h"

  • 深入解析C++的WNDCLASS结构体及其在Windows中的应用

    WNDCLASS是一个由系统支持的结构,用来储存某一类窗口的信息,如ClassStyle,消息处理函数,Icon,Cursor,背景Brush等.也就是说,CreateWindow只是将某个WNDCLASS定义的窗体变成实例.要得到某一窗口的WNDCLASS数据,可以用GetClassLong(); RegisterClass()就是在系统注册某一类型的窗体.也就是将你提供的WNDCLASS数据注册为一个窗口类,在WNDCLASS.lpszClassName中定义该WNDCLASS的标识,无论C

  • C++ 学习之旅 Windows程序内部运行原理

    学习C++与.net不同的是,一定要搞清楚Windows程序内部运行原理,因为他所涉及大多数是操作系统的调用,而.net毕竟是在.netFrameWork上唱戏. 那Windows应用程序,操作系统,计算机硬件之间的相互关系究竟什么了,下面的图就给予很好的解释. 向下箭头①是 应用程序运行判断处理的结果,输出到输出的设备. 向上箭头②是输入设备,输入到操作系统中. 向下箭头③代表API,我们要解释以下API是什么.API是应用程序接口, 表示应用程序可以通知操作系统执行某个具体的动作,如操作系统

  • Visual C++程序设计中Windows GDI贴图闪烁的解决方法

    本文实例讲述了Visual C++程序设计中Windows GDI贴图闪烁的解决方法.分享给大家供大家参考.具体如下: 一般的windows 复杂的界面需要使用多层窗口而且要用贴图来美化,所以不可避免在窗口移动或者改变大小的时候出现闪烁. 先来谈谈闪烁产生的原因 原因一: 如果熟悉显卡原理的话,调用GDI函数向屏幕输出的时候并不是立刻就显示在屏幕 上只是写到了显存里,而显卡每隔一段时间把显存的内容输出到屏幕上,这就是刷新周期. 一般显卡的刷新周期是 1/80秒左右,具体数字可以自己设置的. 这样

  • C++程序中使用Windows系统Native Wifi API的基本教程

    Windows应用想要实现连接wifi,监听wifi信号,断开连接等功能,用NativeWifi API是个不错的选择. 打开MSDN,搜索NativeWifi Api,找到Native Wifi页.在这里. 信息量很大,如果像我着急实现上述功能,看海量的文档有些来不及.如果直接给我例子,在运行中调试,阅读代码,效率会更高. 但是,我并没有成功.首先,Sample在SDK中,参见这里.我下载几次都失败了,最后放弃这条路.后来同事给了我一份Sample,我不敢确定是否就是这个,但是代码写的也是很晦

  • c++利用windows函数实现计时示例

    复制代码 代码如下: //Windows系统下可以用 time(),clock(),timeGetTime(),GetTickCount(),QueryPerformanceCounter()来对一段程序代码进行计时 #include <stdio.h>#include <windows.h>#include <time.h>                   //time_t time()  clock_t clock()    #include <Mmsys

  • 基于Windows C++ 应用程序通用日志组件的使用详解

    引言 在如何记录程序日志方面,通常有三种选择: 1.采用Log4CXX等公共开源日志组件:这类日志组件的特点是跨平台且功能比较强大,例如可以把日志发往另一台服务器或记录到数据库中等:另外,可配置性较高,可以通过配置文件或程序代码对日志进行很多个性化设置.但从另外一个角度看,由于这些优点往往也导致了在使用方面的缺点.首先,对于一般应用程序来说,它们并不需要太多的功能,通常只需要把日志记录到文件或反馈到应用程序,功能太多反正让用户使用起来觉得繁琐还得背负很多从来都用不到的代码.其次,这类日志组件通常

  • Java日志组件间关系详解

    一. 总览 本文章不对日志组件进行优劣评价,只是对关系进行对比.在日志中组件中存在这样的几种关系, 这几种关系理解清楚, 有助于我们对日志的引入和使用. 二. 日志门面 日志门面就是指直接引入我们程序中进行记录日志的日志组件,作为日志门面的这些组件会在程序中直接依赖, 上图中就列举的几种常见的日志门面的组件.像一些软件直接回默认使用一些组件, 比如Spring使用的就是commons-logging, activiti使用的日志门面就是slf4j, 其他的软件也都会选用自己认为好用的日志门面.

  • 微信小程序自定义toast组件的方法详解【含动画】

    本文实例讲述了微信小程序自定义toast组件的方法.分享给大家供大家参考,具体如下: 怎么创建就不说了,前面一篇有 微信小程序自定义prompt组件 直接上代码 wxml <!-- components/toast/toast.wxml --> <view class="toast-box {{isShow? 'show':''}}" animation="{{animationData}}"> <view class="to

  • 微信小程序Echarts图表组件使用方法详解

    1:下载 GitHub 上的 项目 2:但项目下载之后,打开小程序开发工具,可以看到效果如下,适配性还是比较完美的. 如果是在项目里面引入组件的话,打开从github上面下载的代码,将ec-canvas文件夹复制黏贴到你的项目里面. 好的,组件已经复制到了我的项目里面,现在我想实现一个折线图,现在开始去组件里面搬运复制黏贴代码了. wxml <!--index.wxml--> <view class="container"> <ec-canvas id=&

  • 微信小程序自定义波浪组件使用方法详解

    最近看到好多app上有波浪背景,有动态的,有静态的,这里是在小程序中用得动态. 先看看效果图:里面的文本是组件内部定义的. 这是用两个svg的图片用css关键帧动画做的效果(这里谢谢子弹短信里前端群的小伙伴提供的web版的css动画文件) 在小程序中使用,注意一个问题:就是svg不可以直接使用,需要转为base64(这个大家应该有收藏吧),这里我已经转换好了,在下面的wxss中. 这里顺便用一下自定义组件: 首先定义组件 wave wave.wxml:这里我默认是用得显示个人信息.其中isSho

  • 微信小程序组件 marquee实例详解

    微信小程序组件 marquee实例详解 1. marquee标签 html是有marquee标签的,可以实现跑马灯效果,但小程序没有,所以要实现.这里考虑使用css3的animation实现. html的marquee是这样使用的. <marquee direction="left" behavior="scroll" scrollamount="1" scrolldelay="0" loop="-1"

  • 基于vue2.0动态组件及render详解

    如下所示: <template> <div class="hello"> <h1>{{ msg }}</h1> <h2>这里是Boor</h2> <component v-bind:my-data="items" v-bind:is="currentView"> <!-- 组件在 vm.currentview 变化时改变! --> </compo

  • spring boot Slf4j日志框架的体系结构详解

    目录 前言 一.五花八门的日志工具包 1.1. 日志框架 1.2.日志门面 1.3日志门面存在的意义 二.日志框架选型 三.日志级别 四.常见术语概念解析 总结 前言 刚刚接触到java log日志的同学可能会被各种日志框架吓到,包括各种日志框架之间的jar总是发生冲突,另很多小伙伴头疼不已.那我们本篇的内容就是将各种java 日志框架发展过程,以及他们之间的关系,以及如何选型来介绍给大家. 一.五花八门的日志工具包 1.1. 日志框架 JDK java.util.logging 包:java.

  • React 模式之纯组件使用示例详解

    目录 什么是纯组件 纯组件解决了什么问题 怎么使用纯组件 CC: shouldComponentUpdate() 和 React.PureComponent FC: React.memo() 你可能并不需要纯组件 什么是纯组件 纯组件(Pure Component)这概念衍生自纯函数.纯函数指的是返回结果只依赖于传入的参数,且对函数作用域外没有副作用的函数.这种函数在相同参数下,返回结果是不变的.纯函数的返回值能被安全地缓存起来,在下次调用时,跳过函数执行,直接读取缓存.因为函数没有外部副作用,

  • Windows系统中搭建Go语言开发环境图文详解

    目录 1.Go语言简介 2.安装Git 3.Go 工具链(编译器)安装 3.1.环境变量GOROOT 3.2.环境变量GOPATH 3.3.Go常用命令 4.包管理 4.1.go module 4.2.gopm 5.编写Go语言代码的IDE或编辑工具 5.1.基于VSCode的Go开发环境 5.1.1.安装VSCode 5.1.2.安装插件 5.1.3.常用配置 5.2.GoLand 5.3.Vim 5.4.其他Go代码编写工具 6.Go语言学习资料分享 本文详细讲述如何在 Windows 系统

随机推荐