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






4.81/5 (7投票s)
异步、“崩溃安全”的日志记录,支持动态日志接收器
引言
G3log 是一个异步的“崩溃安全”日志记录器,支持添加自定义日志接收器。它开源且跨平台,目前在 Windows、Linux 和 OSX 上使用。G3log 的构建目标是像同步日志记录器一样安全,但速度极快,所有耗时的日志记录工作都在后台线程中完成。
它提供了一个可选的日志接收器,用于将日志保存到文件,并支持轻松添加自定义日志接收器。
传统日志记录器与异步 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
- LogWorker 在没有接收器的情况下创建。
- 添加一个接收器,并返回一个具有访问接收器 API 的句柄
- 添加接收器时必须指定默认的日志函数
- 初始化时必须初始化日志一次,以允许 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
- 接收器由 G3log 管理,并作为 `std::unique_ptr` 包装后传输给日志记录器。
- 接收器的日志接收函数作为参数传递给 `LogWorker::addSink`。
- `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)