secra-logger 0.3.0

一个生产级的 Rust 日志系统库,基于 tracing 生态系统构建,支持结构化 JSON 日志、文件滚动、UTC+8 时区等特性
# Secra Logger 架构说明

## 📄 日志系统总体架构

该日志系统基于 `tracing` 生态系统构建,旨在提供统一的日志接口,支持结构化 JSON 输出、高度可配置性、上下文感知以及生产环境的稳定性。

### 核心组件

1. **日志入口**:业务代码通过 `tracing` 提供的宏(如 `info!``error!``debug!` 等)记录日志,实现对业务代码的透明性。

2. **日志层(Layer)**:使用 `tracing-subscriber` 组合多个 Layer,处理日志的格式化、过滤和输出。

3. **格式化器**:使用 `tracing-subscriber` 内置的 JSON 格式化器,将日志格式化为标准的 JSON 格式。

4. **输出管理**:通过 `tracing-appender` 实现日志的文件输出与滚动策略,支持按天滚动。

5. **上下文管理**:利用 `tracing` 的 Span 功能,支持请求 ID、自定义字段等上下文信息的注入。

## 🧱 模块划分与职责说明

### 1. config 模块

**职责**:负责解析和校验日志配置,包括日志等级、输出位置、滚动策略等。

**核心类型**:
- `LogConfig`:日志配置结构体
- `LogLevel`:日志等级枚举(ERROR, WARN, INFO, DEBUG, TRACE)
- `LogOutput`:日志输出目标(Stdout, File, Both)

**功能**:
- 从环境变量加载配置(`from_env`- 从 YAML/TOML 文件加载配置(`from_yaml`, `from_toml`- 解析 `RUST_LOG` 环境变量格式
- 构建 `tracing` 的 env filter 字符串

### 2. time 模块

**职责**:处理时间相关的操作,确保日志时间戳采用 UTC+8 时区。

**核心功能**:
- `now_utc8()`:获取当前 UTC+8 时间
- `format_utc8()`:格式化时间为 ISO 8601 格式(UTC+8)
- `format_log_timestamp()`:格式化日志时间戳
- `Utc8Time`:实现 `FormatTime` trait,用于 tracing-subscriber

### 3. formatter 模块

**职责**:JSON 格式化逻辑(当前使用 tracing-subscriber 内置格式化器)。

**说明**:虽然模块已创建,但当前实现使用 `tracing-subscriber` 内置的 JSON 格式化器,该模块保留用于未来扩展。

### 4. layer 模块

**职责**:组合和管理不同的 `tracing` Layer,处理日志的过滤、格式化和输出。

**核心功能**:
- `create_layers()`:根据配置创建日志层
- `create_json_stdout_layer()`:创建 JSON 格式的标准输出层
- `create_text_stdout_layer()`:创建文本格式的标准输出层
- `create_file_layer()`:创建文件输出层(支持滚动)
- `create_both_layer()`:创建同时输出到文件和标准输出的层

**技术细节**:
- 使用 `tracing-appender::rolling::RollingFileAppender` 处理文件滚动
- 使用 `tracing_subscriber::fmt::writer::Tee` 实现同时写入多个目标
- 自定义 `Utc8Time` 时间格式化器,确保所有日志时间戳为 UTC+8

### 5. init 模块

**职责**:提供日志系统的初始化入口,确保日志系统的正确配置和启动。

**核心功能**:
- `init_logging()`:初始化日志系统(只能调用一次)
- `init_from_env()`:从环境变量初始化
- `init_default()`:使用默认配置初始化
- `is_initialized()`:检查是否已初始化
- `set_panic_hook()`:设置 panic hook,捕获 panic 并记录

**初始化流程**:
1. 检查是否已初始化(双重检查锁)
2. 桥接 `log` crate 到 `tracing`
3. 创建环境过滤器
4. 创建日志层
5. 组合 layers 到 subscriber
6. 初始化 subscriber
7. 设置 panic hook(如果启用)
8. 标记为已初始化

## 🦀 核心接口定义

### 初始化接口

```rust
pub fn init_logging(config: LogConfig) -> Result<(), InitError>;
pub fn init_from_env() -> Result<(), InitError>;
pub fn init_default() -> Result<(), InitError>;
```

### 配置接口

```rust
pub struct LogConfig {
    pub level: LogLevel,
    pub modules: HashMap<String, LogLevel>,
    pub output: LogOutput,
    pub json: bool,
    pub actix_web: bool,
    pub capture_panic: bool,
}

pub enum LogOutput {
    Stdout,
    File {
        path: PathBuf,
        max_size: Option<u64>,
        daily: bool,
        max_files: Option<usize>,
    },
    Both { /* ... */ },
}
```

## ⚙️ 初始化流程说明

1. **解析配置**:从配置文件或环境变量中读取日志配置,生成 `LogConfig` 实例。

2. **设置日志等级**:根据配置设置全局默认日志等级,并支持按模块或目标进行细粒度配置。

3. **配置输出**:根据配置选择日志输出位置,支持标准输出和文件输出。

4. **设置滚动策略**:如果选择文件输出,配置文件滚动策略,支持按天滚动。

5. **初始化 Layer**:组合 `tracing` 的不同 Layer,包括格式化器、过滤器和输出器。

6. **注册订阅者**:将组合好的 Layer 注册为全局订阅者,开始接收和处理日志事件。

## 📦 实现细节

### 文件滚动

使用 `tracing-appender::rolling::RollingFileAppender` 处理文件滚动:
- 支持按天滚动(`Rotation::DAILY`- 支持不滚动(`Rotation::NEVER`- 自动创建目录
- 支持文件被删除后重新创建

### 时区处理

所有日志时间戳统一为 UTC+8(北京时间):
- 使用 `chrono::FixedOffset` 定义 UTC+8 时区
- 实现自定义 `FormatTime` trait
- 所有时间戳格式为 ISO 8601:`2025-01-01T12:00:00.123+08:00`

### JSON 格式

使用 `tracing-subscriber` 内置的 JSON 格式化器:
- 自动包含时间戳、等级、目标、消息等字段
- 支持 Span 字段自动注入
- 兼容 ELK / OpenSearch / Loki / CloudWatch

### Panic 捕获

自动设置 panic hook:
- 捕获 panic 信息并记录为 ERROR 级别日志
- 包含 panic 消息和位置信息
- 调用默认 panic hook 保持原有行为

## 🚀 使用示例

### 基本使用

```rust
use secra_logger::{init_logging, LogConfig, LogLevel, LogOutput};

let config = LogConfig {
    level: LogLevel::Info,
    output: LogOutput::Stdout,
    json: true,
    ..Default::default()
};

init_logging(config).unwrap();
tracing::info!("Hello, world!");
```

### 使用 Span

```rust
let span = tracing::info_span!(
    "http_request",
    request_id = "req-12345",
    user_id = "user123"
);
let _enter = span.enter();
tracing::info!("处理请求");
```

### 文件输出

```rust
let config = LogConfig {
    level: LogLevel::Debug,
    output: LogOutput::File {
        path: PathBuf::from("./logs/app.log"),
        max_size: Some(100 * 1024 * 1024),
        daily: true,
        max_files: Some(7),
    },
    json: true,
    ..Default::default()
};
init_logging(config).unwrap();
```

## ⚠️ 注意事项

1. **初始化只能调用一次**:重复初始化会返回 `InitError::AlreadyInitialized` 错误。

2. **文件 Guard**:使用 `tracing-appender``non_blocking` 时,返回的 `WorkerGuard` 需要在程序生命周期内保持,否则日志可能丢失。当前实现中,对于文件输出,guard 被 drop,这可能导致日志丢失。在生产环境中,应该保存 guard。

3. **类型系统限制**:由于 Rust 类型系统的限制,当前实现只支持组合一个 layer。对于 `Both` 模式,使用 `Tee` writer 实现同时写入文件和标准输出。

4. **环境变量优先级**`RUST_LOG` 环境变量的优先级高于配置文件中的设置。

## 🔮 未来改进

1. **支持多个独立 layers**:使用更复杂的类型系统或宏来支持组合多个独立的 layers。

2. **保存 WorkerGuard**:实现一个机制来保存 `WorkerGuard`,确保日志不丢失。

3. **按大小滚动**:完善按文件大小滚动的支持(当前 tracing-appender 的限制)。

4. **Actix-Web 集成**:完善 `tracing-actix-web` 的集成。

5. **自定义格式化器**:实现完全自定义的 JSON 格式化器,支持更多字段和格式选项。