# ftlog

[![Build Status](https://github.com/nonconvextech/ftlog/workflows/ftlog/badge.svg?branch=main)](https://github.com/nonconvextech/ftlog/actions)
![License](https://img.shields.io/crates/l/ftlog.svg)
[![Latest Version](https://img.shields.io/crates/v/ftlog.svg)](https://crates.io/crates/ftlog)
[![ftlog](https://docs.rs/ftlog/badge.svg)](https://docs.rs/ftlog)

普通的日志库受到磁盘io和系统pipe影响，单线程顺序写入单条速度大概要2500ns（SSD），如果碰到io抖动或者慢磁盘，日志会是低延时交易的主要瓶颈。
本库先把日志send到channel，再启动后台单独线程recv并且磁盘写入，测试速度在300ns左右。

## 用法

在 `Cargo.toml` 中加入引用
```toml
ftlog = "0.2"
```

在`main`函数开头加入配置：
```rust
// ftlog 导出了log库常用的宏
use ftlog::appender::FileAppender;
use ftlog::{debug, trace};
use log::{error, info, warn};

// 最简配置，使用默认设置
// root函数传入None则打印日志到stderr
let dest = FileAppender::new("./current.log");
ftlog::builder().root(dest).build().unwrap().init().unwrap();

trace!("Hello world!");
debug!("Hello world!");
info!("Hello world!");
warn!("Hello world!");
error!("Hello world!");

```

以下是一个功能更丰富的例子：

```rust
use ftlog::{
    appender::{Duration, FileAppender, Period},
    FtLogFormat, LevelFilter, Record,
};

// 配置logger
let logger = ftlog::builder()
    // 全局日志过滤等级
    .max_log_level(LevelFilter::Info)
    // 设置全局日志格式,为了性能时间戳格式不允许修改
    .format(FtLogFormatter)
    // 设置日志消息处理队列长度上限，避免大量未处理日志导致高内存占用
    // 设成 `false` 会在队列满时丢弃新日志
    // 设成 `true` 会在队列满时阻塞线程，等待日志线程
    // 以下是默认设置
    // 也可以用 unbound() 设置成不定长消息队列，消息特别多时会按需申请内存
    .bounded(100_000, false) // .unbounded()
    // 配置默认输出位置，传入None则输出到stderr
    .root(FileAppender::rotate_with_expire(
        "./current.log",
        Period::Day,
        Duration::days(7),
    ))
    // 将 ftlog::appender 下的日志输出到后面定义的"ftlog-appender"，即"ftlog-appender.log"文件
    .filter("ftlog::appender", "ftlog-appender", LevelFilter::Error)
    .appender("ftlog-appender", FileAppender::new("ftlog-appender.log"))
    .build()
    .expect("logger build failed");
// 将ftlog设置为全局logger
logger.init().expect("set logger failed");
```

更多例子请参见 `./examples`。

### 日志格式

```plain
2022-04-08 19:20:48.190+08 298ms <<这里是日志的延时，正常这个数为0，如果太大说明channel堵塞了 INFO main [src/ftlog.rs:14] 14575
```

其中 `298ms` 表示调用日志打印到日志专属线程开始处理日志消息之间的延迟，正常应为0ms。

```plain
2022-04-10 21:27:15.996+08 0ms 2 <<这表示丢弃了2条日志，说明使用了limit语法 INFO main [src/main.rs:29] limit running3 !
```

### 带间隔的日志

本库支持限制单条日志打印的频率。

不同代码行的日志间隔是互相独立的，互不影响。

后台线程以(模块、文件名、代码行)为key来记录上次日志打印时间，小于间隔则跳过。

下面这一行日志会有最小3000毫秒的间隔，也就是最多3000ms一条。
```rust
info!(limit=3000; "limit running{} !", 1);
```

```markdown
2022-04-10 21:27:10.996+08 0ms 0 INFO main [src/main.rs:29] limit running 3s !
2022-04-10 21:27:15.996+08 0ms 2 INFO main [src/main.rs:29] limit running 3s !
```
上面的数字2表示，在两条日志中间，还有2条被丢弃的日志。

### 日志分割

本库支持日志按时间分割，自动适配本地时区，可选分割时间精度如下：

- 分钟 `Period::Minute`
- 小时 `Period::Hour`
- 日 `Period::Day`
- 月 `Period::Month`
- 年 `Period::Year`

日志分割通过`FileAppender`设置，使用示例如下：

```rust
use ftlog::appender::{FileAppender, Period};

let logger = ftlog::builder()
    .root(FileAppender::rotate("./mylog.log", Period::Minute))
    .build()
    .unwrap();
logger.init().unwrap();
```

设置按分钟分割时，输出日志文件格式为 `mylog-{MMMM}{YY}{DD}T{hh}{mm}.log`，其中`mylog`为调用时指定的文件名。如果未指定拓展名则在指定文件名后面加时间标签。

时间戳只保留到分割的时间精度，如按日分割保存的文件名格式为 `log-{MMMM}{YY}{DD}.log`。

输出的日志示例：
```sh
$ ls
# 按分钟分割
current-20221026T1351.log
# 按小时分割
current-20221026T13.log
# 按日分割
current-20221026.log
# 按月分割
current-202210.log
# 按年分割
current-2022.log
# 设置的保存文件名无拓展时(如"./log")，则直接在文件名末尾加时间
log-20221026T1351
```

#### 自动清理分割后的日志

在设置了日志按时间分割的基础上，可以设置自动清理日志。按照上次修改时间清理ftlog产生的日志文件。

**注意**：自动清理使用文件名匹配，文件名与日志文件名格式完全一致的文件也会被清理。

```rust
use ftlog::{appender::{Period, FileAppender, Duration}};

// 这个例子中，文件名满足current-\d{8}T\d{4}.log的文件将被清理
// `another-\d{8}T\d{4}.log`, `current-\d{8}T\d{4}` 等等由于文件名不匹配不会被清理
// `current-\d{8}.log` 由于分割时间精度不同也不会被清理
// 按天分割，日志最多保留7天。
let appender = FileAppender::rotate_with_expire("./current.log", Period::Day, Duration::days(7));
let logger = ftlog::builder()
    .root(appender)
    .build()
    .unwrap();
logger.init().unwrap();
```
## 可选功能
- **tsc**
  使用TSC寄存器作为时钟源，实现同等时间精度下更快地获取时间戳。

  注意，启用TSC必须满足以下条件：
  1. CPU不能变频
  1. 必须在 **x86架构** 的CPU上，目前只支持 **Linux** 系统。否则会启用备用方案，牺牲时间精度换取速度

## 性能评测

> Rust：1.67.0-nightly

//! |                                                   |  message type | Apple M1 Pro, 3.2GHz  | AMD EPYC 7T83, 3.2GHz |
//! | ------------------------------------------------- | ------------- | --------------------- | --------------------- |
//! | `ftlog`                                           | static string |   75 ns/iter    | 385 ns/iter    |
//! | `ftlog`                                           | with i32      |   106 ns/iter   | 491 ns/iter    |
//! | `env_logger` <br/> output to file                 | static string | 1,674 ns/iter  | 1,142 ns/iter   |
//! | `env_logger` <br/> output to file                 | with i32      | 1,681 ns/iter   | 1,179 ns/iter   |
//! | `env_logger` <br/> output to file with `BufWriter`| static string | 279 ns/iter     | 550 ns/iter     |
//! | `env_logger` <br/> output to file with `BufWriter`| with i32      | 278 ns/iter     | 565 ns/iter     |

License: MIT OR Apache-2.0
