一个好用的高性能日志库——NanoLog
博主介绍:程序喵大人
- 35- 资深C/C++/Rust/Android/iOS客户端开发
- 10年大厂工作经验
- 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
- 《C++20高级编程》《C++23高级编程》等多本书籍著译者
- 更多原创精品文章,首发gzh,见文末
- 👇👇记得订阅专栏,以防走丢👇👇
😉C++基础系列专栏
😃C语言基础系列专栏
🤣C++大佬养成攻略专栏
🤓C++训练营
👉🏻个人网站
NanoLog
NanoLog:https://github.com/PlatformLab/NanoLog
NanoLog 是一个极其高性能的纳秒级 C++ 日志系统,提供简单的 printf 风格 API,能够实现每秒超过 8000 万条日志记录,中位延迟仅为 7 纳秒左右。
它通过在编译时提取静态日志信息,仅在运行时热路径中记录动态组件,并将格式化工作推迟到离线进程来实现如此惊人的性能。这基本上将工作从运行时转移到了编译和执行后阶段。
有关此日志系统所使用技术的更多信息,请参阅在 2018 年 USENIX 年度技术会议上发表的 NanoLog 论文 或原始作者的博士论文。
性能
本节展示了 NanoLog 与现有的日志系统(如 spdlog v1.1.0、Log4j2 v2.8、Boost 1.55、glog v0.3.5 以及在 Windows 10 上使用 Windows 软件跟踪预处理器的 Windows 事件跟踪(WPP))的性能对比。
吞吐量
通过测量连续记录 100 万条消息(无延迟)且使用 1-16 个日志线程时的最大吞吐量得出(NanoLog 记录了 1 亿条消息以生成可比较大小的日志文件)。ETW 是“Windows 事件跟踪”。所使用的日志消息可在下方的
运行时延迟
以纳秒为单位进行测量,每个单元格代表第 50 百分位 / 第 99.9 百分位尾部延迟。所使用的日志消息可在下方的日志消息映射中找到。
日志消息映射
上方基准测试中使用的日志消息。斜体 表示动态日志参数。
消息 ID | 使用的日志消息 |
---|---|
staticString | Starting backup replica garbage collector thread(启动备份副本垃圾回收线程) |
singleInteger | Backup storage speeds (min): 181 MB/s read(备份存储速度(最小):181 MB/s 读取) |
twoIntegers | buffer has consumed 1032024 bytes of extra storage, current allocation: 1016544 bytes(缓冲区已消耗 1032024 字节的额外存储,当前分配:1016544 字节) |
singleDouble | Using tombstone ratio balancer with ratio = 0.4(使用墓碑比率平衡器,比率 = 0.4) |
complexFormat | Initialized InfUdDriver buffers: 50000 receive buffers ( 97 MB), 50 transmit buffers ( 0 MB), took 26.2 ms(已初始化 InfUdDriver 缓冲区:50000 个接收缓冲区(97 MB),50 个传输缓冲区(0 MB),耗时 26.2 毫秒) |
stringConcat | Opened session with coordinator at basic+udp:host=192.168.1.140,port=12246(已与协调器在 basic+udp:host=192.168.1.140,port=12246 处打开会话) |
使用 NanoLog
前提条件
目前 NanoLog 仅适用于基于 Linux 的系统,并依赖以下组件:
-
C++17 编译器:GNU g++ 7.5.0 或更高版本
-
GNU Make 4.0 或更高版本
-
Python 3.4.2 或更高版本
-
POSIX AIO 和线程(通常随 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 库即可。示例应用程序可在 sample 目录中找到。
要构建 C++17 NanoLog 运行时库,请进入 runtime 目录并调用 make
。这将生成 ./libNanoLog.a
以链接你的应用程序,以及一个 ./decompressor
应用程序,可用于解压缩二进制日志。
当你编译你的应用程序时,确保包含 NanoLog 头文件目录(-I ./runtime
),链接到 NanoLog、pthreads 和 POSIX AIO(-L ./runtime/ -lNanoLog -lrt -pthread
),并在编译器中启用格式检查(例如,传入 -Werror=format
作为编译标志)。最后一步非常重要,因为格式错误可能会在运行时默默损坏日志文件。示例 g++ 调用可在 sample GNUmakefile 中找到。
在编译并运行应用程序后,可以将生成的日志文件传递给 ./decompressor
应用程序,以生成完整的可读日志文件(如下所述)。
预处理器 NanoLog
预处理器版本的 NanoLog 需要与用户构建链进行更紧密的集成,仅适用于高级 / 极端用户。
它要求用户的 GNUmakefile 包含 NanoLogMakeFrag,声明 USR_SRCS 和 USR_OBJS 变量以分别列出应用程序的所有源文件和目标文件,并使用预定义的 run-cxx
宏来编译 所有 用户 .cc 文件到 .o 文件,而不是使用 g++
。更多详细信息,请参阅预处理器示例 GNUmakefile。
内部来说,run-cxx
调用将在源文件上运行 Python 脚本,并生成特定于用户应用程序每次编译的库代码。换句话说,编译构建了一个 不可移植的 NanoLog 库版本,即使在相同应用程序的不同编译之间也是如此,每次 make
调用都会重建此库。
此外,编译还应在应用程序目录中生成一个 ./decompressor
可执行文件,这可以用于重建完整的可读日志文件(如下所述)。
示例应用程序
示例应用程序旨在作为用户如何与 NanoLog 库交互的指南。用户可以修改这些应用程序以测试 NanoLog 的各种 API 和功能。C++17 和预处理器版本的示例应用程序分别位于 ./sample 和 ./sample_preprocessor 目录中。用户可以修改每个目录中的 main.cc
,构建 / 运行应用程序,并执行解压缩器以查看结果。
以下是 C++17 NanoLog 示例应用程序的示例:
cd sample# 修改应用程序
nano main.ccmake clean-all
make
./sampleApplication
./decompressor decompress /tmp/logFile
注意:示例应用程序将日志文件设置为 /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;
}
有效的日志级别有 DEBUG、NOTICE、WARNING 和 ERROR,可以通过 NanoLog::setLogLevel(...)
设置日志级别。
NanoLog 的其余 API 在 NanoLog.h 头文件中有文档说明。
执行后日志解压缩器
永久链接:执行后日志解压缩器
用户应用程序的执行应生成一个压缩的二进制日志文件(默认位置:./compressedLog 或 /tmp/logFile)。要使日志文件可读,只需调用 decompressor
应用程序并附带日志文件。
./decompressor decompress ./compressedLog
构建 NanoLog 库后,解压缩器可执行文件可在 ./runtime 目录(对于 C++17 NanoLog)或用户应用程序目录(对于预处理器 NanoLog)中找到。
单元测试
NanoLog 项目包含大量测试以确保正确性。以下是每个测试的描述以及如何访问 / 构建 / 执行它们。
集成测试
集成测试对 NanoLog 系统进行端到端的构建和测试。对于 C++17 NanoLog 和预处理器 NanoLog,它会使用 NanoLog 库编译客户端应用程序,执行该应用程序,并将生成的日志文件通过解压缩器运行。此外,它还会比较解压缩器的输出,以确保日志内容与预期结果一致。
可以使用以下命令执行这些测试:
cd integrationTest
./run.sh
预处理器和库单元测试
NanoLog 库和预处理器引擎也包含自己的单元测试套件。这些测试通过调用各个函数并检查其返回值是否与预期结果一致来测试每个组件的内部工作。
要运行 NanoLog 预处理器单元测试,请执行以下命令:
cd preprocessor
python UnitTests.py
要构建并运行 NanoLog 库单元测试,请执行以下命令:
git submodule update --initcd runtime
make clean
make test
./test --gtest_filter=-*assert*
注意:使用 gtest 过滤器是为了移除包含断言死亡语句的测试。
码字不易,欢迎大家点赞,关注,评论,谢谢!
C++训练营
专为校招、社招3年工作经验的同学打造的1V1 C++训练营,量身定制学习计划、每日代码review,简历优化,面试辅导,已帮助多名学员获得大厂offer!