65.9K
CodeProject 正在变化。 阅读更多。
Home

G3log,轻松实现异步日志记录

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (7投票s)

2014 年 8 月 21 日

公共领域

4分钟阅读

viewsIcon

43442

downloadIcon

513

异步、“崩溃安全”的日志记录,支持动态日志接收器

引言

G3log 是一个异步的“崩溃安全”日志记录器,支持添加自定义日志接收器。它开源且跨平台,目前在 Windows、Linux 和 OSX 上使用。G3log 的构建目标是像同步日志记录器一样安全,但速度极快,所有耗时的日志记录工作都在后台线程中完成。

它提供了一个可选的日志接收器,用于将日志保存到文件,并支持轻松添加自定义日志接收器。

 

G3log example with possible logging sinks vs a traditional logger
传统日志记录器与异步 g3log 的对比。g3log 的特性包括一些 g3log 用户当前正在使用的日志接收器。

G2log -> G3log

G3log 是开源且跨平台的。G3log 基于 2011 年发布的异步日志记录器 g2log,该日志记录器之前已在此处的 CodeProject 上介绍过。

g2log 的一个有趣的性能基准测试与 g3log 相比,g3log 的速度大约快 30%。日志调用线程越多,g3log 的性能优势就越明显。

G3log 具有引人注目的功能,例如:

  • 日志记录和基于契约的设计框架
  • LOG 调用是异步的,以避免减慢 LOG 调用线程的速度
  • LOG 调用是线程安全的
  • 在退出前,队列中的 LOG 条目会被刷新到日志接收器,因此在关闭时不会丢失任何条目
  • 捕获和记录 SIGSEGV 及其他致命信号,确保受控关闭
  • 在 Linux/OSX 上,捕获到的致命信号将生成堆栈转储到日志中
  • G3log 是跨平台的,目前在 Windows、各种 Linux 平台和 OSX 上使用
  • 在跨库使用时完全安全,即使是那些在运行时动态加载的库。
  • 与快速的 g2log 相比,性能有了显著提升。

 

使用 g3log

g3log 使用级别特定的日志记录。这可以在不减慢软件的日志调用部分的情况下实现。得益于活动对象(active object)的概念[1][2],g3log 可实现异步日志记录——实际的日志记录工作(包括缓慢的磁盘或网络 I/O 访问)在一个或多个后台线程中完成。

Example usage

可以选择使用流式语法或 printf 式语法

提供条件日志 `LOG_IF` 和普通 `LOG` 调用。默认的日志级别包括:`DEBUG`、`INFO`、`WARNING` 和 `FATAL`。

LOG(INFO) << "streaming API is as easy as ABC or " << 123;
 
// The streaming API has a printf-like equivalent
LOGF(WARNING, "Printf-style syntax is also %s", "available");

 

条件日志记录

LOG_IF(DEBUG, small < large) << "Conditional logging is also available. 
 
// The streaming API has a printf-like equivalent
LOGF_IF(INFO, small > large, 
             "Only expressions that are evaluated to true %s, 
             "will trigger a log call for the conditional API")

 

基于契约的设计

调用 `CHECK(false)` 将触发一个“致命”消息。它会被记录,然后应用程序会退出。调用 `LOG(FATAL)` 在本质上与调用 `CHECK(false)` 相同。

CHECK(false) << "triggers a FATAL message"
CHECKF(boolean_expression,"printf-api is also available");

 

初始化

使用 g3log 的典型场景如下所示。在程序启动的main函数中,立即使用默认的日志到文件接收器初始化 `g2::LogWorker`。

// main.cpp
#include<g2log.hpp>
#include<g2logworker.hpp>
#include <std2_make_unique.hpp>
 
#include "CustomSink.h" // can be whatever
 
int main(int argc, char**argv) {
   using namespace g2;
   std::unique_ptr<LogWorker> logworker{ LogWorker::createWithNoSink() }; // 1
   auto sinkHandle = logworker->addSink(std2::make_unique<CustomSink>(),  // 2
                                          &CustomSink::ReceiveLogMessage); // 3
   g2::initializeLogging(logworker.get());  // 4
  1. LogWorker 在没有接收器的情况下创建。
  2. 添加一个接收器,并返回一个具有访问接收器 API 的句柄
  3. 添加接收器时必须指定默认的日志函数
  4. 初始化时必须初始化日志一次,以允许 LOG 调用。

G3log 与接收器

接收器是 `LOG` 调用的接收者。G3log 提供了一个默认接收器(与 G2log 使用的相同),它将 `LOG` 调用保存到文件。接收器可以是任何类类型,没有限制,只要它能够接收 `std::string` 格式的 `LOG` 消息,或者接收 `g2::LogMessageMover`。

`std::string` 选项将提供预格式化的 `LOG` 输出。`g2::LogMessageMover` 是一个包装结构,包含原始数据 `g2::LogMessage`。如果您希望在自己的接收器中进行自定义处理和格式化,可以使用它。

使用 `g2::LogMessage` 很简单

// example similar to the default FileSink. It receives the LogMessage
//        and applies the default formatting with .toString()
void FileSink::fileWrite(LogMessageMover message) {
   ...

   std::string entry =  message.get().toString(); 
   ...

}

 

接收器创建

添加自定义接收器时,必须指定一个日志接收函数,该函数接受 `std::string` 作为参数(用于默认日志格式外观),或者接受 `g2::LogMessage` 作为参数(用于您自己定制的日志格式)。

auto sinkHandle = logworker->addSink(std2::make_unique<CustomSink>(), // 1, 3
                                     &CustomSink::ReceiveLogMessage); // 2
  1. 接收器由 G3log 管理,并作为 `std::unique_ptr` 包装后传输给日志记录器。
  2. 接收器的日志接收函数作为参数传递给 `LogWorker::addSink`。
  3. `LogWorker::addSink` 返回一个自定义接收器的句柄。

 

调用自定义接收器

自定义接收器的所有公共函数都可以通过句柄访问。

// handle-to-sink calls are thread safe. The calls are executed asynchronously
std::future<void> received = sinkHandle->call(&CustomSink::Foo, some_param, other);

 

代码示例

示例用法,添加了一个自定义接收器。通过接收器句柄调用实际接收器对象中的函数。

// main.cpp
#include<g2log.hpp>
#include<g2logworker.hpp>
#include <std2_make_unique.hpp>
 
#include "CustomSink.h"
 
int main(int argc, char**argv) {
   using namespace g2;
   std::unique_ptr<LogWorker> logworker{ LogWorker::createWithNoSink() };
   auto sinkHandle = logworker->addSink(std2::make_unique<CustomSink>(),
                                          &CustomSink::ReceiveLogMessage);
 
   // initialize the logger before it can receive LOG calls
   initializeLogging(logworker.get());
   LOG(WARNING) << "This log call, may or may not happend before"
                << "the sinkHandle->call below";
 
 
   // You can call in a thread safe manner public functions on your sink
   // The call is asynchronously executed on your custom sink.
   std::future<void> received = sinkHandle->call(&CustomSink::Foo, 
                                                 param1, param2);
 
   // If the LogWorker is initialized then at scope exit the g2::shutDownLogging() will be called. 
   // This is important since it protects from LOG calls from static or other entities that will go out of
   // scope at a later time. 
   //
   // It can also be called manually:
   g2::shutDownLogging();
}

 

示例用法,使用默认文件日志记录器并添加了一个自定义接收器。

// main.cpp
#include<g2log.hpp>
#include<g2logworker.hpp>
#include <std2_make_unique.hpp>
 
#include "CustomSink.h"
 
int main(int argc, char**argv) {
   using namespace g2;
   auto defaultHandler = LogWorker::createWithDefaultLogger(argv[0], 
                                                 path_to_log_file);
 
   // logger is initialized
   g2::initializeLogging(defaultHandler.worker.get());
 
   LOG(DEBUG) << "Make log call, then add another sink";
 
   defaultHandler.worker->addSink(std2::make_unique<CustomSink>(),
                                  &CustomSink::ReceiveLogMessage);
 
   ...
}

 

在代码库中的任何地方使用该日志记录器都**非常简单**。

// some_file.cpp
#include <g2log.hpp>
void SomeFunction() {
   ...
   LOG(INFO) << "Hello World";
}

 

g3log API

除了日志 API:`LOG`, `LOG_IF`, `CHECK`(以及类似的 printf 调用)之外,还有一些函数有助于使用和调整 g3log。

初始化

// g2log.hpp
initializeLogging(...) 

动态日志级别

即在运行时启用/禁用日志级别。启用此功能是通过以下方式完成的:
#define G2_DYNAMIC_LOGGING.

// g2loglevels.hpp
void setLogLevel(LEVELS level, bool enabled_status);
bool logLevel(LEVELS level); 

 

接收器处理

有关添加自定义接收器或访问默认文件接收器的信息,请参阅 `g2logworker.hpp`。

std::unique_ptr<SinkHandle<T>> addSink(...)
static g2::DefaultFileLogger createWithDefaultLogger(...)
static std::unique_ptr<LogWorker> createWithNoSink();

 

内部函数

有关几个通常不需要编码器使用的 `internal` 函数,请参阅 `g2log.hpp`,但如果 g3log 需要针对特殊关闭情况进行调整,则可以使用它们。

// will be called when the LogWorker goes out of scope
shutDownLogging() 
 
// for unit testing, or customized fatal call handling
void changeFatalInitHandlerForUnitTesting(...) 

 

在哪里获取

请参阅 https://bitbucket.org/KjellKod/g3log

谢谢

感谢 g2log 用户社区的宝贵反馈。凭借您的反馈和积极的 Beta 测试,g2log 得以进一步改进,成为当前的 g3log 版本。

 

尽情享受吧

Kjell (又名 KjellKod)

© . All rights reserved.