ezlog
介绍
ezlog是一个高性能的跨平台文件日志库。
可以用在Flutter,android,iOS,Windows,Linux,MacOS。
特性
- 可以用在Flutter,android,iOS,Windows,Linux,MacOS。
- 使用mmap做文件映射
- 支持压缩
- 支持加密
- 接口回调获取日志文件
- 支持日志过期清理
- 命令行解析
开源协议
See LICENSE-MIT, LICENSE-APACHE
Flutter 用例
在pubspec.yaml中添加ezlog_flutter依赖
dependencies:
ezlog_flutter: ^0.2.0
示例
import 'dart:io';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:ezlog_flutter/ezlog_flutter.dart';
import 'package:path_provider/path_provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
initEZLog();
}
Future<void> initEZLog() async {
EZLog.init(true);
Directory appDocDir = await getApplicationSupportDirectory();
String logDir = '${appDocDir.path}/ezlog';
var logger = EZLogger.config(
EZLogConfig.plaintext("main", Level.trace.id, logDir, 7));
logger.d("init", "success");
var logs = await EZLog.requestLogFilesForDate("main", "2022_08_25");
}
}
Android用例
添加ezlog依赖
打开项目build.gradle
文件,添加mavenCentral
仓库
buildscript {
repositories {
...
mavenCentral()
...
}
}
allprojects {
repositories {
...
mavenCentral()
...
}
}
在App层级的build.gradle
添加ezlog依赖
dependencies {
implementation "wtf.s1.ezlog:ezlog:0.2+"
}
同步gradle
在应用中初始化
override fun onCreate() {
super.onCreate()
val path = File(filesDir, "ezlog").absolutePath
val config = EZLogConfig.Builder("demo", path)
.compress(EZLog.CompressZlib)
.compressLevel(EZLog.CompressFast)
.cipher(EZLog.Aes128Gcm)
.cipherKey("a secret key!!!!".toByteArray())
.cipherNonce("unique nonce".toByteArray())
.enableTrace(BuildConfig.DEBUG)
.build()
EZLog.initWith(config)
EZLog.v("ezlog", "first blood")
EZLog.registerCallback(object : Callback {
override fun onLogsFetchSuccess(
logName: String?,
date: String?,
logs: Array<out String>?
) {
Log.i("ezlog", "$logName $date ${logs.contentToString()}")
logs?.let {
logs.getOrNull(0)?.let { log ->
Log.i("ezlog", "check file exists ${File(log).exists()}")
}
}
}
override fun onLogsFetchFail(logName: String?, date: String?, err: String?) {
Log.i("ezlog", "$logName $date $err")
}
})
}
iOS用例
添加ezlog依赖
在Podfile中添加依赖
pod 'EZLog', '~> 0.2'
接下来
pod update
打开Xcode,添加示例代码
import EZLog
init() {
pthread_setname_np("main")
#if DEBUG
ezlogInitWithTrace()
#else
ezlogInit()
#endif
let dirPath = URL.documents.appendingPathComponent("ezlog").relativePath
let config = EZLogConfig(level: Level.trace,
dirPath: dirPath,
name: "demo",
keepDays: 7,
maxSize: 150*1024,
compress: CompressKind.ZLIB,
compressLevel: CompressLevel.DEFAULT,
cipher: Cipher.AES128GCM,
cipherKey: [UInt8]("a secret key!!!!".utf8),
cipherNonce: [UInt8]("unique nonce".utf8))
let logger = EZLogger(config: config)
ezlogRegisterCallback(success: {name, date, logs in
if !logs.isEmpty {
for log in logs {
print("name:" + name + " date:" + date + " log:" + log);
}
} else {
print("no log found at that time")
}
}, fail: {name, date, err in
print("name:" + name + " date:" + date + " err:" + err);
})
logger.debug("first blood")
}
点击运行,查看控制台输出
Rust用例
添加ezlog依赖
在Cargo.toml中添加依赖
[dependencies]
ezlog = "0.2"
示例
#![allow(unused)] fn main() { use ezlog::EZLogConfigBuilder; use ezlog::Level; use log::{error, info, warn}; use log::{LevelFilter, Log}; ezlog::InitBuilder::new().init(); let config = EZLogConfigBuilder::new() .level(Level::Trace) .dir_path( dirs::download_dir() .unwrap() .into_os_string() .into_string() .expect("dir path error"), ) .build(); ezlog::create_log(config); info!("hello ezlog"); }
在examples文件夹中查看更多示例
性能
Android平台性能测试
单条日志格式
Library | Time (ns) | Allocations |
---|---|---|
logcat | 2,427 | 7 |
logan | 4,726 | 14 |
ezlog | 8,404 | 7 |
xlog | 12,459 | 7 |
启动时间
启动时间基线
min 206.4, median 218.5, max 251.9
启动时初始化ezlog
min 206.8, median 216.6, max 276.6
架构设计
代码结构
├── android
│ ├── app # android demo app
│ └── lib-ezlog # ezlog android library
├── examples # Rust examples
├── ezlog_flutter # Flutter plugin
├── ezlogcli # Rust command line tool
├── ezlog-core # Rust core library
├── ios
│ ├── EZLog # ezlog iOS library
│ ├── demo # iOS demo app
│ └── framework # ezlog XCFramework
日志文件格式
文件头
字节范围 | 内容 |
---|---|
0-1 | 固定字符 |
2 | 版本号 |
3 | Flag位 |
4-7 | 日志的当前下标 |
8-15 | Unix时间戳(大端) |
16 | 压缩算法 |
17 | 加密算法 |
18-21 | 密钥哈希 |
单条日志格式
字节范围 | 字段名称 | 描述 |
---|---|---|
0 | 开始标记 | 固定字符0x3b标记开始 |
1-可变下标 | 日志长度 | 描述日志内容长度的字节数 |
可变下标+1-可变下标+日志长度 | 日志内容 | 日志内容 |
可变下标+日志长度+1 | 结束标记 | 固定字符0x21表示结束 |
压缩算法
我们使用zlib作为默认压缩算法
加密算法
我们使用AES-GCM-SIV作为默认加密算法
AES-GCM-SIV 是一种对称加密算法,与非对称加密相比更高效。作为一种 AEAD 算法,与 AES-CFB 相比,它更安全;与 AES-GCM 相比,AES-GCM-SIV 具有抗重复口令攻击的特点。
确保 nonce 不重复
首先,我们需要一个初始nonce,在创建记录器时随机生成。然后,我们获取日志文件创建时的时间戳。当我们写入日志记录时,我们知道当前日志文件的索引,并且我们可以使用以下公式计算当前日志记录的nonce:
nonce = init_nonce ^ timestamp.extend(index)
本地构建
- 安装并配置 rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
- 使用nightly版本的rust
rustup default nightly
我们使用 build-std 特性,所以需要添加 nightly src 组件
rustup component add rust-src --toolchain nightly-x86_64-apple-darwin
clone 仓库并在命令行工具中打开。然后运行
cargo check
等待依赖下载…
cargo build -p ezlog
Flutter 构建
flutter packages get
flutter packages upgrade
Android 构建
- 添加 android targets
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
我们使用 cargo-ndk
来构建 dylib
cargo install cargo-ndk
cd android
sh b_android.sh
然后在 AndroidStudio 中打开当前工作区
iOS 构建
- 添加 iOS targets
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
安装 cbindgen
cargo install --force cbindgen
cd ios dir
sh b_ios.sh
在 Xcode 中打开 ios/EZlog.xcworkspace