# Secra Logger
基于 `tracing` 生态的日志库,主打 **自适应模式**:
- **库模式(Library)**:检测到外部已经初始化了全局 subscriber(例如应用已有自己的 `tracing_subscriber`),本库**不再初始化**,只提供统一的配置结构与初始化入口(幂等、零侵入)。
- **应用模式(Application)**:检测到当前进程还没初始化 subscriber,本库负责初始化 `tracing_subscriber`,并按配置安装 **console/file** 输出。
> 你既可以在“最终应用”里用它来统一初始化,也可以在“公共库/SDK”里用它而不破坏上层应用已有的日志体系。
## 特性
- ✅ 基于 `tracing` / `tracing-subscriber`
- ✅ **自动检测**并选择“库模式 / 应用模式”
- ✅ 支持控制台输出(human/json,可选颜色、target、文件与行号)
- ✅ 支持文件输出(human/json,异步 non-blocking writer)
- ✅ 支持通过 `RUST_LOG` 控制过滤规则(优先级更高)
- ✅ 支持 `log` crate 桥接(`tracing_log::LogTracer`)
## 安装
在 `Cargo.toml` 添加依赖:
```toml
[dependencies]
secra-logger = "3"
tracing = "0.1"
```
> 说明:Rust **不会**因为你依赖了 `secra-logger` 就自动让你在代码里使用 `tracing::info!`;如果你要写 `tracing` 宏,请显式添加 `tracing` 依赖(如上所示)。
## 快速开始(最小可运行)
下面示例在“未初始化 subscriber 的应用”中,会走 **应用模式** 并把日志输出到控制台:
```rust
use secra_logger::{LogConfig, Logger};
fn main() -> secra_logger::Result<()> {
let mut logger = Logger::new(LogConfig::default())?;
logger.init()?;
tracing::info!("应用启动");
tracing::warn!(user_id = 42, "演示结构化字段");
Ok(())
}
```
### 输出到文件(同时保留控制台)
```rust
use secra_logger::{LogConfig, LogFormat, Logger, LogRotationConfig, RotationStrategy};
use std::path::PathBuf;
fn main() -> secra_logger::Result<()> {
let mut cfg = LogConfig::default();
// 控制台
cfg.console_output = true;
cfg.console_format = LogFormat::Human;
// 文件
cfg.file_output = true;
cfg.log_file = Some(PathBuf::from("./logs/app.log"));
cfg.file_format = LogFormat::Json;
cfg.rotation = LogRotationConfig {
strategy: RotationStrategy::Daily,
max_files: 7,
};
let mut logger = Logger::new(cfg)?;
logger.init()?;
tracing::info!("这条会输出到 console + file");
Ok(())
}
```
> 注意:当前文件输出使用 `tracing-appender` 的 rolling appender。**文件名基准来自 `log_file` 的 file_stem**(例如 `./logs/app.log` 会以 `app` 为基准名)。
## 使用教程(详细)
### 1) 先理解“自适应模式”在你项目里的含义
- **最终应用(binary/service)**:通常推荐直接 `Logger::new(cfg)?.init()?;` 作为启动时的统一入口。
- **公共库/SDK(被别人依赖)**:也可以调用 `Logger::new(cfg)?.init()?;`,但如果上层应用已经初始化了 subscriber,本库会自动退化为 **库模式**,不会覆盖上层配置。
### 2) 显式指定模式(可选)
如果你不想依赖自动检测(例如测试里行为要固定),可以显式指定:
```rust
use secra_logger::{LogConfig, Logger, LoggingMode};
fn main() -> secra_logger::Result<()> {
let mut logger = Logger::new_with_mode(LogConfig::default(), LoggingMode::Application);
logger.init()?;
tracing::info!("强制 Application 模式");
Ok(())
}
```
### 3) 日志级别与过滤规则(`RUST_LOG` 优先)
初始化时会创建 `EnvFilter`:
- **如果设置了环境变量 `RUST_LOG`**:使用 `RUST_LOG` 的规则(优先级更高)。
- **否则**:使用 `LogConfig.level` 作为默认 directive(例如 `INFO`)。
示例:
```bash
RUST_LOG=debug cargo run
```
也可以按模块过滤:
```bash
RUST_LOG=my_app=debug,hyper=warn cargo run
```
### 4) 结构化日志怎么写
本库不改变 `tracing` 的用法,直接用 `tracing` 宏即可:
```rust
tracing::info!(
user_id = 123,
action = "login",
duration_ms = 150,
"用户登录成功"
);
```
### 5) 文件轮转策略(重要限制)
配置项在 `LogConfig.rotation.strategy`:
- `RotationStrategy::Daily`:按天轮转(真实实现)
- `RotationStrategy::Size(usize)`:**当前会降级为按小时轮转**(因为 `tracing-appender` 没有内建按大小轮转)
同时 `LogRotationConfig.max_files` 目前是 **保留字段**:
- `tracing-appender` 当前不负责自动清理旧文件
- 该字段用于兼容/未来扩展(不会自动删除旧日志)
### 6) 初始化幂等与常见坑
- `Logger::init()` 是幂等的:同一个 `Logger` 重复调用不会重复初始化。
- 在 **应用模式** 下内部使用 `try_init()`:如果进程里其它地方已经先 `init()` 过 subscriber,本次不会覆盖(不会 panic)。
- **文件不生成**常见原因:
- `cfg.file_output = true` 但 `cfg.log_file = None`
- `./logs` 目录不存在或无写权限(请先创建目录或确保权限)
- 进程提前退出:异步写入需要 `WorkerGuard` 存活(本库已在 `Logger` 内部持有;请确保 `logger` 的生命周期覆盖你的运行期)
### 7) 常用配置模板(直接复制)
#### 仅控制台(开发环境)
```rust
use secra_logger::{LogConfig, LogFormat, Logger};
fn main() -> secra_logger::Result<()> {
let mut cfg = LogConfig::default();
cfg.console_output = true;
cfg.console_format = LogFormat::Human;
cfg.console_colors = true;
cfg.file_output = false;
let mut logger = Logger::new(cfg)?;
logger.init()?;
tracing::debug!("仅控制台输出");
Ok(())
}
```
#### 仅文件(生产环境常见)
```rust
use secra_logger::{LogConfig, LogFormat, Logger, LogRotationConfig, RotationStrategy};
use std::path::PathBuf;
fn main() -> secra_logger::Result<()> {
let mut cfg = LogConfig::default();
cfg.console_output = false;
cfg.file_output = true;
cfg.log_file = Some(PathBuf::from("./logs/app.log"));
cfg.file_format = LogFormat::Json;
cfg.rotation = LogRotationConfig {
strategy: RotationStrategy::Daily,
max_files: 7,
};
let mut logger = Logger::new(cfg)?;
logger.init()?;
tracing::info!("仅文件输出(JSON)");
Ok(())
}
```
#### 控制台 JSON(便于容器 stdout 收集)
```rust
use secra_logger::{LogConfig, LogFormat, Logger};
fn main() -> secra_logger::Result<()> {
let mut cfg = LogConfig::default();
cfg.console_output = true;
cfg.console_format = LogFormat::Json;
cfg.console_colors = false; // JSON 通常不需要颜色
cfg.file_output = false;
let mut logger = Logger::new(cfg)?;
logger.init()?;
tracing::info!(service = "api", "stdout JSON");
Ok(())
}
```
#### 精简控制台元信息(不显示 target/文件/行号)
```rust
use secra_logger::{LogConfig, Logger};
fn main() -> secra_logger::Result<()> {
let mut cfg = LogConfig::default();
cfg.console_show_target = false;
cfg.console_show_file = false;
cfg.console_show_line = false;
let mut logger = Logger::new(cfg)?;
logger.init()?;
tracing::info!("更干净的控制台输出");
Ok(())
}
```
### 8) 与现有 `tracing_subscriber` 共存(库/框架集成)
如果你的应用已经自己初始化了 subscriber(比如为了加上其它 layer),本库会自动检测并进入 **库模式**,不会覆盖你已有的全局 subscriber:
```rust
use secra_logger::{LogConfig, Logger};
use tracing_subscriber::util::SubscriberInitExt;
fn main() -> secra_logger::Result<()> {
// 应用自己的 subscriber
tracing_subscriber::fmt().with_env_filter("info").finish().init();
// secra-logger 将检测到已有 subscriber -> Library 模式(不会覆盖)
let mut logger = Logger::new(LogConfig::default())?;
logger.init()?;
tracing::info!("仍然由应用自己的 subscriber 负责输出");
Ok(())
}
```
### 9) 使用 `log` crate 宏(兼容老代码)
本库在初始化时会尝试安装 `tracing_log::LogTracer`(把 `log` 事件桥接到 `tracing`)。如果你的项目使用 `log::info!` 这类宏:
```rust
use secra_logger::{LogConfig, Logger};
fn main() -> secra_logger::Result<()> {
let mut logger = Logger::new(LogConfig::default())?;
logger.init()?;
log::info!("通过 log 宏输出(会被桥接到 tracing)");
Ok(())
}
```
## 配置参考(对照 `src/config.rs`)
### `LogConfig`
```rust
pub struct LogConfig {
pub level: tracing::Level,
pub console_output: bool,
pub console_format: LogFormat,
pub console_colors: bool,
pub console_show_target: bool,
pub console_show_file: bool,
pub console_show_line: bool,
pub file_output: bool,
pub log_file: Option<std::path::PathBuf>,
pub file_format: LogFormat,
pub rotation: LogRotationConfig,
}
```
字段含义:
- **level**:仅在未设置 `RUST_LOG` 时生效,作为默认过滤级别(默认 `INFO`)
- **console_output**:是否启用控制台输出(默认 `true`)
- **console_format**:控制台格式
- `LogFormat::Human`:人类可读
- `LogFormat::Json`:JSON
- **console_colors**:控制台 ANSI 颜色(默认 `true`)
- **console_show_target / file / line**:控制台是否展示 `target/文件名/行号`(默认都为 `true`)
- **file_output**:是否启用文件输出(默认 `false`)
- **log_file**:文件输出路径(推荐类似 `./logs/app.log`)。注意它用于确定输出目录与文件基准名(stem)
- **file_format**:文件格式(默认 `Json`)
- **rotation**:轮转配置(见下)
### `LogRotationConfig` / `RotationStrategy`
```rust
pub struct LogRotationConfig {
pub strategy: RotationStrategy,
pub max_files: usize,
}
pub enum RotationStrategy {
Daily,
Size(usize),
}
```
## API 速览
- `Logger::new(config: LogConfig) -> Result<Logger>`:创建(自动检测模式)
- `Logger::new_with_mode(config: LogConfig, mode: LoggingMode) -> Logger`:创建(强制模式)
- `Logger::init(&mut self) -> Result<()>`:初始化(幂等)
- `SubscriberDetector::has_subscriber() -> bool`:是否已设置全局 dispatcher
- `SubscriberDetector::detect_mode() -> LoggingMode`:检测模式(带缓存)
## 许可证
MIT License