Nanolog 是用于 C++ 的性能极佳的纳秒级日志系统,它公开了一个简单的类似于 printf 的 API,并以超过 7 纳秒的中值延迟实现了超过 8000 万条日志/秒。
实现这种疯狂的性能的方法是在编译时提取静态日志信息,仅将动态组件记录在运行时热路径中,然后将格式推迟到脱机进程中。 这基本上将工作从运行时移出,进入编译和后执行阶段。
有关此日志记录系统中使用的技术的更多信息,请参阅 2018 USENIX 年度技术会议上发布的 NanoLog 论文 (opens new window) 或原始作者的 博士学位论文 (opens new window)。
# Performance
本部分显示了使用现有日志记录系统,例如 spdlog v1.1.0 (opens new window), Log4j2 v2.8 (opens new window), Boost 1.55 (opens new window), glog v0.3.5 (opens new window) 和 Windows10 上事件跟踪器的性能。
# 吞吐量
最大吞吐量是通过 1-16 条线程无延迟地连续记录 100 万条消息来衡量的 (NanoLog 记录了 1 亿条消息以生成可比较大小的日志文件)。 ETW 是 "Windows 事件跟踪"。 所使用的日志消息可以在下面的 日志消息图 中找到
# 运行延迟
以纳秒为单位,每个像元代表第 50 / 99.9 百分位 尾延迟。 可以在 日志消息图 中找到使用的日志消息。
Message | NanoLog | spdlog | Log4j2 | glog | Boost | ETW |
---|---|---|---|---|---|---|
staticString | 7/ 37 | 214/ 2546 | 174 / 3364 | 1198/ 5968 | 1764/ 3772 | 161/ 2967 |
stringConcat | 7/ 36 | 279/ 905 | 256 / 25087 | 1212/ 5881 | 1829/ 5548 | 191/ 3365 |
singleInteger | 7/ 32 | 268/ 855 | 180 / 9305 | 1242/ 5482 | 1914/ 5759 | 167/ 3007 |
twoIntegers | 8/ 62 | 437/ 1416 | 183 / 10896 | 1399/ 6100 | 2333/ 7235 | 177/ 3183 |
singleDouble | 8/ 43 | 585/ 1562 | 175 / 4351 | 1983/ 6957 | 2610/ 7079 | 165/ 3182 |
complexFormat | 8/ 40 | 1776/ 5267 | 202 / 18207 | 2569/ 8877 | 3334/ 11038 | 218/ 3426 |
# 日志消息图
记录以上基准中使用的消息。斜体表示动态日志参数。
Message ID | Log Message Used |
---|---|
staticString | 启动备份副本垃圾回收器线程 |
singleInteger | 备份存储速度(最小值):181 MB / s 读取 |
twoIntegers | 缓冲区已消耗 1032024 字节的额外存储空间,当前分配为:1016544 字节 |
singleDouble | 使用比率 0.4 的逻辑删除比率平衡器 |
complexFormat | 初始化的 InfUdDriver 缓冲区:50000 接收缓冲区 (97 MB),50 发送缓冲区 (0 MB),花费 26.2 ms |
stringConcat | 在 basic+udp:host=192.168.1.140, port=12246 通讯会话 |
# 使用 NanoLog
# 先决条件
当前,NanoLog 仅适用于基于 Linux 的系统,并且依赖于以下条件:
- C++17 编译器:GNU g++ 7.5.0 (opens new window) 或更新的
- GNU Make 4.0 (opens new window) 或更新的
- Python 3.4.2 (opens new window) 或更新的
- POSIX AIO 和 Threads(通常与 Linux 一起安装)
# NanoLog 管道
NanoLog 系统通过对静态日志元数据进行重复数据删除并以二进制格式输出动态日志数据来实现低延迟日志记录。 这意味着 NanoLog 生成的日志文件为二进制文件,必须通过单独的解压缩程序传递,以生成完整的,人类可读的 ASCII 日志。
# 编译 NanoLog
NanoLog 有两个版本(预处理器版本和 C++17 版本,您必须选择一个才能与您的应用程序一起使用,因为它们无法互操作。 两者之间的最大区别是,预处理器版本需要一个将 Python 脚本集成到其构建链中,而 C++17 版本则更接近于常规库(只需对其进行构建和链接)。 使用预处理器版本的好处是,它在编译时执行更多的工作,从而导致运行时的优化程度略高。
如果您不知道要使用哪一个,请使用 C++17 NanoLog,因为它更易于使用。
# C++17 NanoLog
C++17 的 NanoLog 像传统库一样工作。
只需 #include“ NanoLogCpp17.h
并链接到 NanoLog 库。
可以在 示例目录 中找到示例应用程序。
要构建 C++ 17 NanoLog 运行时库,请进入 运行时目录 并调用 make
。
这将产生 ./libNanoLog.a
来链接程序和一个 ../ decompressor
程序(可用于重新填充二进制日志)。
编译程序时,请确保包括 NanoLog 标头目录 (-I ./runtime
),指向 NanoLog,pthreads 和 POSIX AIO 的链接 (-L ./ runtime / -lNanoLog -lrt -pthread
),并在编译器中启用格式检查(例如,传递-Werror = format 作为编译标志)。
启用格式检查非常重要,因为格式错误可能会在运行时无提示地破坏日志文件。
可以在 sample GNUmakefile 中找到 g++ 调用示例。
编译并运行程序之后,可以将生成的日志文件传递到 ./decompressor
程序以生成完整的人类可读日志文件(以下说明)。
# 预处理 NanoLog
NanoLog 的预处理器版本需要与用户构建链进行更紧密的集成,并且仅适用于高级用户。
它需要用户的 GNUmakefile 包含 [NanoLogMakeFrag](./ NanoLogMakeFrag),声明 USR_SRCS 和 USR_OBJS 变量以分别列出所有程序的源文件和对象文件,并使用预定义的 run-cxx
宏将所有的 .cc 用户文件编译为 .o 文件而不是 g++
文件。
有关更多详细信息,请参见 预处理程序样本 GNUmakefile。
在内部, run-cxx
调用将在源文件上运行 Python 脚本,并且每次编译都会生成特定于用户程序的库代码。
换句话说,即使是在同一应用程序的编译之间,编译也会构建不可移植的 NanoLog 库,并且每次 make
调用都会重新构建该库。
此外,编译还应在 app 目录中生成一个 ./decompressor
可执行文件,该文件可用于重构完整的人类可读日志文件(以下说明)。
# 示例应用
示例程序旨在成为用户如何与 NanoLog 库进行交互的指南。
用户可以修改这些应用程序以测试 NanoLog 的各种 API 和功能。
这些应用程序的 C++17 和 Preprocessor 版本分别位于 ./sample 和 ./sample_preprocessor 中。
可以在每个目录中修改 main.cc
,构建并运行,然后执行 decompressor
以检查结果。
以下是 C++17 NanoLog 的 示例应用程序 的示例。
cd sample
# Modify the application
nano main.cc
make clean-all
make
./sampleApplication
./decompressor decompress /tmp/logFile
2
3
4
5
6
7
8
9
注意:示例程序将日志文件设置在 /tmp/logFile
。
# NanoLog API
要在代码中使用 NanoLog 系统,只需包含 NanoLog 头文件(用于 C++17 NanoLog 的 NanoLogCpp17.h 或 用于预处理 NanoLog 的 NanoLog.h),并以类似于 printf 的方式调用 NANO_LOG()
函数,但之前的日志级别除外。
下面的例子:
#include "NanoLogCpp17.h"
using namespace NanoLog::LogLevels;
int main()
{
NANO_LOG(NOTICE, "Hello World! This is an integer %d and a double %lf\r\n", 1, 2.0);
return 0;
}
2
3
4
5
6
7
8
有效的日志级别是 DEBUG,NOTICE,WARNING 和 ERROR,并且可以通过 NanoLog::setLogLevel(...)
设置日志记录级别。
NanoLog API 的其余部分记录在 NanoLog.h 头文件中。
# 执行后日志解压缩
用户程序的执行会生成一个压缩的二进制日志文件 (默认位置:./compressedLog 或者 /tmp/logFile)。
为了使日志文件易于阅读,只需用 decompressor
调用日志文件即可。
./decompressor decompress ./compressedLog
构建 NanoLog 库之后,可以在 ./runtime 目录(对于 C ++ 17 NanoLog) 或用户 app 目录 (对于 Preprocessor NanoLog) 中找到解压缩器可执行文件。