Easylogging++入门教程(四)Easylogging++ 日志记录

上一篇:Easylogging++入门教程(三)Easylogging++ 配置

下一篇:Easylogging++入门教程(五)Easylogging++ 特征

四、Easylogging++ 日志记录

Logging in easylogging++ 使用一系列宏 macros 进行日志的记录和输出。对于库的使用者而言方便而且不必关心过多的细节。当然,如果你想研究它的工作原理,可以参考 Easylogging++设计原理 相关的章节。

4.1 日志记录的基本方法

easylogging++ 提供了两个基本的宏定义,用于日志记录和输出:

  • LOG(LEVEL)
  • CLOG(LEVEL, logger ID)

宏定义 LOG 使用默认日志记录器,而宏 CLOG (Custom LOG)  可以通过第二个参数 logger ID 指定日志记录器。关于第一个参数 LEVEL,请参考《Easylogging++入门教程(三)Easylogging++ 配置》3.1 日志级别Level 部分的介绍。根据你的需要,不同的日志记录器可以有不同的配置,你也可以自定义宏来访问自定义logger。对于复杂格式的详细日志记录,有其他不同的宏定义来操作。下面是一个简单的使用宏进行日志记录的代码实例,假设已经对easylogging++进行了初始化。

LOG(INFO) << "This is info log";
CLOG(ERROR, "performance") << "This is info log using performance logger";

还有一种使用宏 LOG 进行日志记录的方式。你可以使用已经注册的logger ID定义宏ELPP_DEFAULT_LOGGER 和 ELPP_CURR_FILE_PERFORMANCE_LOGGER_ID ,然后当你在使用宏 LOG 进行日志记录时,会自动使用你指定的日志记录器,而非默认的日志记录器。

注意,上面的方法相关的代码需要放置在实现文件中而非头文件。

下面是简单的代码实例:

#ifndef ELPP_DEFAULT_LOGGER
#   define ELPP_DEFAULT_LOGGER "update_manager"
#endif
#ifndef ELPP_CURR_FILE_PERFORMANCE_LOGGER_ID
#   define ELPP_CURR_FILE_PERFORMANCE_LOGGER_ID ELPP_DEFAULT_LOGGER
#endif
#include "easylogging++.h"
UpdateManager::UpdateManager {
    _TRACE; // Logs using LOG(TRACE) provided logger is already registered - i.e, update_manager
    LOG(INFO) << "This will log using update_manager logger as well";
}
#include "easylogging++.h"
UpdateManager::UpdateManager {
    _TRACE; // Logs using LOG(TRACE) using default logger because no `ELPP_DEFAULT_LOGGER` is defined unless you have it in makefile
}

除了宏定义,你也可以使用 Logger 类进行日志记录。这一特征需要编译器支持可变参数模板(variadic templates)。在 samples/STL/logger-log-functions.cpp 中有对此的更详细的介绍。

4.2 有条件的日志记录 Conditional Logging

Easylogging++ 根据条件进行日志记录的功能。日志只有设定的某些条件满足时才进行记录,这一功能有时很有用,相关的宏带有后缀 _IF;

  • LOG_IF(condition, LEVEL)
  • CLOG_IF(condition, LEVEL, logger ID)

代码实例:

LOG_IF(condition, INFO) << "Logged if condition is true";

LOG_IF(false, WARNING) << "Never logged";
CLOG_IF(true, INFO, "performance") << "Always logged (performance logger)"

用于复杂详细型日志记录的宏一般以 V 开头,如, VLOG_IF and CVLOG_IF。下面有 关于复杂格式日志的详细介绍。

4.3 计次记录

Easylogging++ 另一个很好的记录日志方式是:计次记录。该种方式下,日志是否被记录,和调用一定次数相关。相关的宏定义为 _EVERY_N;

  • LOG_EVERY_N(n, LEVEL)
  • CLOG_EVERY_N(n, LEVEL, logger ID)

4.3.1 基于调用次数的日志记录

有些日志记录和调用的次数相关。相关宏包括:

  • LOG_AFTER_N(n, LEVEL); 调用次数超过n次后,即从第n+1次调用开始进行记录。
  • LOG_N_TIMES(n, LEVEL); 最多只进行n次日志记录,超过n次调用,不记录。

4.3.2 代码实例:

for (int i = 1; i <= 10; ++i) {
   LOG_EVERY_N(2, INFO) << "Logged every second iter";
}
// 5 logs written; 2, 4, 6, 7, 10

for (int i = 1; i <= 10; ++i) {
   LOG_AFTER_N(2, INFO) << "Log after 2 hits; " << i;
}
// 8 logs written; 3, 4, 5, 6, 7, 8, 9, 10

for (int i = 1; i <= 100; ++i) {
   LOG_N_TIMES(3, INFO) << "Log only 3 times; " << i;
}
// 3 logs writter; 1, 2, 3

 DEBUG 模式下的同样版本的宏以字母 D 开头,如 DLOG 仅在debug模式记录日志。debug模式(当定义了 _DEBUG 或者宏 NDEBUG 被 undefined)。

4.4 printf 风格的日志记录

对于支持C++11的带变长参数的模板功能的编译器,可以使用printf风格的日志记录方式。Logger类提供该种方式的功能函数。该种方式是多线程安全、类型安全的。

printf 风格的日志记录,分两步走:

  1. 获取对应的日志记录器 el::Loggers::getLogger(<logger_id>);
  2. 调用日志记录器的功能函数。

printf唯一不同的地方就是函数用 %v 代表参数;而不是自定义格式标识符。You can escape this by %%v

Following are various function signatures:

  • info(const char*, const T&, const Args&...)
  • warn(const char*, const T&, const Args&...)
  • error(const char*, const T&, const Args&...)
  • debug(const char*, const T&, const Args&...)
  • fatal(const char*, const T&, const Args&...)
  • trace(const char*, const T&, const Args&...)
  • verbose(int vlevel, const char*, const T&, const Args&...)

4.4.1 printf 风格的日志记录代码实例

// Use default logger
el::Logger* defaultLogger = el::Loggers::getLogger("default");

// STL logging (`ELPP_STL_LOGGING` should be defined)
std::vector<int> i;
i.push_back(1);
defaultLogger->warn("My first ultimate log message %v %v %v", 123, 222, i);

// Escaping
defaultLogger->info("My first ultimate log message %% %%v %v %v", 123, 222);

4.5 Verbose Logging

4.5.1 Basic

Verbose logging is useful in every software to record more information than usual. Very useful for troubleshooting. Following are verbose logging specific macros;

  • VLOG(verbose-level)
  • CVLOG(verbose-level, logger ID)

Conditional and Occasional Logging

Verbose logging also has conditional and occasional logging aspects i.e,

  • VLOG_IF(condition, verbose-level)
  • CVLOG_IF(condition, verbose-level, loggerID)
  • VLOG_EVERY_N(n, verbose-level)
  • CVLOG_EVERY_N(n, verbose-level, loggerID)
  • VLOG_AFTER_N(n, verbose-level)
  • CVLOG_AFTER_N(n, verbose-level, loggerID)
  • VLOG_N_TIMES(n, verbose-level)
  • CVLOG_N_TIMES(n, verbose-level, loggerID)top Goto Top

Verbose-Level

Verbose level is level of verbosity that can have range of 1-9. Verbose level will not be active unless you either set application arguments for it. Please read through Application Arguments section to understand more about verbose logging.

In order to change verbose level on the fly, please use Loggers::setVerboseLevel(base::type::VerboseLevel) akaLoggers::setVerboseLevel(int) function. (You can check current verbose level by Loggers::verboseLevel()

Check If Verbose Logging Is On

You can use a macro VLOG_IS_ON(verbose-level) to check to see if certain logging is on for source file for specified verbose level. This returns boolean that you can embed into if condition.

if (VLOG_IS_ON(2)) {
   // Verbosity level 2 is on for this file
}

VModule

VModule is functionality for verbose logging (as mentioned in above table) where you can specify verbosity by modules/source file. Following are some examples with explanation; Any of vmodule below starts with -vmodule= andLoggingFlag::DisableVModulesExtensions flag not set. Vmodule can completely be disabled by adding flagLoggingFlag::DisableVModules

Example with LoggingFlag::AllowVerboseIfModuleNotSpecified flag;

main=3,parser*=4:

  • A bad example but good enough for explanation;
  • Verbosity for any following file will be allowed; main{.h, .c, .cpp, .cc, .cxx, -inl.h, .hxx, .hpp} parser{.h, .c, .cpp, .cc, .cxx, -inl.h, .hxx, .hpp}
  • No other file will be logged for verbose level

Example with no LoggingFlag::AllowVerboseIfModuleNotSpecified flag;

main=3,parser*=4: Same explanation but any other file that does not fall under specified modules will have verbose logging enabled.

In order to change vmodules on the fly (instead of via command line args) – use Loggers::setVModules(const char*) whereconst char* represents the modules e.g, main=3,parser*=4 (as per above example)

STL Logging

As mentioned earlier, with easylogging++, you can log your STL templates including most containers. In order to do so you will need to define ELPP_STL_LOGGING macro. This enables including all the necessary headers and defines all necessary functions. For performance, containers are limited to log maximum of 100 entries. This behaviour can be changed by changed header file (base::consts::kMaxLogPerContainer) but not recommended as in order to log, writer has to go through each entry causing potential delays. But if you are not really concerned with performance, you may change this value.

Supported Templates

Following templates are supported as part of STL Logging; note: basic and primitive types e.g, std::string or long are not listed as they is supported anyway, following list only contains non-basic types e.g, containers or bitset etc.

* * * *
std::vector std::list std::deque std::queue
std::stack std::priority_queue std::set std::multiset
std::pair std::bitset std::map std::multimap

Some C++11 specific templates are supported by further explicit macro definitions; note these also need ELPP_STL_LOGGING

Template Macro Needed
std::array ELPP_LOG_STD_ARRAY
std::unordered_map ELPP_LOG_UNORDERED_MAP
std::unordered_multimap ELPP_LOG_UNORDERED_MAP
std::unordered_set ELPP_LOG_UNORDERED_SET
std::unordered_multiset ELPP_LOG_UNORDERED_SET

Standard manipulators are also supported, in addition std::stringstream is also supported.

Registering New Loggers

Loggers are unique in logger repository by ID. You can register new logger the same way as you would get logger. UsinggetLogger(.., ..) from el::Loggers helper class. This function takes two params, first being ID and second being boolean (optional) to whether or not to register new logger if does not already exist and returns pointer to existing (or newly created) el::Logger class. This second param is optional and defaults to true. If you set it to false and logger does not exist already, it will return nullptr.

By default, Easylogging++ registers three loggers (+ an internal logger);

  • Default logger (ID: default)
  • Performance logger (ID: performance)
  • Syslog logger (if ELPP_SYSLOG macro is defined) (ID: syslog)

If you wish to register a new logger, say e.g, with ID business

el::Logger* businessLogger = el::Loggers::getLogger("business");

This will register a new logger if it does not already exist otherwise it will get an existing one. But if you have passed in falseto the second param and logger does not already exist, businessLogger will be nullptr.

When you register a new logger, default configurations are used (see Default Configurations section above). Also worth noticing, logger IDs are case sensitive.

Unregister Loggers

You may unregister loggers; any logger except for default. You should be really careful with this function, only unregister loggers that you have created yourself otherwise you may end up in unexpected errors. For example, you dont want to unregister logger that is used or initialized by a third-party library and it may be using it.

To unregister logger, use el::Loggers::unregisterLogger("logger-id")

Populating Existing Logger IDs

Although this is a rare situation but if you wish to get list of all the logger IDs currently in repository, you may useel::Loggers::populateAllLoggerIds(std::vector<std::string>&) function to do that. The list passed in is cleared and filled up with all existing logger IDs.

Sharing Logging Repository

For advance logging, you can share your logging repositories to shared or static libraries, or even from library to application. This is rare case but a very good example is as follows;

Let’s say we have an application that uses easylogging++ and has it’s own configuration, now you are importing library that uses easylogging++ and wants to access logging repository of main application. You can do this using two ways;

  • Instead of using INITIALIZE_EASYLOGGINGPP you use SHARE_EASYLOGGINGPP(access-function-to-repository)
  • Instead of using INITIALIZE_EASYLOGGINGPP you use INITIALIZE_NULL_EASYLOGGINGPP and thenel::Helpers::setStorage(el::base::type::StoragePointer)Refer this for details

After you share repository, you can reconfigure the only repository (i.e, the one that is used by application and library both), and use both to write logs. A very good example is in samples/VC++/DLLSample

 

上一篇:Easylogging++入门教程(三)Easylogging++ 配置

下一篇:Easylogging++入门教程(五)Easylogging++ 特征

© 版权声明
THE END
喜欢就支持一下吧,技术咨询可以联系QQ407933975
点赞0
分享
评论 抢沙发