日志使用规范¶
本项目使用 tracing crate 进行日志记录,遵循统一的日志级别规范。
目录¶
日志级别定义¶
debug - 详细的调试信息¶
适用场景: - 请求体/响应体内容 - 中间计算结果 - 循环迭代细节 - 函数参数和返回值
使用场景示例:
debug!("发送私信请求体: {:?}", body);
debug!("响应状态码: {}", status);
debug!("响应结果: {}", text);
debug!("计算 MD5: {}", md5_hash);
info - 关键操作成功的通知¶
适用场景: - 服务启动/停止 - 重要操作成功(如上传完成、私信发送成功) - 阶段性进度通知(如批量操作总结)
使用场景示例:
info!("开始批量发送私信,目标用户数量: {}", user_ids.len());
info!("批量上传完成!成功: {}, 失败: {}", success_count, failure_count);
info!("图片上传成功!Media ID: {}", media_id);
warn - 可恢复的异常情况¶
适用场景: - 重试操作 - 降级方案 - 非致命错误
使用场景示例:
warn!("FINALIZE 失败 (状态码 {}), 准备重试 (第 {} 次)", status, retry_count);
warn!("发现重复的 media_id: {}", duplicate_id);
warn!("未找到 User ID,使用默认值");
error - 错误和失败¶
适用场景: - 请求失败 - 解析错误 - 系统级错误
使用场景示例:
error!("发送私信到用户 {} 失败: {}", user_id, error_msg);
error!("HTTP {} 响应内容: {}", status, text);
error!("认证失败:无法提取 CSRF token");
API 操作日志规范¶
推荐模式¶
// ✅ 正确:详细信息使用 debug
debug!("发送私信请求体: {:?}", body);
debug!("响应状态码: {}", status);
debug!("响应结果: {}", text);
// ✅ 正确:关键操作使用 info
info!("开始批量发送私信,目标用户数量: {}", user_ids.len());
info!("批量上传完成!成功: {}, 失败: {}", success_count, failure_count);
// ✅ 正确:重试使用 warn
warn!("FINALIZE 失败 (状态码 {}), 准备重试 (第 {} 次)", status, retry_count);
// ✅ 正确:错误使用 error
error!("发送私信到用户 {} 失败: {}", user_id, error_msg);
error!("HTTP {} 响应内容: {}", status, text);
禁止模式¶
// ❌ 错误:详细调试信息使用 info
info!("发送私信请求体: {:?}", body); // 应该用 debug
// ❌ 错误:中间状态使用 info
info!("准备发送 {} 个并发请求", tasks.len()); // 应该用 debug
// ❌ 错误:响应内容使用 info
info!("响应结果: {}", text); // 应该用 debug
日志初始化¶
推荐的日志格式配置¶
本项目统一使用以下清晰简洁的日志格式:
use tracing_subscriber;
fn main() {
// 推荐的日志配置 - 清晰简洁的格式
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false) // 隐藏冗长的模块路径
.with_thread_ids(false) // 隐藏线程 ID
.with_thread_names(false) // 隐藏线程名
.with_file(true) // 显示文件名
.with_line_number(true) // 显示行号
.compact() // 使用紧凑格式
.init();
// 应用代码...
}
日志输出效果对比¶
优化前(默认格式):
优化后(推荐格式):
配置说明¶
| 配置项 | 说明 | 效果 |
|---|---|---|
.with_target(false) |
隐藏模块路径 | 不显示 x_api::x::upload::client |
.with_thread_ids(false) |
隐藏线程 ID | 不显示 ThreadId(1) |
.with_thread_names(false) |
隐藏线程名 | 不显示线程名称 |
.with_file(true) |
显示文件名 | 显示 client.rs |
.with_line_number(true) |
显示行号 | 显示 :316 |
.compact() |
紧凑格式 | 简化时间戳,移除 ANSI 转义序列 |
生产环境配置¶
use tracing_subscriber;
fn main() {
// 生产环境 - INFO 级别日志
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.with_thread_ids(false)
.with_thread_names(false)
.with_file(true)
.with_line_number(true)
.compact()
.init();
// 应用代码...
}
开发/调试环境配置¶
use tracing_subscriber;
fn main() {
// 开发环境 - DEBUG 级别日志
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_target(false) // 仍然隐藏冗长的模块路径
.with_thread_ids(false)
.with_thread_names(false)
.with_file(true)
.with_line_number(true)
.compact()
.init();
// 应用代码...
}
通过环境变量控制¶
# 设置日志级别为 DEBUG
RUST_LOG=debug cargo run
# 设置日志级别为 INFO
RUST_LOG=info cargo run
# 只显示特定模块的日志
RUST_LOG=x_api::dm=debug cargo run
日志输出示例¶
图片上传流程¶
// INIT 阶段
info!("开始上传图片,大小: {} bytes", image_size);
debug!("INIT 请求 URL: {}", init_url);
debug!("INIT 响应: {:?}", init_response);
// APPEND 阶段
debug!("APPEND 请求 media_id: {}", media_id);
debug!("APPEND 响应状态: {}", status);
// FINALIZE 阶段
debug!("FINALIZE 请求 MD5: {}", md5);
if retry_count > 0 {
warn!("FINALIZE 失败,重试第 {} 次", retry_count);
}
info!("图片上传成功!Media ID: {}", media_id_string);
批量私信发送¶
info!("开始批量发送私信,目标用户数量: {}", user_ids.len());
for (index, user_id) in user_ids.iter().enumerate() {
debug!("发送第 {}/{} 条私信到用户 {}", index + 1, total, user_id);
debug!("请求体: {:?}", request_body);
match result {
Ok(_) => {
info!("私信发送成功!用户: {}", user_id);
}
Err(e) => {
error!("私信发送失败!用户: {}, 错误: {}", user_id, e);
}
}
}
info!("批量发送完成!成功: {}, 失败: {}", success_count, failure_count);
禁止的做法¶
❌ 在日志中输出敏感信息¶
// ❌ 禁止:完整的 cookies 或 token
error!("认证失败,cookies: {}", cookies); // 禁止
// ✅ 正确:脱敏处理
error!("认证失败,cookies 长度: {} bytes", cookies.len());
❌ 过度使用 info 级别¶
// ❌ 禁止:中间状态使用 info
info!("循环第 {} 次", i);
info!("临时变量值: {}", temp);
info!("准备调用函数 foo()");
// ✅ 正确:中间状态使用 debug
debug!("循环第 {} 次", i);
debug!("临时变量值: {}", temp);
debug!("准备调用函数 foo()");
❌ 使用 println! 进行日志输出¶
性能考虑¶
避免昂贵的字符串格式化¶
// ❌ 避免:总是执行格式化
debug!("大数据结构: {:?}", expensive_data_structure);
// ✅ 推荐:条件日志
if tracing::enabled!(tracing::Level::DEBUG) {
debug!("大数据结构: {:?}", expensive_data_structure);
}
使用结构化日志¶
use tracing::{info, instrument};
#[instrument]
async fn send_dm(user_id: &str, message: &str) -> Result<DMResult, TwitterError> {
info!(user_id = %user_id, message_len = message.len(), "发送私信");
// ...
}
日志格式化建议¶
统一使用中文¶
// ✅ 正确:使用中文
info!("批量上传完成!成功: {}, 失败: {}", success_count, failure_count);
// ❌ 避免:混用中英文
info!("Batch upload completed! 成功: {}, 失败: {}", success_count, failure_count);
使用 Emoji 增强可读性(可选)¶
info!("✅ 图片上传成功!Media ID: {}", media_id);
error!("❌ 发送私信失败!用户: {}", user_id);
warn!("⚠️ FINALIZE 重试第 {} 次", retry_count);
debug!("🔍 响应状态码: {}", status);
相关文档¶
- CLAUDE.md - 项目规范和架构设计
- TESTING.md - 测试策略(包含测试日志规范)
- API.md - Twitter API 详细规格
- DEVELOPMENT.md - 开发指南