概述
压缩
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:Swift
,Rust
。未来将支持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的设备上运行。
库 | 每秒处理的日志记录数(速度) |
---|---|
Pinenut | 447460 |
Xlog | 317473 |
许可协议
松果核采用MIT许可证发布。更多信息请参阅LICENSE文件。