easylogging++的那些事(一)功能介绍

一、概述

1、easylogging++是什么?

    引用官方文档的话说,easylogging++是一个面向 C++ 应用程序的单头高效日志库(目前已经拆分为两个文件 easylogging++.heasylogging++.cc)。它是非常强大的,高度可扩展和可配置的用户的要求。它提供了编写自己的接收器的能力(通过特性称为 LogDispatchCallback)。目前,在 github 和其他开源源码控制管理站点上,有数百个开源项目正在使用这个库。
    本文档基于 easylogging++ v9.96.7

2、快速上手

    接入 easylogging++有两种方式,两种方式都需要在你的项目中直接包含源码(easylogging++.heasylogging++.cc):

1) 主程序中直接初始化

    通过 INITIALIZE_EASYLOGGINGPP 宏初始化即可开始

	#include "easylogging++.h"
	INITIALIZE_EASYLOGGINGPP
	
	int main(int argc, char* argv[]) {
	   LOG(INFO) << "My first info log using default logger";
	   return 0;
	}

    或者也可以不直接在程序入口处添加 INITIALIZE_EASYLOGGINGPP 宏,改为隐式初始化,借由定义 AUTO_INITIALIZE_EASYLOGGINGPP 宏实现。

	#include "easylogging++.h"
	 
	int main(int argc, char* argv[]) {
	    LOG(INFO) << "My first info log using default logger";
	    return 0;
	}

2) 将 easylogging++构建成一个动态库

    当需要在多个库和主程序之间共用同一个日志配置时,可以将 easylogging++构建成一个动态库,在动态库内部进行日志库的全局初始化工作(INITIALIZE_EASYLOGGINGPP),然后在 main 入口处统一进行日志文件的配置,这样就可以像第一种方式那样进行正常的日志输出了。

    这里说说 easylogging++构建成动态库的注意事项:
    windows:构建成动态库时需要定义宏 ELPP_AS_DLLELPP_EXPORT_SYMBOLS,使用动态库时定义宏 ELPP_AS_DLL
    linux:按照正常的构建动态库的方式有点问题,程序退出时,在析构的时候释放内存会出错,暂时还不清楚是什么原因,初步怀疑是导出全局变量不能直接按照 windows 那样在函数外直接使用 INITIALIZE_EASYLOGGINGPP 来进行初始化:

    main.cpp 如下(什么也没干,只是看看程序正常启动与退出):

	int main(int argc, char* argv[])
	{
		return 0;
	}

    通过 gdb 调试发现在全局对象析构的时候会出错:

	GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-47.el6
	Copyright (C) 2013 Free Software Foundation, Inc.
	License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
	This is free software: you are free to change and redistribute it.
	There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
	and "show warranty" for details.
	This GDB was configured as "x86_64-redhat-linux-gnu".
	For bug reporting instructions, please see:
	<http://www.gnu.org/software/gdb/bugs/>...
	Reading symbols from /root/Easylogging++/Easylogging++/main...done.
	(gdb) run
	Starting program: /root/Easylogging++/Easylogging++/main 
	warning: Skipping deprecated .gdb_index section in /usr/lib/debug/lib64/ld-2.12.so.debug.
	Do "set use-deprecated-index-sections on" before the file is read
	to use the section anyway.
	 
	Program received signal SIGSEGV, Segmentation fault.
	0x00007ffff7b8bfb9 in el::base::utils::safeDelete<el::base::RegisteredHitCounters> (pointer=@0x6010b0: > 	0x6011f0) at ../src/easylogging++.h:86
 	865	  delete pointer;
 	(gdb) q
 	A debugging session is active.
 	 
 		Inferior 1 [process 21239] will be killed.
 	

    上面出错的地方对应的源码为:

	Storage::~Storage(void) {
	ELPP_INTERNAL_INFO(4, "Destroying storage");
	#if ELPP_ASYNC_LOGGING
	ELPP_INTERNAL_INFO(5, "Replacing log dispatch callback to synchronous");
	uninstallLogDispatchCallback<base::AsyncLogDispatchCallback>(std::string("AsyncLogDispatchCallback"));
	installLogDispatchCallback<base::DefaultLogDispatchCallback>(std::string("DefaultLogDispatchCallback"));
	ELPP_INTERNAL_INFO(5, "Destroying asyncDispatchWorker");
	base::utils::safeDelete(m_asyncDispatchWorker);
	ELPP_INTERNAL_INFO(5, "Destroying asyncLogQueue");
	base::utils::safeDelete(m_asyncLogQueue);
	#endif  // ELPP_ASYNC_LOGGING
	ELPP_INTERNAL_INFO(5, "Destroying registeredHitCounters");
	base::utils::safeDelete(m_registeredHitCounters);
	ELPP_INTERNAL_INFO(5, "Destroying registeredLoggers");
	base::utils::safeDelete(m_registeredLoggers);
	ELPP_INTERNAL_INFO(5, "Destroying vRegistry");
	base::utils::safeDelete(m_vRegistry);
	}

    其中的:

	base::utils::safeDelete(m_registeredHitCounters);

    如果有对这块比较了解的,可以留言区回复一下,谢谢!应该是 linux 下库导出 C++全局对象的问题!windows` 下一切正常!

    第一种方式会在多个库内部生成自己内部的日志库的全局变量(通过 INITIALIZE_EASYLOGGINGPP 来定义)
    第二种方式是共享了同一个日志库的全局变量
    两种方式其他配置相同(如设置线程安全等等)

    注意 :ELPP_AS_DLLELPP_EXPORT_SYMBOLS 只用于 windows

    上面说的日志库的全局变量主要用来管理日志输出内部需要的一些信息(如日志的格式等等),后面在源码剖析 INITIALIZE_EASYLOGGINGPP 的时候会详细讲解。

二、功能简介

1、基本日志输出

    日志分两种:用户日志syslog 日志,而用户日志又可分为 普通日志verbose 日志。
    写日志可以使用两种方式:

1) 使用 easylogging++提供的写日志相关的一些宏

    用户日志syslog 日志都提供了以下几种类型写日志的宏:
    easylogging++针对部分宏都提供了对应的 DEBUG 宏: 基本宏加 前缀 D (通过 ELPP_DISABLE_DEBUG_LOGS 宏控制)

1.1) 基本用法:

    普通日志是 LOGCLOG ,PLOGCPLOG(PLOGCPLOGLOGCLOG 多了 errno 对应的错误信息) 对应 DEBUG 宏: DLOG DCLOG DPLOGDCPLOG
    verbose 日志是 VLOGCVLOG 对应 DEBUG 宏: DVLOGDCVLOG
    syslog 日志是 SYSLOGCSYSLOG 对应 DEBUG 宏: DSYSLOGDCSYSLOG

1.2) 有条件日志记录宏

    就是在上面的基本用法的基础上加上 后缀_IF 如:
    普通日志:LOG_IFCLOG_IFPLOG_IFCPLOG_IF(PLOG_IFCPLOG_IFLOG_IFCLOG_IF 多了 errno 对应的错息),对应 DEBUG 宏: DLOG_IFDCLOG_IFDPLOG_IFDCPLOG_IF
    verbose 日志:VLOG_IFCVLOG_IF,对应 DEBUG 宏: DVLOG_IFDCVLOG_IF
    syslog 日志:SYSLOG_IFCSYSLOG_IF,对应 DEBUG 宏: DSYSLOG_IFDCSYSLOG_IF

1.3) 偶尔写日志:

    分别是在基本用法的基础上加上相应后缀:
    xxx_EVERY_N 每统计 N 次后记录日志 如:
    LOG_EVERY_N
    CLOG_EVERY_N
    VLOG_EVERY_N
    CVLOG_EVERY_N
    SYSLOG_EVERY_N
    CSYSLOG_EVERY_N
    xxx_AFTER_N 在统计 N 次后记录日志 如:
    LOG_AFTER_N
    CLOG_AFTER_N
    VLOG_AFTER_N
    CVLOG_AFTER_N
    SYSLOG_AFTER_N
    CSYSLOG_AFTER_N
    xxx_N_TIMES 仅仅记录日志 N 次 如:
    LOG_N_TIMES
    CLOG_N_TIMES
    VLOG_N_TIMES
    CVLOG_N_TIMES
    SYSLOG_N_TIMES
    CSYSLOG_N_TIMES

	//例子如下:
	LOG(INFO) << "LOG(INFO)";
	DLOG(INFO) << "DLOG(INFO)";
	
	CLOG(INFO, "default") << "CLOG(INFO, \"default\")";
	DCLOG(INFO, "default") << "DCLOG(INFO, \"default\")";
	
	PLOG(INFO) << "PLOG(INFO)";
	DPLOG(INFO) << "DPLOG(INFO)";
	
	CPLOG(INFO, "default") << "CPLOG(INFO, \"default\")";
	DCPLOG(INFO, "default") << "DCPLOG(INFO, \"default\")";
	
	VLOG(2)  << "VLOG(2)";
	DVLOG(2) << "DVLOG(2)";
	
	CVLOG(2, "default") << "CVLOG(2, \"default\")";
	DCVLOG(2, "default") << "DCVLOG(2, \"default\")";
	
	SYSLOG(INFO) << "SYSLOG(INFO)";
	DSYSLOG(INFO) << "DSYSLOG(INFO)";
	
	CSYSLOG(INFO, "default") << "CSYSLOG(INFO, \"default\")";
	DCSYSLOG(INFO, "default") << "DCSYSLOG(INFO, \"default\")";
	
	LOG_IF(true, INFO) << "LOG_IF(true, INFO)";
	DLOG_IF(true, INFO) << "DLOG_IF(true, INFO)";
	
	CLOG_IF(true, INFO, "default") << "CLOG_IF(true, INFO, \"default\")";
	DCLOG_IF(true, INFO, "default") << "DCLOG_IF(true, INFO, \"default\")";
	
	PLOG_IF(true, INFO) << "PLOG_IF(true, INFO)";
	DPLOG_IF(true, INFO) << "DPLOG_IF(true, INFO)";
	
	CPLOG_IF(true, INFO, "default") << "CPLOG_IF(true, INFO, \"default\")";
	DCPLOG_IF(true, INFO, "default") << "DCPLOG_IF(true, INFO, \"default\")";
	
	SYSLOG_IF(true, INFO) << "SYSLOG_IF(true, INFO)";
	DSYSLOG_IF(true, INFO) << "SYSLOG_IF(true, INFO)";
	
	CSYSLOG_IF(true, INFO, "default") << "CSYSLOG_IF(true, INFO, \"default\")";
	DCSYSLOG_IF(true, INFO, "default") << "CSYSLOG_IF(true, INFO, \"default\")";
	
	for (size_t i = 0; i < 10; i++)
	{
		//xxx_EVERY_N
		LOG_EVERY_N(3, INFO) << "LOG_EVERY_N(3, INFO)";
		CLOG_EVERY_N(3, INFO, "default") << "CLOG_EVERY_N(3, INFO, \"default\")";
		VLOG_EVERY_N(3, 2) << "VLOG_EVERY_N(3, 2)";
		CVLOG_EVERY_N(3, 2, "default") << "CVLOG_EVERY_N(3, 2, \"default\")";
		SYSLOG_EVERY_N(3, INFO) << "SYSLOG_EVERY_N(3, INFO)";
		CSYSLOG_EVERY_N(3, INFO, "default") << "CSYSLOG_EVERY_N(3, INFO, \"default\")";
	
		//xxx_AFTER_N
		LOG_AFTER_N(3, INFO) << "LOG_AFTER_N(3, INFO)";
		CLOG_AFTER_N(3, INFO, "default") << "CLOG_AFTER_N(3, INFO, \"default\")";
		VLOG_AFTER_N(3, 2) << "VLOG_AFTER_N(3, 2)";
		CVLOG_AFTER_N(3, 2, "default") << "CVLOG_AFTER_N(3, 2, \"default\")";
		SYSLOG_AFTER_N(3, INFO) << "SYSLOG_AFTER_N(3, INFO)";
		CSYSLOG_AFTER_N(3, INFO, "default") << "CSYSLOG_AFTER_N(3, INFO, \"default\")";
	
		//xxx_N_TIMES
		LOG_N_TIMES(3, INFO) << "LOG_N_TIMES(3, INFO)";
		CLOG_N_TIMES(3, INFO, "default") << "CLOG_N_TIMES(3, INFO, \"default\")";
		VLOG_N_TIMES(3, 2) << "VLOG_N_TIMES(3, 2)";
		CVLOG_N_TIMES(3, 2, "default") << "CVLOG_N_TIMES(3, 2, \"default\")";
		SYSLOG_N_TIMES(3, INFO) << "SYSLOG_N_TIMES(3, INFO)";
		CSYSLOG_N_TIMES(3, INFO, "default") << "CSYSLOG_N_TIMES(3, INFO, \"default\")";
	}

2、使用 easylogging++的 el:: Logger 类提供的类似 printf 的接口

	//相关接口声明如下: 
	void Logger::info(const char*, const T&, const Args&...);
	void Logger::warn(const char*, const T&, const Args&...);
	void Logger::error(const char*, const T&, const Args&...);
	void Logger::debug(const char*, const T&, const Args&...);
	void Logger::fatal(const char*, const T&, const Args&...);
	void Logger::trace(const char*, const T&, const Args&...);
	void Logger::verbose(int vlevel, const char*, const T&, const Args&...); 
	//例子: 
	el::Logger* defaultLogger = el::Loggers::getLogger("default");    
	defaultLogger->info("My first ultimate log message %% %%v %v %v", 123, 222);  

    注意:上面的这些接口不支持%file, %func,%line 和%loc 这些日志格式说明符

3、检查宏:当条件不满足时输出日志

    分两种: 普通检查宏和对应 DEBUG 宏(普通检查宏加前缀 D)

1) 通用检查宏

    CCHECK 对应 DEBUG 宏:DCCHECK
    CPCHECK(比 CCHECK 多了 errno 对应的错误信息) 对应 DEBUG 宏:DCPCHECK
    CHECK 对应 DEBUG 宏:DCHECK
    PCHECK(比 CHECK 多了 errno 对应的错误信息) 对应 DEBUG 宏:DPCHECK

2) 检查对象是否相等

    注意:需要对象支持 == 运算符
    CCHECK_EQ 对应 DEBUG 宏:DCCHECK_EQ
    CHECK_EQ 对应 DEBUG 宏:DCHECK_EQ

3) 检查对象是否不相等

    注意:需要对象支持!= 运算符
    CCHECK_NE 对应 DEBUG 宏: DCCHECK_NE
    CHECK_NE 对应 DEBUG 宏: DCHECK_NE

4) 检查一个对象是否小于另一个对象

    注意:需要对象支持 < 运算符
    CCHECK_LT 对应 DEBUG 宏:DCCHECK_LT
    CHECK_LT 对应 DEBUG 宏:DCHECK_LT

5) 检查一个对象是否大于另一个对象

    注意:需要对象支持 > 运算符
    CCHECK_GT 对应 DEBUG 宏:DCCHECK_GT
    CHECK_GT 对应 DEBUG 宏:DCHECK_GT

6) 检查一个对象是否小于或等于另一个对象

    注意:需要对象支持 <= 运算符
    CCHECK_LE 对应 DEBUG 宏:DCCHECK_LE
    CHECK_LE 对应 DEBUG 宏:DCHECK_LE

7) 检查一个对象是否大于或等于另一个对象

    注意:需要对象支持 >= 运算符
    CCHECK_GE 对应 DEBUG 宏:DCCHECK_GE
    CHECK_GE 对应 DEBUG 宏:DCHECK_GE

8) 检查对象是否在某个区间内

    注意:需要对象支持 <=和> = 运算符
    CCHECK_BOUNDS 对应 DEBUG 宏:DCCHECK_BOUNDS
    CHECK_BOUNDS 对应 DEBUG 宏:DCHECK_BOUNDS

9) 检查指针是否非空

    CCHECK_NOTNULL 对应 DEBUG 宏:DCCHECK_NOTNULL
    CHECK_NOTNULL 对应 DEBUG 宏:DCHECK_NOTNULL

10) 检查两个字符串是否相等(区分大小写)

    CCHECK_STREQ 对应 DEBUG 宏:DCCHECK_STREQ
    CHECK_STREQ 对应 DEBUG 宏:DCHECK_STREQ

11) 检查两个字符串是否不相等(区分大小写)

    CCHECK_STRNE 对应 DEBUG 宏:DCCHECK_STRNE
    CHECK_STRNE 对应 DEBUG 宏:DCHECK_STRNE

12) 检查两个字符串是否相等(不区分大小写)

    CCHECK_STRCASEEQ 对应 DEBUG 宏:DCCHECK_STRCASEEQ
    CHECK_STRCASEEQ 对应 DEBUG 宏: DCHECK_STRCASEEQ

13) 检查两个字符串是否不相等(不区分大小写)

    CCHECK_STRCASENE 对应 DEBUG 宏:DCCHECK_STRCASENE
    CHECK_STRCASENE 对应 DEBUG 宏:DCHECK_STRCASENE

	// 例子
	#include "easylogging++.h"
	INITIALIZE_EASYLOGGINGPP
	 
	int main(int argc, char* argv[])
	{
    	//默认FATAL日志会停止程序运行,这里设置可以让所有检查宏都输出日志,否则只输出第一条程序就退出了
		el::Loggers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog);
 
		//通用检查宏
		CCHECK(1 > 2, "default");
		DCCHECK(1 > 2, "default");
 
		CPCHECK(1 > 2, "default");
		DCPCHECK(1 > 2, "default");
 
		CHECK(1 > 2);
		DCHECK(1 > 2);
 
		PCHECK(1 > 2);
		DPCHECK(1 > 2);
 
		//检查对象是否相等(需要对象支持==运算符)
		CCHECK_EQ(1, 2, "default");
		DCCHECK_EQ(1, 2, "default");
		CHECK_EQ(1, 2);
		DCHECK_EQ(1, 2);
 
		//检查对象是否不相等(需要对象支持!=运算符)
		CCHECK_NE(1, 1, "default");
		DCCHECK_NE(1, 1, "default");
 
		CHECK_NE(1, 1);
		DCHECK_NE(1, 1);
 
		//检查一个对象是否小于另一个对象(需要对象支持<运算符)
		CCHECK_LT(2, 1, "default");
		DCCHECK_LT(2, 1, "default");
 
		CHECK_LT(2, 1);
		DCHECK_LT(2, 1);
 
		//检查一个对象是否大于另一个对象(需要对象支持>运算符)
		CCHECK_GT(1, 2, "default");
		DCCHECK_GT(1, 2, "default");
 
		CHECK_GT(1, 2);
		DCHECK_GT(1, 2);
 
		//检查一个对象是否小于或等于另一个对象(需要对象支持<=运算符)
		CCHECK_LE(2, 1, "default");
		DCCHECK_LE(2, 1, "default");
 
		CHECK_LE(2, 1);
		DCHECK_LE(2, 1);
 
		//检查一个对象是否大于或等于另一个对象(需要对象支持>=运算符)
		CCHECK_GE(1, 2, "default");
		DCCHECK_GE(1, 2, "default");
 
		CHECK_GE(1, 2);
		DCHECK_GE(1, 2);
 
		//检查值是否在某个区间内(需要对象支持<=和>=运算符)
		CCHECK_BOUNDS(1, 2, 3, "default");
		DCCHECK_BOUNDS(1, 2, 3, "default");
 
		CHECK_BOUNDS(1, 2, 3);
		DCHECK_BOUNDS(1, 2, 3);
 
		//检查指针是否非空
		CCHECK_NOTNULL(NULL, "default");
		DCCHECK_NOTNULL(nullptr, "default");
 
		CHECK_NOTNULL(NULL);
		DCHECK_NOTNULL(nullptr);
 
		//检查两个字符串是否相等(区分大小写)
		CCHECK_STREQ("test", "123", "default");
		DCCHECK_STREQ("test", "123", "default");
 
		CHECK_STREQ("test", "123");
		DCHECK_STREQ("test", "123");
 
		//检查两个字符串是否不相等(区分大小写)
		CCHECK_STRNE("test", "test", "default");
		DCCHECK_STRNE("test", "test", "default");
 
		CHECK_STRNE("test", "test");
		DCHECK_STRNE("test", "test");
 
		//检查两个字符串是否相等(不区分大小写)
		CCHECK_STRCASEEQ("test", "123", "default");
		DCCHECK_STRCASEEQ("test", "123", "default");
 
		CHECK_STRCASEEQ("test", "123");
		DCHECK_STRCASEEQ("test", "123");
 
		//检查两个字符串是否不相等(不区分大小写) 
		CCHECK_STRCASENE("test", "TEST", "default");
		DCCHECK_STRCASENE("test", "TEST", "default");
 
		CHECK_STRCASENE("test", "TEST");
		DCHECK_STRCASENE("test", "TEST");
	}

4、性能跟踪

    相关宏:

  • TIMED_SCOPE_IF
  • TIMED_SCOPE
  • TIMED_FUNC_IF
  • TIMED_FUNC
  • TIMED_BLOCK
  • PERFORMANCE_CHECKPOINT
  • PERFORMANCE_CHECKPOINT_WITH_ID
	//例子  
	#include "easylogging++.h"  
	INITIALIZE_EASYLOGGINGPP  
	
	void performHeavyTask(int iter) {
	
		TIMED_FUNC(timerObj);
		TIMED_FUNC_IF(timerFuncObj, iter>5);
		TIMED_SCOPE(timerObj1, "TIMED_SCOPE");
		TIMED_SCOPE_IF(timerObj2, "TIMED_SCOPE_IF", iter > 5);
	
		// Some initializations
		// Some more heavy tasks
		
		while (iter-- > 0) {
			TIMED_BLOCK(timerBlkObj1, "TIMED_BLOCK")
			{
				LOG(INFO) << "test TIMED_BLOCK!";
			}
			// Perform some heavy task in each iter
			if (iter % 3) {
				PERFORMANCE_CHECKPOINT(timerObj);
				PERFORMANCE_CHECKPOINT_WITH_ID(timerObj, "PERFORMANCE_CHECKPOINT");
			}
		}
	}
	
	int main(int argc, char* argv[])
	{
		performHeavyTask(6);
	}

5、日志旋转(源码当中的日志旋转主要是用于备份,和实际项目有些差距)

    通过 el:: Helpers:: installPreRollOutCallback 安装对应的日志旋转回调

	void installPreRollOutCallback(const PreRollOutCallback& callback);

6、崩溃处理

    通过 el:: Helpers:: setCrashHandler 来设置自定义的崩溃处理器

	void setCrashHandler(const el::base::debug::CrashHandler::Handler& crashHandler);

	//例子
	#include "easylogging++.h"
	INITIALIZE_EASYLOGGINGPP
 
	void boom()
	{
		char* ptr = nullptr;
		*ptr = '\0';
	 }
 
	void myCrashHandler(int sig) {
		LOG(ERROR) << "Woops! Crashed!";
	 	// FOLLOWING LINE IS ABSOLUTELY NEEDED AT THE END IN ORDER TO ABORT APPLICATION
		el::Helpers::crashAbort(sig);
	 }
 
	int main(int argc, char* argv[])
	{
		el::Helpers::setCrashHandler(myCrashHandler);
	 
		boom();
	}

7、堆栈跟踪

    通过调用 el:: base:: debug:: StackTrace(); 来打印堆栈
    注意: 仅仅支持 GCC

8、STL, QT, BOOST, wxWidgets 相关数据类型直接输出到日志的支持

9、兼容第三方类型直接日志输出支持

1) 可以修改源码的第三方类型

    通过继承 el:: Loggable,实现 log 接口来实现

	void log(el::base::type::ostream_t& os);

2)不能修改源码的第三方类型

    通过 MAKE_LOGGABLE 宏来扩展

	//例子
	#include "easylogging++.h"
	INITIALIZE_EASYLOGGINGPP
	
	//假设Rect不能修改源码
	class Rect
	{
	public:
		Rect(int height, int width) :m_height(height), m_width(width) {}
		int width() const { return m_width; }
		int height() const { return m_height; }
	private:
		int m_height;
		int m_width;
	};
	
	MAKE_LOGGABLE(Rect, rect, os)
	{
		os << "[width:" << rect.width() << ", height:" << rect.height() << "]";
		return os;
	}
	
	//假设Integer能够修改源码
	class Integer : public el::Loggable
	{
	public:
		Integer(int i) : m_underlyingInt(i) {}
		Integer& operator=(const Integer& integer) {
			m_underlyingInt = integer.m_underlyingInt;
			return *this;
		}
		virtual void log(el::base::type::ostream_t& os) const {
			os <<"int->" << m_underlyingInt;
		}
	
		int getInt(void) const { return m_underlyingInt; }
	private:
		int m_underlyingInt;
	};
	
	
	int main(int argc, char* argv[])
	{
		LOG(INFO) << Integer(5);
		LOG(INFO) << Rect(1, 2);
	}

easylogging++的基本情况介绍就到这里,下一篇文章将开始介绍 easylogging++的特性宏。

posted @ 2022-11-23 19:36  节奏自由  阅读(1073)  评论(0编辑  收藏  举报