#logger #log #logging #encryption-key #log-parser #compression

nightly pineapple-log

适用于客户端(iOS、Android、桌面)的极高性能日志系统,用 Rust 编写

2 个版本

0.0.2 2024 年 5 月 15 日
0.0.1 2023 年 11 月 26 日

#1502加密学

MIT 许可证

140KB
3K SLoC

pineapple 日志

适用于客户端(iOS、Android、桌面)的极高性能日志系统,用 Rust 编写。

codecov Crates.io Cocoapods Version

概述

Architecture

压缩

pineapple 支持流式日志压缩,它使用 Zstandard (即 zstd),这是一种在压缩率和速度之间取得良好平衡的高性能压缩算法。

加密

pineapple 在日志过程中使用 AES 128 算法进行对称加密。为了防止将对称密钥直接嵌入到代码中,pineapple 使用 ECDH 进行密钥协商(不使用 RSA,因为其密钥太长)。在初始化 Logger 时,无需提供对称加密密钥,而是应传递 ECDH 公钥。

pineapple 使用 secp256r1 椭圆曲线进行 ECDH。您可以自己生成加密的秘密和公钥,或使用 pineapple 内置的命令行工具:pinenut-cli

缓冲

为了最小化 IO 频率,pineapple 在写入文件之前缓冲日志数据。客户端程序可能会意外退出(例如,崩溃),pineapple 使用 mmap 作为缓冲支持,以便如果程序意外退出,操作系统仍然可以帮助持久化缓冲数据。下次初始化 Logger 时,自动读取缓冲数据并将其写回日志文件。

此外,pineapple 实现了 双缓冲 系统,以提高缓冲读写性能并防止异步 IO 影响当前线程的日志记录。

提取

使用 pineapple,我们不需要检索目录中的所有日志文件来提取日志,它提供了方便的提取功能,并支持以分钟粒度的时间范围提取。

解析

pineapple 日志文件的内容是在编码、压缩和加密后的特殊二进制序列,我们可以使用 pineapple 提供的解析功能来解析日志文件。

安装

Pinenut 提供以下语言的 API:SwiftRust。未来将支持 Kotlin

Swift 包管理器

.package(url: "https://github.com/TangentW/Pinenut.git", from: "0.0.1")

CocoaPods

pod 'Pinenut'

Rust Cargo

[dependencies]
pinenut-log = 0.0.1

用法

无论使用哪种语言,Pinenut 的 API 都大致相同。

日志初始化

Pinenut 使用一个 Logger 实例进行日志记录。在我们初始化 Logger 之前,我们需要传递日志标识符和存储日志文件的目录路径来构建 Domain 结构。

我们可以通过显式指定 Config 来自定义 Logger,详细信息请参阅 API 文档。

Swift 代码
let domain = Domain(identifier: "MyApp", directory: "/path/to/dir")
let config = Config(key: "Public Key Base64", compressionLevel: 10)
let logger = Logger(domain: domain, config: config)
Rust 代码
let domain = Domain::new("MyApp".into(), "/path/to/dir".into());
let config = Config::new().key_str(Some("Public Key Base64")).compression_level(10);
let logger = Logger::new(domain, config);

日志记录

只需构建一个 Record 并调用 log 方法。

Swift 代码

Swift 为日志记录提供了更方便的 API

logger.log(.info, "Hello World")
logger.log(.debug, tag: "MyModule", "Debug Message")

// `Logger` provides APIs for logging levels.
logger.info("Hello World")
logger.error("Error message")
logger.debug(tag: "MyModule", "Debug message")

// Flushes any buffered records asynchronously.
logger.flush()
Rust 代码

Rust 中可以通过 Builder 模式构建记录

// Builds `Meta` & `Record`.
let meta = Meta::builder().level(Level::Info).build();
let record = Record::builder().meta(meta).content("Hello World").build();
logger.log(&record);

// Flushes any buffered records asynchronously.
logger.flush();

详细信息请参阅 API 文档。

提取

只需调用 extract 方法来提取指定时间范围(以分钟粒度)的日志,并将它们写入目标文件。

Swift 代码
let domain = Domain(identifier: "MyApp", directory: "/path/to/dir")
let range = Date(timeIntervalSinceNow: -1800)...Date()

do {
    try Logger.extract(domain: domain, timeRange: range, destPath: "/path/to/destination")
} catch {
    print("Error: \(error)")
}
Rust 代码
let domain = Domain::new("MyApp".into(), "/path/to/dir".into());
let now = chrono::Utc::now();
let range = now.sub(Duration::from_secs(1800))..=now;

if let Err(err) = pinenut_log::extract(domain, range, "/path/to/destination") {
    println!("Error: {err}");
}

注意:提取文件的内容仍然是一个经过编码、压缩和加密的二进制序列。我们需要解析它以查看易于阅读的日志文本内容。

解析

您可以使用 parse 函数进行日志解析,并且您可以指定解析文本的格式。详细信息请参阅 API 文档。

Swift 代码
do {
    try Logger.parse(path: path, to: dest, secretKey: secretKey)
} catch {
    print("Error: \(error)")
}
Rust 代码
// Specifies the `DefaultFormater` as the log formatter.
if let Err(err) = pinenut_log::parse_to_file(&path, &output, secret_key, DefaultFormatter) {
    println!("Error: {err}");
}

或者使用内置的命令行工具 pinenut-cli

$ pinenut-cli parse ./my_log.pine \
    --output ./plain.log          \
    --secret-key XXXXXXXXXXX

密钥生成

在初始化 Logger 或解析日志之前,您需要准备公钥和私钥(公钥用于初始化 Logger,私钥用于解析日志)。

您可以使用 pinenut-cli 生成这对密钥

$ pinenut-cli gen-keys

基准测试

Pinenut 的某些设计灵感来源于 Xlog,以下是它们基准测试的比较。

这两个日志库都支持 Zstd,所以基准测试将使用相同版本的 zstd 库(v1.5.5)和压缩级别 10 进行。

在 iPhone 12 上执行,搭载 iOS 15.5。

Benchmark

每秒处理的日志记录数(速度)
Pinenut 447460
Xlog 317473

TODO

  • 级别过滤器
  • Kotlin API

许可

Pinenut 在 MIT 许可下发布。有关详细信息,请参阅 LICENSE。

依赖项

~7–13MB
~158K SLoC