Pinenut 0.0.2

Pinenut 0.0.2

Tangent维护。



Pinenut 0.0.2

  • Tangent

Pinenut日志

为客户端(iOS、Android、桌面)编写的一个非常高效的日志系统。

codecov Crates.io Cocoapods Version

概述

Architecture

压缩

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

加密

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

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

缓冲

为了最小化I/O频率,Pinenut在将日志数据写入文件之前对其进行缓冲。客户端程序可能会意外退出(例如,崩溃),Pinenut使用mmap作为缓冲支持,这样即使在程序意外退出时,操作系统也能够帮助持久化缓冲数据。下次初始化Logger时,将自动读取和将缓冲数据写回日志文件。

此外,Pinenut实现了一种双缓冲系统,以改善缓冲读写性能并防止异步I/O影响当前线程的日志记录。

提取

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

解析

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

安装

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

许可协议

松果核采用MIT许可证发布。更多信息请参阅LICENSE文件。