easylogging++的那些事(一)功能介绍
一、概述
1、easylogging++是什么?
引用官方文档的话说,easylogging++是一个面向
C++
应用程序的单头高效日志库(目前已经拆分为两个文件easylogging++.h
和easylogging++.cc
)。它是非常强大的,高度可扩展和可配置的用户的要求。它提供了编写自己的接收器的能力(通过特性称为LogDispatchCallback
)。目前,在 github 和其他开源源码控制管理站点上,有数百个开源项目正在使用这个库。
本文档基于 easylogging++ v9.96.7
2、快速上手
接入 easylogging++有两种方式,两种方式都需要在你的项目中直接包含源码(
easylogging++.h
和easylogging++.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_DLL
和ELPP_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_DLL
和ELPP_EXPORT_SYMBOLS
只用于 windows 下上面说的日志库的全局变量主要用来管理日志输出内部需要的一些信息(如日志的格式等等),后面在源码剖析
INITIALIZE_EASYLOGGINGPP
的时候会详细讲解。
二、功能简介
1、基本日志输出
日志分两种:
用户日志
和syslog
日志,而用户日志又可分为普通日志
和verbose
日志。
写日志可以使用两种方式:1) 使用 easylogging++提供的写日志相关的一些宏
用户日志
和syslog
日志都提供了以下几种类型写日志的宏:
easylogging++针对部分宏都提供了对应的 DEBUG 宏: 基本宏加 前缀 D (通过ELPP_DISABLE_DEBUG_LOGS
宏控制)1.1) 基本用法:
普通日志是
LOG
和CLOG
,PLOG
和CPLOG
(PLOG
和CPLOG
比LOG
和CLOG
多了 errno 对应的错误信息) 对应 DEBUG 宏:DLOG
DCLOG
DPLOG
和DCPLOG
verbose
日志是VLOG
和CVLOG
对应 DEBUG 宏:DVLOG
和DCVLOG
syslog
日志是SYSLOG
和CSYSLOG
对应 DEBUG 宏:DSYSLOG
和DCSYSLOG
1.2) 有条件日志记录宏
就是在上面的基本用法的基础上加上 后缀_IF 如:
普通日志:LOG_IF
和CLOG_IF
,PLOG_IF
和CPLOG_IF
(PLOG_IF
和CPLOG_IF
比LOG_IF
和CLOG_IF
多了 errno 对应的错息),对应 DEBUG 宏:DLOG_IF
和DCLOG_IF
,DPLOG_IF
和DCPLOG_IF
verbose
日志:VLOG_IF
和CVLOG_IF
,对应 DEBUG 宏:DVLOG_IF
和DCVLOG_IF
syslog
日志:SYSLOG_IF
和CSYSLOG_IF
,对应 DEBUG 宏:DSYSLOG_IF
和DCSYSLOG_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++的特性宏。
本文来自博客园,作者:节奏自由,转载请注明原文链接:https://www.cnblogs.com/DesignLife/p/16918862.html