ezlog

中文介绍

介绍

ezlog是一个高性能的跨平台文件日志库。

可以用在Flutter,android,iOS,Windows,Linux,MacOS。

本项目参考了XlogLogan, 使用Rust重写。

特性

  • 可以用在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平台性能测试

单条日志格式

LibraryTime (ns)Allocations
logcat2,4277
logan4,72614
ezlog8,4047
xlog12,4597

启动时间

启动时间基线

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版本号
3Flag位
4-7日志的当前下标
8-15Unix时间戳(大端)
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