提交qiming-mcp-proxy
This commit is contained in:
@@ -0,0 +1,749 @@
|
||||
//! 生产环境日志模块
|
||||
//!
|
||||
//! 提供生产环境专用的日志功能,包括结构化日志、日志聚合、性能监控等。
|
||||
#![allow(dead_code)]
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::error;
|
||||
|
||||
/// 生产日志管理器
|
||||
#[derive(Clone)]
|
||||
pub struct ProductionLogger {
|
||||
/// 日志配置
|
||||
config: LoggingConfig,
|
||||
/// 日志收集器
|
||||
collectors: Vec<Arc<dyn LogCollector + Send + Sync>>,
|
||||
/// 日志过滤器
|
||||
filters: Vec<Arc<dyn LogFilter + Send + Sync>>,
|
||||
/// 日志统计
|
||||
stats: Arc<RwLock<LoggingStats>>,
|
||||
/// 日志缓冲区
|
||||
buffer: Arc<RwLock<LogBuffer>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ProductionLogger {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ProductionLogger")
|
||||
.field("config", &self.config)
|
||||
.field("collectors_count", &self.collectors.len())
|
||||
.field("filters_count", &self.filters.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// 日志配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoggingConfig {
|
||||
/// 日志级别
|
||||
pub level: LogLevel,
|
||||
/// 输出格式
|
||||
pub format: LogFormat,
|
||||
/// 输出目标
|
||||
pub targets: Vec<LogTarget>,
|
||||
/// 缓冲配置
|
||||
pub buffer_config: BufferConfig,
|
||||
/// 轮转配置
|
||||
pub rotation_config: RotationConfig,
|
||||
/// 采样配置
|
||||
pub sampling_config: SamplingConfig,
|
||||
}
|
||||
|
||||
/// 日志级别
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LogLevel {
|
||||
Trace,
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
/// 日志格式
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LogFormat {
|
||||
/// JSON 格式
|
||||
Json,
|
||||
/// 纯文本格式
|
||||
Text,
|
||||
/// 结构化格式
|
||||
Structured,
|
||||
/// 自定义格式
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
/// 日志输出目标
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum LogTarget {
|
||||
/// 控制台输出
|
||||
Console,
|
||||
/// 文件输出
|
||||
File { path: String },
|
||||
/// 系统日志
|
||||
Syslog,
|
||||
/// 远程日志服务
|
||||
Remote { endpoint: String, api_key: String },
|
||||
/// Elasticsearch
|
||||
Elasticsearch { url: String, index: String },
|
||||
/// 自定义目标
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
/// 缓冲配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BufferConfig {
|
||||
/// 缓冲区大小
|
||||
pub size: usize,
|
||||
/// 刷新间隔
|
||||
pub flush_interval: Duration,
|
||||
/// 批量大小
|
||||
pub batch_size: usize,
|
||||
/// 是否启用压缩
|
||||
pub enable_compression: bool,
|
||||
}
|
||||
|
||||
/// 轮转配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RotationConfig {
|
||||
/// 最大文件大小 (MB)
|
||||
pub max_file_size_mb: u64,
|
||||
/// 最大文件数量
|
||||
pub max_files: u32,
|
||||
/// 轮转间隔
|
||||
pub rotation_interval: Duration,
|
||||
/// 压缩旧文件
|
||||
pub compress_old_files: bool,
|
||||
}
|
||||
|
||||
/// 采样配置
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SamplingConfig {
|
||||
/// 是否启用采样
|
||||
pub enabled: bool,
|
||||
/// 采样率 (0.0-1.0)
|
||||
pub rate: f64,
|
||||
/// 采样策略
|
||||
pub strategy: SamplingStrategy,
|
||||
}
|
||||
|
||||
/// 采样策略
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum SamplingStrategy {
|
||||
/// 随机采样
|
||||
Random,
|
||||
/// 基于时间的采样
|
||||
TimeBased,
|
||||
/// 基于级别的采样
|
||||
LevelBased,
|
||||
/// 自适应采样
|
||||
Adaptive,
|
||||
}
|
||||
|
||||
/// 日志条目
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LogEntry {
|
||||
/// 时间戳
|
||||
pub timestamp: SystemTime,
|
||||
/// 日志级别
|
||||
pub level: LogLevel,
|
||||
/// 消息
|
||||
pub message: String,
|
||||
/// 模块
|
||||
pub module: String,
|
||||
/// 文件名
|
||||
pub file: Option<String>,
|
||||
/// 行号
|
||||
pub line: Option<u32>,
|
||||
/// 字段
|
||||
pub fields: HashMap<String, serde_json::Value>,
|
||||
/// 跟踪 ID
|
||||
pub trace_id: Option<String>,
|
||||
/// 跨度 ID
|
||||
pub span_id: Option<String>,
|
||||
}
|
||||
|
||||
/// 日志收集器 trait
|
||||
pub trait LogCollector {
|
||||
/// 收集日志
|
||||
fn collect(&self, entry: &LogEntry) -> Result<()>;
|
||||
/// 刷新缓冲区
|
||||
fn flush(&self) -> Result<()>;
|
||||
/// 获取收集器名称
|
||||
fn name(&self) -> &str;
|
||||
}
|
||||
|
||||
/// 日志过滤器 trait
|
||||
pub trait LogFilter {
|
||||
/// 过滤日志
|
||||
fn filter(&self, entry: &LogEntry) -> bool;
|
||||
/// 获取过滤器名称
|
||||
fn name(&self) -> &str;
|
||||
}
|
||||
|
||||
/// 控制台日志收集器
|
||||
pub struct ConsoleCollector {
|
||||
/// 收集器名称
|
||||
name: String,
|
||||
/// 格式化器
|
||||
formatter: Box<dyn LogFormatter + Send + Sync>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ConsoleCollector {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ConsoleCollector")
|
||||
.field("name", &self.name)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// 文件日志收集器
|
||||
pub struct FileCollector {
|
||||
/// 收集器名称
|
||||
name: String,
|
||||
/// 文件路径
|
||||
file_path: String,
|
||||
/// 轮转配置
|
||||
rotation_config: RotationConfig,
|
||||
/// 格式化器
|
||||
formatter: Box<dyn LogFormatter + Send + Sync>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for FileCollector {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FileCollector")
|
||||
.field("name", &self.name)
|
||||
.field("file_path", &self.file_path)
|
||||
.field("rotation_config", &self.rotation_config)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// 远程日志收集器
|
||||
pub struct RemoteCollector {
|
||||
/// 收集器名称
|
||||
name: String,
|
||||
/// 远程端点
|
||||
endpoint: String,
|
||||
/// API 密钥
|
||||
api_key: String,
|
||||
/// HTTP 客户端
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for RemoteCollector {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RemoteCollector")
|
||||
.field("name", &self.name)
|
||||
.field("endpoint", &self.endpoint)
|
||||
.field("api_key", &"<hidden>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// 日志格式化器 trait
|
||||
pub trait LogFormatter {
|
||||
/// 格式化日志条目
|
||||
fn format(&self, entry: &LogEntry) -> Result<String>;
|
||||
}
|
||||
|
||||
/// JSON 格式化器
|
||||
#[derive(Debug)]
|
||||
pub struct JsonFormatter;
|
||||
|
||||
/// 文本格式化器
|
||||
#[derive(Debug)]
|
||||
pub struct TextFormatter {
|
||||
/// 时间格式
|
||||
time_format: String,
|
||||
/// 是否包含颜色
|
||||
colored: bool,
|
||||
}
|
||||
|
||||
/// 级别过滤器
|
||||
#[derive(Debug)]
|
||||
pub struct LevelFilter {
|
||||
/// 最小级别
|
||||
min_level: LogLevel,
|
||||
}
|
||||
|
||||
/// 模块过滤器
|
||||
#[derive(Debug)]
|
||||
pub struct ModuleFilter {
|
||||
/// 允许的模块
|
||||
allowed_modules: Vec<String>,
|
||||
/// 禁止的模块
|
||||
denied_modules: Vec<String>,
|
||||
}
|
||||
|
||||
/// 速率限制过滤器
|
||||
#[derive(Debug)]
|
||||
pub struct RateLimitFilter {
|
||||
/// 速率限制器
|
||||
limiter: Arc<RwLock<HashMap<String, RateLimiter>>>,
|
||||
/// 每秒最大日志数
|
||||
max_logs_per_second: u32,
|
||||
}
|
||||
|
||||
/// 简单速率限制器
|
||||
#[derive(Debug)]
|
||||
struct RateLimiter {
|
||||
/// 最后重置时间
|
||||
last_reset: SystemTime,
|
||||
/// 当前计数
|
||||
count: u32,
|
||||
/// 限制
|
||||
limit: u32,
|
||||
}
|
||||
|
||||
/// 日志缓冲区
|
||||
#[derive(Debug)]
|
||||
pub struct LogBuffer {
|
||||
/// 缓冲的日志条目
|
||||
entries: Vec<LogEntry>,
|
||||
/// 缓冲区大小限制
|
||||
max_size: usize,
|
||||
/// 最后刷新时间
|
||||
last_flush: SystemTime,
|
||||
}
|
||||
|
||||
/// 日志统计
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LoggingStats {
|
||||
/// 总日志数
|
||||
pub total_logs: u64,
|
||||
/// 按级别统计
|
||||
pub logs_by_level: HashMap<String, u64>,
|
||||
/// 按模块统计
|
||||
pub logs_by_module: HashMap<String, u64>,
|
||||
/// 错误统计
|
||||
pub error_count: u64,
|
||||
/// 警告统计
|
||||
pub warning_count: u64,
|
||||
/// 平均处理时间
|
||||
pub avg_processing_time: Duration,
|
||||
/// 缓冲区使用率
|
||||
pub buffer_usage: f64,
|
||||
/// 丢弃的日志数
|
||||
pub dropped_logs: u64,
|
||||
}
|
||||
|
||||
impl ProductionLogger {
|
||||
/// 创建新的生产日志管理器
|
||||
pub fn new(config: LoggingConfig) -> Self {
|
||||
Self {
|
||||
config,
|
||||
collectors: Vec::new(),
|
||||
filters: Vec::new(),
|
||||
stats: Arc::new(RwLock::new(LoggingStats::default())),
|
||||
buffer: Arc::new(RwLock::new(LogBuffer::new(1000))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 添加日志收集器
|
||||
pub fn add_collector(&mut self, collector: Arc<dyn LogCollector + Send + Sync>) {
|
||||
self.collectors.push(collector);
|
||||
}
|
||||
|
||||
/// 添加日志过滤器
|
||||
pub fn add_filter(&mut self, filter: Arc<dyn LogFilter + Send + Sync>) {
|
||||
self.filters.push(filter);
|
||||
}
|
||||
|
||||
/// 记录日志
|
||||
pub async fn log(&self, entry: LogEntry) -> Result<()> {
|
||||
// 应用过滤器
|
||||
for filter in &self.filters {
|
||||
if !filter.filter(&entry) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// 更新统计
|
||||
self.update_stats(&entry).await;
|
||||
|
||||
// 添加到缓冲区
|
||||
let mut buffer = self.buffer.write().await;
|
||||
buffer.add_entry(entry.clone());
|
||||
|
||||
// 检查是否需要刷新
|
||||
if buffer.should_flush(&self.config.buffer_config) {
|
||||
drop(buffer);
|
||||
self.flush_buffer().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 刷新缓冲区
|
||||
pub async fn flush_buffer(&self) -> Result<()> {
|
||||
let mut buffer = self.buffer.write().await;
|
||||
let entries = buffer.drain_entries();
|
||||
drop(buffer);
|
||||
|
||||
// 发送到所有收集器
|
||||
for entry in entries {
|
||||
for collector in &self.collectors {
|
||||
if let Err(e) = collector.collect(&entry) {
|
||||
error!(
|
||||
"Log collector {} processing failed: {}",
|
||||
collector.name(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新所有收集器
|
||||
for collector in &self.collectors {
|
||||
if let Err(e) = collector.flush() {
|
||||
error!(
|
||||
"Log collector {} failed to refresh: {}",
|
||||
collector.name(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 更新统计信息
|
||||
async fn update_stats(&self, entry: &LogEntry) {
|
||||
let mut stats = self.stats.write().await;
|
||||
stats.total_logs += 1;
|
||||
|
||||
let level_key = format!("{:?}", entry.level);
|
||||
*stats.logs_by_level.entry(level_key).or_insert(0) += 1;
|
||||
|
||||
*stats
|
||||
.logs_by_module
|
||||
.entry(entry.module.clone())
|
||||
.or_insert(0) += 1;
|
||||
|
||||
match entry.level {
|
||||
LogLevel::Error => stats.error_count += 1,
|
||||
LogLevel::Warn => stats.warning_count += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取统计信息
|
||||
pub async fn get_stats(&self) -> LoggingStats {
|
||||
self.stats.read().await.clone()
|
||||
}
|
||||
|
||||
/// 启动后台任务
|
||||
pub async fn start_background_tasks(&self) {
|
||||
let buffer = Arc::clone(&self.buffer);
|
||||
let config = self.config.clone();
|
||||
let logger = self.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut interval = tokio::time::interval(config.buffer_config.flush_interval);
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
let should_flush = {
|
||||
let buffer = buffer.read().await;
|
||||
!buffer.entries.is_empty()
|
||||
};
|
||||
|
||||
if should_flush {
|
||||
if let Err(e) = logger.flush_buffer().await {
|
||||
error!("Failed to refresh the log buffer regularly: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl LogCollector for ConsoleCollector {
|
||||
fn collect(&self, entry: &LogEntry) -> Result<()> {
|
||||
let formatted = self.formatter.format(entry)?;
|
||||
println!("{formatted}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&self) -> Result<()> {
|
||||
// 控制台不需要刷新
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl LogCollector for FileCollector {
|
||||
fn collect(&self, entry: &LogEntry) -> Result<()> {
|
||||
let _formatted = self.formatter.format(entry)?;
|
||||
// 这里应该实现文件写入逻辑
|
||||
// 包括轮转检查
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&self) -> Result<()> {
|
||||
// 刷新文件缓冲区
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl LogCollector for RemoteCollector {
|
||||
fn collect(&self, _entry: &LogEntry) -> Result<()> {
|
||||
// 这里应该实现异步发送到远程服务
|
||||
// 可以使用队列缓冲
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(&self) -> Result<()> {
|
||||
// 刷新远程队列
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl LogFormatter for JsonFormatter {
|
||||
fn format(&self, entry: &LogEntry) -> Result<String> {
|
||||
serde_json::to_string(entry).context("序列化日志条目为 JSON 失败")
|
||||
}
|
||||
}
|
||||
|
||||
impl LogFormatter for TextFormatter {
|
||||
fn format(&self, entry: &LogEntry) -> Result<String> {
|
||||
let timestamp = entry
|
||||
.timestamp
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(format!(
|
||||
"[{}] {:5} {}: {}",
|
||||
timestamp.as_secs(),
|
||||
format!("{:?}", entry.level),
|
||||
entry.module,
|
||||
entry.message
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl LogFilter for LevelFilter {
|
||||
fn filter(&self, entry: &LogEntry) -> bool {
|
||||
self.level_to_number(&entry.level) >= self.level_to_number(&self.min_level)
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"level_filter"
|
||||
}
|
||||
}
|
||||
|
||||
impl LevelFilter {
|
||||
fn level_to_number(&self, level: &LogLevel) -> u8 {
|
||||
match level {
|
||||
LogLevel::Trace => 0,
|
||||
LogLevel::Debug => 1,
|
||||
LogLevel::Info => 2,
|
||||
LogLevel::Warn => 3,
|
||||
LogLevel::Error => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LogFilter for ModuleFilter {
|
||||
fn filter(&self, entry: &LogEntry) -> bool {
|
||||
if !self.denied_modules.is_empty()
|
||||
&& self
|
||||
.denied_modules
|
||||
.iter()
|
||||
.any(|m| entry.module.starts_with(m))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.allowed_modules.is_empty() {
|
||||
return self
|
||||
.allowed_modules
|
||||
.iter()
|
||||
.any(|m| entry.module.starts_with(m));
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"module_filter"
|
||||
}
|
||||
}
|
||||
|
||||
impl LogFilter for RateLimitFilter {
|
||||
fn filter(&self, _entry: &LogEntry) -> bool {
|
||||
// 这里应该实现速率限制逻辑
|
||||
// 基于模块或其他标识符
|
||||
true
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"rate_limit_filter"
|
||||
}
|
||||
}
|
||||
|
||||
impl LogBuffer {
|
||||
/// 创建新的日志缓冲区
|
||||
pub fn new(max_size: usize) -> Self {
|
||||
Self {
|
||||
entries: Vec::with_capacity(max_size),
|
||||
max_size,
|
||||
last_flush: SystemTime::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 添加日志条目
|
||||
pub fn add_entry(&mut self, entry: LogEntry) {
|
||||
if self.entries.len() >= self.max_size {
|
||||
// 移除最旧的条目
|
||||
self.entries.remove(0);
|
||||
}
|
||||
self.entries.push(entry);
|
||||
}
|
||||
|
||||
/// 检查是否应该刷新
|
||||
pub fn should_flush(&self, config: &BufferConfig) -> bool {
|
||||
self.entries.len() >= config.batch_size
|
||||
|| self.last_flush.elapsed().unwrap_or_default() >= config.flush_interval
|
||||
}
|
||||
|
||||
/// 排空缓冲区
|
||||
pub fn drain_entries(&mut self) -> Vec<LogEntry> {
|
||||
self.last_flush = SystemTime::now();
|
||||
std::mem::take(&mut self.entries)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoggingConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
level: LogLevel::Info,
|
||||
format: LogFormat::Json,
|
||||
targets: vec![LogTarget::Console],
|
||||
buffer_config: BufferConfig {
|
||||
size: 1000,
|
||||
flush_interval: Duration::from_secs(5),
|
||||
batch_size: 100,
|
||||
enable_compression: false,
|
||||
},
|
||||
rotation_config: RotationConfig {
|
||||
max_file_size_mb: 100,
|
||||
max_files: 10,
|
||||
rotation_interval: Duration::from_secs(3600),
|
||||
compress_old_files: true,
|
||||
},
|
||||
sampling_config: SamplingConfig {
|
||||
enabled: false,
|
||||
rate: 1.0,
|
||||
strategy: SamplingStrategy::Random,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoggingStats {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
total_logs: 0,
|
||||
logs_by_level: HashMap::new(),
|
||||
logs_by_module: HashMap::new(),
|
||||
error_count: 0,
|
||||
warning_count: 0,
|
||||
avg_processing_time: Duration::from_millis(0),
|
||||
buffer_usage: 0.0,
|
||||
dropped_logs: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_production_logger() {
|
||||
let config = LoggingConfig::default();
|
||||
let logger = ProductionLogger::new(config);
|
||||
|
||||
let entry = LogEntry {
|
||||
timestamp: SystemTime::now(),
|
||||
level: LogLevel::Info,
|
||||
message: "测试日志".to_string(),
|
||||
module: "test".to_string(),
|
||||
file: None,
|
||||
line: None,
|
||||
fields: HashMap::new(),
|
||||
trace_id: None,
|
||||
span_id: None,
|
||||
};
|
||||
|
||||
logger.log(entry).await.unwrap();
|
||||
|
||||
let stats = logger.get_stats().await;
|
||||
assert_eq!(stats.total_logs, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_level_filter() {
|
||||
let filter = LevelFilter {
|
||||
min_level: LogLevel::Warn,
|
||||
};
|
||||
|
||||
let info_entry = LogEntry {
|
||||
timestamp: SystemTime::now(),
|
||||
level: LogLevel::Info,
|
||||
message: "信息日志".to_string(),
|
||||
module: "test".to_string(),
|
||||
file: None,
|
||||
line: None,
|
||||
fields: HashMap::new(),
|
||||
trace_id: None,
|
||||
span_id: None,
|
||||
};
|
||||
|
||||
let error_entry = LogEntry {
|
||||
level: LogLevel::Error,
|
||||
..info_entry.clone()
|
||||
};
|
||||
|
||||
assert!(!filter.filter(&info_entry));
|
||||
assert!(filter.filter(&error_entry));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_formatter() {
|
||||
let formatter = JsonFormatter;
|
||||
|
||||
let entry = LogEntry {
|
||||
timestamp: SystemTime::now(),
|
||||
level: LogLevel::Info,
|
||||
message: "测试消息".to_string(),
|
||||
module: "test".to_string(),
|
||||
file: Some("test.rs".to_string()),
|
||||
line: Some(42),
|
||||
fields: HashMap::new(),
|
||||
trace_id: Some("trace123".to_string()),
|
||||
span_id: Some("span456".to_string()),
|
||||
};
|
||||
|
||||
let formatted = formatter.format(&entry).unwrap();
|
||||
assert!(formatted.contains("测试消息"));
|
||||
assert!(formatted.contains("trace123"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user