提交qiming-mcp-proxy

This commit is contained in:
Codex
2026-06-01 13:03:20 +08:00
parent 9e9486b7c2
commit afb3d9f4e6
394 changed files with 124494 additions and 0 deletions

View File

@@ -0,0 +1,997 @@
use crate::utils::health_check::{HealthCheckResult, HealthStatus, SystemHealthStatus};
use crate::utils::logging::{LogLevel, StructuredLogger};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
use tokio::sync::{RwLock, mpsc};
use uuid::Uuid;
/// 告警级别
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum AlertLevel {
Info,
Warning,
Critical,
Emergency,
}
impl std::fmt::Display for AlertLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AlertLevel::Info => write!(f, "INFO"),
AlertLevel::Warning => write!(f, "WARNING"),
AlertLevel::Critical => write!(f, "CRITICAL"),
AlertLevel::Emergency => write!(f, "EMERGENCY"),
}
}
}
impl From<HealthStatus> for AlertLevel {
fn from(status: HealthStatus) -> Self {
match status {
HealthStatus::Healthy => AlertLevel::Info,
HealthStatus::Degraded => AlertLevel::Warning,
HealthStatus::Unhealthy => AlertLevel::Critical,
HealthStatus::Unknown => AlertLevel::Warning,
}
}
}
/// 告警规则
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertRule {
pub id: String,
pub name: String,
pub description: String,
pub component: Option<String>, // None表示适用于所有组件
pub condition: AlertCondition,
pub level: AlertLevel,
pub cooldown: Duration,
pub enabled: bool,
}
impl AlertRule {
pub fn new(
id: String,
name: String,
description: String,
condition: AlertCondition,
level: AlertLevel,
) -> Self {
Self {
id,
name,
description,
component: None,
condition,
level,
cooldown: Duration::from_secs(300), // 默认5分钟冷却
enabled: true,
}
}
pub fn with_component(mut self, component: String) -> Self {
self.component = Some(component);
self
}
pub fn with_cooldown(mut self, cooldown: Duration) -> Self {
self.cooldown = cooldown;
self
}
pub fn disabled(mut self) -> Self {
self.enabled = false;
self
}
/// 检查规则是否匹配给定的健康检查结果
pub fn matches(&self, result: &HealthCheckResult) -> bool {
if !self.enabled {
return false;
}
// 检查组件匹配
if let Some(ref component) = self.component {
if &result.component != component {
return false;
}
}
// 检查条件匹配
self.condition.evaluate(result)
}
/// 检查规则是否匹配系统健康状态
pub fn matches_system(&self, status: &SystemHealthStatus) -> bool {
if !self.enabled {
return false;
}
// 如果指定了组件,检查该组件
if let Some(ref component) = self.component {
if let Some(component_result) = status.get_component_status(component) {
return self.condition.evaluate(component_result);
}
return false;
}
// 否则检查整体状态
self.condition.evaluate_system(status)
}
}
/// 告警条件
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AlertCondition {
/// 健康状态等于指定状态
HealthStatusEquals(HealthStatus),
/// 健康状态不等于指定状态
HealthStatusNotEquals(HealthStatus),
/// 响应时间超过阈值(毫秒)
ResponseTimeExceeds(u64),
/// 连续失败次数超过阈值
ConsecutiveFailuresExceeds(u32),
/// 错误率超过阈值(百分比)
ErrorRateExceeds(f64),
/// 自定义条件(使用详细信息中的键值对)
DetailContains(String, String),
/// 详细信息中的数值超过阈值
DetailValueExceeds(String, f64),
/// 组合条件AND
And(Vec<AlertCondition>),
/// 组合条件OR
Or(Vec<AlertCondition>),
}
impl AlertCondition {
/// 评估条件是否满足
pub fn evaluate(&self, result: &HealthCheckResult) -> bool {
match self {
AlertCondition::HealthStatusEquals(status) => result.status == *status,
AlertCondition::HealthStatusNotEquals(status) => result.status != *status,
AlertCondition::ResponseTimeExceeds(threshold) => result.response_time_ms > *threshold,
AlertCondition::DetailContains(key, value) => result.details.get(key) == Some(value),
AlertCondition::DetailValueExceeds(key, threshold) => result
.details
.get(key)
.and_then(|v| v.parse::<f64>().ok())
.is_some_and(|v| v > *threshold),
AlertCondition::And(conditions) => conditions.iter().all(|c| c.evaluate(result)),
AlertCondition::Or(conditions) => conditions.iter().any(|c| c.evaluate(result)),
// 这些条件需要历史数据暂时返回false
AlertCondition::ConsecutiveFailuresExceeds(_) => false,
AlertCondition::ErrorRateExceeds(_) => false,
}
}
/// 评估系统级条件
pub fn evaluate_system(&self, status: &SystemHealthStatus) -> bool {
match self {
AlertCondition::HealthStatusEquals(health_status) => {
status.overall_status == *health_status
}
AlertCondition::HealthStatusNotEquals(health_status) => {
status.overall_status != *health_status
}
AlertCondition::ErrorRateExceeds(threshold) => {
let total = status.components.len() as f64;
if total == 0.0 {
return false;
}
let error_rate = (status.unhealthy_count as f64 / total) * 100.0;
error_rate > *threshold
}
AlertCondition::And(conditions) => conditions.iter().all(|c| c.evaluate_system(status)),
AlertCondition::Or(conditions) => conditions.iter().any(|c| c.evaluate_system(status)),
_ => false, // 其他条件不适用于系统级评估
}
}
}
/// 告警事件
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertEvent {
pub id: String,
pub rule_id: String,
pub rule_name: String,
pub level: AlertLevel,
pub component: Option<String>,
pub message: String,
pub details: HashMap<String, String>,
pub timestamp: u64,
pub resolved: bool,
pub resolved_at: Option<u64>,
}
impl AlertEvent {
pub fn new(
rule: &AlertRule,
component: Option<String>,
message: String,
details: HashMap<String, String>,
) -> Self {
Self {
id: Uuid::new_v4().to_string(),
rule_id: rule.id.clone(),
rule_name: rule.name.clone(),
level: rule.level.clone(),
component,
message,
details,
timestamp: SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
resolved: false,
resolved_at: None,
}
}
pub fn resolve(&mut self) {
self.resolved = true;
self.resolved_at = Some(
SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
);
}
pub fn is_active(&self) -> bool {
!self.resolved
}
pub fn duration(&self) -> Option<Duration> {
if let Some(resolved_at) = self.resolved_at {
Some(Duration::from_secs(resolved_at - self.timestamp))
} else {
Some(Duration::from_secs(
SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
- self.timestamp,
))
}
}
}
/// 告警通知器trait
#[async_trait::async_trait]
pub trait AlertNotifier: Send + Sync {
async fn send_alert(
&self,
event: &AlertEvent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
async fn send_resolution(
&self,
event: &AlertEvent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
fn name(&self) -> &str;
}
/// 日志告警通知器
pub struct LogAlertNotifier {
logger: Arc<StructuredLogger>,
}
impl LogAlertNotifier {
pub fn new(logger: Arc<StructuredLogger>) -> Self {
Self { logger }
}
}
#[async_trait::async_trait]
impl AlertNotifier for LogAlertNotifier {
async fn send_alert(
&self,
event: &AlertEvent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let log_level = match event.level {
AlertLevel::Info => LogLevel::Info,
AlertLevel::Warning => LogLevel::Warn,
AlertLevel::Critical => LogLevel::Error,
AlertLevel::Emergency => LogLevel::Error,
};
let mut fields = HashMap::new();
fields.insert("alert_id".to_string(), event.id.clone());
fields.insert("rule_id".to_string(), event.rule_id.clone());
fields.insert("rule_name".to_string(), event.rule_name.clone());
fields.insert("alert_level".to_string(), event.level.to_string());
if let Some(ref component) = event.component {
fields.insert("component".to_string(), component.clone());
}
for (k, v) in &event.details {
fields.insert(format!("detail_{k}"), v.clone());
}
let entry = crate::utils::logging::LogEntry::new(
log_level,
format!("ALERT: {}", event.message),
"alerting".to_string(),
)
.with_field("alert_id", &event.id)
.with_field("rule_id", &event.rule_id)
.with_field("rule_name", &event.rule_name)
.with_field("alert_level", event.level.to_string());
let mut final_entry = entry;
for (k, v) in &fields {
final_entry = final_entry.with_field(k, v);
}
self.logger.log(final_entry).await;
Ok(())
}
async fn send_resolution(
&self,
event: &AlertEvent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut fields = HashMap::new();
fields.insert("alert_id".to_string(), event.id.clone());
fields.insert("rule_id".to_string(), event.rule_id.clone());
fields.insert("rule_name".to_string(), event.rule_name.clone());
fields.insert("alert_level".to_string(), event.level.to_string());
if let Some(duration) = event.duration() {
fields.insert(
"duration_seconds".to_string(),
duration.as_secs().to_string(),
);
}
if let Some(ref component) = event.component {
fields.insert("component".to_string(), component.clone());
}
let entry = crate::utils::logging::LogEntry::new(
LogLevel::Info,
format!("ALERT RESOLVED: {}", event.message),
"alerting".to_string(),
)
.with_field("alert_id", &event.id)
.with_field("rule_id", &event.rule_id)
.with_field("rule_name", &event.rule_name)
.with_field("alert_level", event.level.to_string());
let mut final_entry = entry;
for (k, v) in &fields {
final_entry = final_entry.with_field(k, v);
}
self.logger.log(final_entry).await;
Ok(())
}
fn name(&self) -> &str {
"log"
}
}
/// 控制台告警通知器
#[derive(Debug)]
pub struct ConsoleAlertNotifier;
impl Default for ConsoleAlertNotifier {
fn default() -> Self {
Self::new()
}
}
impl ConsoleAlertNotifier {
pub fn new() -> Self {
Self
}
}
#[async_trait::async_trait]
impl AlertNotifier for ConsoleAlertNotifier {
async fn send_alert(
&self,
event: &AlertEvent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let color_code = match event.level {
AlertLevel::Info => "\x1b[36m", // 青色
AlertLevel::Warning => "\x1b[33m", // 黄色
AlertLevel::Critical => "\x1b[31m", // 红色
AlertLevel::Emergency => "\x1b[35m", // 紫色
};
let reset_code = "\x1b[0m";
println!(
"{}🚨 ALERT [{}] {}: {}{}\n Rule: {} ({})\n Component: {}\n Time: {}",
color_code,
event.level,
event.component.as_deref().unwrap_or("SYSTEM"),
event.message,
reset_code,
event.rule_name,
event.rule_id,
event.component.as_deref().unwrap_or("N/A"),
chrono::DateTime::from_timestamp(event.timestamp as i64, 0)
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S UTC").to_string())
.unwrap_or_else(|| "Unknown".to_string())
);
if !event.details.is_empty() {
println!(" Details:");
for (key, value) in &event.details {
println!(" {key}: {value}");
}
}
println!();
Ok(())
}
async fn send_resolution(
&self,
event: &AlertEvent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let duration_str = event
.duration()
.map(|d| format!("{:.1}s", d.as_secs_f64()))
.unwrap_or_else(|| "Unknown".to_string());
println!(
"\x1b[32m✅ ALERT RESOLVED [{}] {}: {}\x1b[0m\n Rule: {} ({})\n Duration: {}\n",
event.level,
event.component.as_deref().unwrap_or("SYSTEM"),
event.message,
event.rule_name,
event.rule_id,
duration_str
);
Ok(())
}
fn name(&self) -> &str {
"console"
}
}
/// Webhook告警通知器
#[derive(Debug)]
pub struct WebhookAlertNotifier {
webhook_url: String,
client: reqwest::Client,
}
impl WebhookAlertNotifier {
pub fn new(webhook_url: String) -> Self {
Self {
webhook_url,
client: reqwest::Client::new(),
}
}
}
#[async_trait::async_trait]
impl AlertNotifier for WebhookAlertNotifier {
async fn send_alert(
&self,
event: &AlertEvent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let payload = serde_json::json!({
"type": "alert",
"event": event
});
let response = self
.client
.post(&self.webhook_url)
.json(&payload)
.send()
.await?;
if !response.status().is_success() {
return Err(
format!("Webhook request failed with status: {}", response.status()).into(),
);
}
Ok(())
}
async fn send_resolution(
&self,
event: &AlertEvent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let payload = serde_json::json!({
"type": "resolution",
"event": event
});
let response = self
.client
.post(&self.webhook_url)
.json(&payload)
.send()
.await?;
if !response.status().is_success() {
return Err(
format!("Webhook request failed with status: {}", response.status()).into(),
);
}
Ok(())
}
fn name(&self) -> &str {
"webhook"
}
}
/// 告警历史记录
#[derive(Debug)]
struct AlertHistory {
_rule_id: String,
last_triggered: Option<Instant>,
consecutive_failures: u32,
total_triggers: u64,
}
impl AlertHistory {
fn new(rule_id: String) -> Self {
Self {
_rule_id: rule_id,
last_triggered: None,
consecutive_failures: 0,
total_triggers: 0,
}
}
fn can_trigger(&self, cooldown: Duration) -> bool {
match self.last_triggered {
Some(last) => last.elapsed() >= cooldown,
None => true,
}
}
fn record_trigger(&mut self) {
self.last_triggered = Some(Instant::now());
self.consecutive_failures += 1;
self.total_triggers += 1;
}
#[allow(dead_code)]
fn reset_failures(&mut self) {
self.consecutive_failures = 0;
}
}
/// 告警管理器
pub struct AlertManager {
rules: Arc<RwLock<HashMap<String, AlertRule>>>,
notifiers: Arc<RwLock<Vec<Arc<dyn AlertNotifier>>>>,
active_alerts: Arc<RwLock<HashMap<String, AlertEvent>>>,
alert_history: Arc<RwLock<HashMap<String, AlertHistory>>>,
event_sender: mpsc::UnboundedSender<AlertEvent>,
event_receiver: Arc<RwLock<Option<mpsc::UnboundedReceiver<AlertEvent>>>>,
}
impl AlertManager {
pub fn new() -> Self {
let (event_sender, event_receiver) = mpsc::unbounded_channel();
Self {
rules: Arc::new(RwLock::new(HashMap::new())),
notifiers: Arc::new(RwLock::new(Vec::new())),
active_alerts: Arc::new(RwLock::new(HashMap::new())),
alert_history: Arc::new(RwLock::new(HashMap::new())),
event_sender,
event_receiver: Arc::new(RwLock::new(Some(event_receiver))),
}
}
/// 添加告警规则
pub async fn add_rule(&self, rule: AlertRule) {
let mut rules = self.rules.write().await;
rules.insert(rule.id.clone(), rule);
}
/// 移除告警规则
pub async fn remove_rule(&self, rule_id: &str) -> bool {
let mut rules = self.rules.write().await;
rules.remove(rule_id).is_some()
}
/// 获取告警规则
pub async fn get_rule(&self, rule_id: &str) -> Option<AlertRule> {
let rules = self.rules.read().await;
rules.get(rule_id).cloned()
}
/// 获取所有告警规则
pub async fn get_all_rules(&self) -> Vec<AlertRule> {
let rules = self.rules.read().await;
rules.values().cloned().collect()
}
/// 添加通知器
pub async fn add_notifier(&self, notifier: Arc<dyn AlertNotifier>) {
let mut notifiers = self.notifiers.write().await;
notifiers.push(notifier);
}
/// 处理健康检查结果
pub async fn process_health_check(&self, result: &HealthCheckResult) {
let rules = self.rules.read().await;
for rule in rules.values() {
if rule.matches(result) {
self.trigger_alert(
rule,
Some(result.component.clone()),
&result.message,
result.details.clone(),
)
.await;
}
}
}
/// 处理系统健康状态
pub async fn process_system_health(&self, status: &SystemHealthStatus) {
let rules = self.rules.read().await;
for rule in rules.values() {
if rule.matches_system(status) {
let message = format!(
"System health issue: {} healthy, {} degraded, {} unhealthy",
status.healthy_count, status.degraded_count, status.unhealthy_count
);
let mut details = HashMap::new();
details.insert(
"overall_status".to_string(),
status.overall_status.to_string(),
);
details.insert(
"healthy_count".to_string(),
status.healthy_count.to_string(),
);
details.insert(
"degraded_count".to_string(),
status.degraded_count.to_string(),
);
details.insert(
"unhealthy_count".to_string(),
status.unhealthy_count.to_string(),
);
details.insert(
"total_response_time_ms".to_string(),
status.total_response_time_ms.to_string(),
);
self.trigger_alert(rule, None, &message, details).await;
}
}
}
/// 触发告警
async fn trigger_alert(
&self,
rule: &AlertRule,
component: Option<String>,
message: &str,
details: HashMap<String, String>,
) {
// 检查冷却时间
{
let mut history = self.alert_history.write().await;
let alert_history = history
.entry(rule.id.clone())
.or_insert_with(|| AlertHistory::new(rule.id.clone()));
if !alert_history.can_trigger(rule.cooldown) {
return;
}
alert_history.record_trigger();
}
// 创建告警事件
let event = AlertEvent::new(rule, component, message.to_string(), details);
// 存储活跃告警
{
let mut active_alerts = self.active_alerts.write().await;
active_alerts.insert(event.id.clone(), event.clone());
}
// 发送事件
if let Err(e) = self.event_sender.send(event) {
eprintln!("Failed to send alert event: {e}");
}
}
/// 解决告警
pub async fn resolve_alert(&self, alert_id: &str) -> bool {
let mut active_alerts = self.active_alerts.write().await;
if let Some(mut event) = active_alerts.remove(alert_id) {
event.resolve();
// 发送解决事件
if let Err(e) = self.event_sender.send(event) {
eprintln!("Failed to send alert resolution event: {e}");
}
true
} else {
false
}
}
/// 获取活跃告警
pub async fn get_active_alerts(&self) -> Vec<AlertEvent> {
let active_alerts = self.active_alerts.read().await;
active_alerts.values().cloned().collect()
}
/// 获取告警统计
pub async fn get_alert_stats(&self) -> AlertStats {
let active_alerts = self.active_alerts.read().await;
let history = self.alert_history.read().await;
let total_active = active_alerts.len();
let critical_count = active_alerts
.values()
.filter(|a| matches!(a.level, AlertLevel::Critical | AlertLevel::Emergency))
.count();
let total_triggered = history.values().map(|h| h.total_triggers).sum();
AlertStats {
total_active,
critical_count,
total_triggered,
rules_count: self.rules.read().await.len(),
notifiers_count: self.notifiers.read().await.len(),
}
}
/// 启动事件处理器
pub async fn start_event_processor(&self) {
let notifiers = self.notifiers.clone();
let receiver = {
let mut event_receiver = self.event_receiver.write().await;
event_receiver.take()
};
if let Some(mut receiver) = receiver {
tokio::spawn(async move {
while let Some(event) = receiver.recv().await {
let notifiers_guard = notifiers.read().await;
for notifier in notifiers_guard.iter() {
let result = if event.resolved {
notifier.send_resolution(&event).await
} else {
notifier.send_alert(&event).await
};
if let Err(e) = result {
eprintln!("Failed to send notification via {}: {}", notifier.name(), e);
}
}
}
});
}
}
/// 创建默认规则
pub async fn create_default_rules(&self) {
// 组件不健康规则
let unhealthy_rule = AlertRule::new(
"component_unhealthy".to_string(),
"Component Unhealthy".to_string(),
"Triggered when a component becomes unhealthy".to_string(),
AlertCondition::HealthStatusEquals(HealthStatus::Unhealthy),
AlertLevel::Critical,
);
self.add_rule(unhealthy_rule).await;
// 组件降级规则
let degraded_rule = AlertRule::new(
"component_degraded".to_string(),
"Component Degraded".to_string(),
"Triggered when a component becomes degraded".to_string(),
AlertCondition::HealthStatusEquals(HealthStatus::Degraded),
AlertLevel::Warning,
);
self.add_rule(degraded_rule).await;
// 响应时间过长规则
let slow_response_rule = AlertRule::new(
"slow_response".to_string(),
"Slow Response Time".to_string(),
"Triggered when response time exceeds 30 seconds".to_string(),
AlertCondition::ResponseTimeExceeds(30000),
AlertLevel::Warning,
);
self.add_rule(slow_response_rule).await;
// 系统错误率过高规则
let high_error_rate_rule = AlertRule::new(
"high_error_rate".to_string(),
"High Error Rate".to_string(),
"Triggered when error rate exceeds 50%".to_string(),
AlertCondition::ErrorRateExceeds(50.0),
AlertLevel::Critical,
);
self.add_rule(high_error_rate_rule).await;
}
}
impl Default for AlertManager {
fn default() -> Self {
Self::new()
}
}
/// 告警统计信息
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertStats {
pub total_active: usize,
pub critical_count: usize,
pub total_triggered: u64,
pub rules_count: usize,
pub notifiers_count: usize,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::health_check::HealthCheckResult;
use std::sync::atomic::{AtomicUsize, Ordering};
struct MockNotifier {
name: String,
alert_count: Arc<AtomicUsize>,
resolution_count: Arc<AtomicUsize>,
}
impl MockNotifier {
fn new(name: String) -> Self {
Self {
name,
alert_count: Arc::new(AtomicUsize::new(0)),
resolution_count: Arc::new(AtomicUsize::new(0)),
}
}
fn get_alert_count(&self) -> usize {
self.alert_count.load(Ordering::Relaxed)
}
fn get_resolution_count(&self) -> usize {
self.resolution_count.load(Ordering::Relaxed)
}
}
#[async_trait::async_trait]
impl AlertNotifier for MockNotifier {
async fn send_alert(
&self,
_event: &AlertEvent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.alert_count.fetch_add(1, Ordering::Relaxed);
Ok(())
}
async fn send_resolution(
&self,
_event: &AlertEvent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.resolution_count.fetch_add(1, Ordering::Relaxed);
Ok(())
}
fn name(&self) -> &str {
&self.name
}
}
#[tokio::test]
async fn test_alert_rule_matching() {
let rule = AlertRule::new(
"test_rule".to_string(),
"Test Rule".to_string(),
"Test description".to_string(),
AlertCondition::HealthStatusEquals(HealthStatus::Unhealthy),
AlertLevel::Critical,
);
let unhealthy_result = HealthCheckResult::new(
"test_component".to_string(),
HealthStatus::Unhealthy,
"Test message".to_string(),
);
let healthy_result = HealthCheckResult::new(
"test_component".to_string(),
HealthStatus::Healthy,
"Test message".to_string(),
);
assert!(rule.matches(&unhealthy_result));
assert!(!rule.matches(&healthy_result));
}
#[tokio::test]
async fn test_alert_condition_evaluation() {
let condition = AlertCondition::And(vec![
AlertCondition::HealthStatusEquals(HealthStatus::Unhealthy),
AlertCondition::ResponseTimeExceeds(1000),
]);
let mut result = HealthCheckResult::new(
"test_component".to_string(),
HealthStatus::Unhealthy,
"Test message".to_string(),
);
result.response_time_ms = 2000;
assert!(condition.evaluate(&result));
result.response_time_ms = 500;
assert!(!condition.evaluate(&result));
}
#[tokio::test]
async fn test_alert_manager() {
let manager = AlertManager::new();
let notifier = Arc::new(MockNotifier::new("test".to_string()));
manager.add_notifier(notifier.clone()).await;
manager.start_event_processor().await;
let rule = AlertRule::new(
"test_rule".to_string(),
"Test Rule".to_string(),
"Test description".to_string(),
AlertCondition::HealthStatusEquals(HealthStatus::Unhealthy),
AlertLevel::Critical,
)
.with_cooldown(Duration::from_millis(100));
manager.add_rule(rule).await;
let unhealthy_result = HealthCheckResult::new(
"test_component".to_string(),
HealthStatus::Unhealthy,
"Test message".to_string(),
);
manager.process_health_check(&unhealthy_result).await;
// 等待事件处理
tokio::time::sleep(Duration::from_millis(50)).await;
assert_eq!(notifier.get_alert_count(), 1);
assert_eq!(manager.get_active_alerts().await.len(), 1);
// 测试冷却时间
manager.process_health_check(&unhealthy_result).await;
tokio::time::sleep(Duration::from_millis(50)).await;
assert_eq!(notifier.get_alert_count(), 1); // 应该还是1因为冷却时间
// 等待冷却时间过去
tokio::time::sleep(Duration::from_millis(100)).await;
manager.process_health_check(&unhealthy_result).await;
tokio::time::sleep(Duration::from_millis(50)).await;
assert_eq!(notifier.get_alert_count(), 2); // 现在应该是2
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
use crate::error::AppError;
use regex::Regex;
use std::path::Path;
/// 检查文件是否存在
pub fn file_exists(file_path: &str) -> bool {
Path::new(file_path).exists()
}
/// 获取文件大小
pub fn get_file_size(file_path: &str) -> Result<u64, AppError> {
let metadata = std::fs::metadata(file_path)
.map_err(|e| AppError::File(format!("无法获取文件元数据: {e}")))?;
Ok(metadata.len())
}
/// 获取文件扩展名
pub fn get_file_extension(file_path: &str) -> Option<String> {
Path::new(file_path)
.extension()
.and_then(|ext| ext.to_str())
.map(|s| s.to_lowercase())
}
/// 创建临时目录
pub fn create_temp_dir(dir_path: &str) -> Result<(), AppError> {
if !Path::new(dir_path).exists() {
std::fs::create_dir_all(dir_path)
.map_err(|e| AppError::File(format!("无法创建临时目录: {e}")))?
}
Ok(())
}
/// 验证文件大小
pub fn validate_file_size(file_size: u64, max_size: u64) -> Result<(), AppError> {
if file_size > max_size {
return Err(AppError::Validation(format!(
"文件大小超过限制: {file_size} > {max_size} 字节"
)));
}
Ok(())
}
/// 清理文件名,移除不安全字符
pub fn sanitize_filename(filename: &str) -> Result<String, AppError> {
if filename.is_empty() {
return Err(AppError::Validation("文件名不能为空".to_string()));
}
// 移除路径分隔符和其他不安全字符
let unsafe_chars = Regex::new(r#"[<>:"/\\|?*\x00-\x1f]"#).unwrap();
let sanitized = unsafe_chars.replace_all(filename, "_").to_string();
// 移除开头和结尾的点和空格
let sanitized = sanitized.trim_matches(|c| c == '.' || c == ' ').to_string();
if sanitized.is_empty() {
return Err(AppError::Validation("清理后的文件名为空".to_string()));
}
// 限制文件名长度
if sanitized.len() > 255 {
let truncated = sanitized.chars().take(255).collect::<String>();
Ok(truncated)
} else {
Ok(sanitized)
}
}

View File

@@ -0,0 +1,36 @@
use crate::error::AppError;
use crate::models::DocumentFormat;
/// 从文件路径检测文档格式
pub fn detect_format_from_path(file_path: &str) -> Result<DocumentFormat, AppError> {
let extension = super::file_utils::get_file_extension(file_path)
.ok_or_else(|| AppError::UnsupportedFormat("无法识别文件扩展名".to_string()))?;
Ok(DocumentFormat::from_extension(&extension))
}
/// 从MIME类型检测文档格式
pub fn detect_format_from_mime(mime_type: &str) -> DocumentFormat {
DocumentFormat::from_mime_type(mime_type)
}
/// 检查格式是否支持
pub fn is_format_supported(format: &DocumentFormat) -> bool {
format.is_supported()
}
/// 获取支持格式列表
pub fn get_supported_formats() -> Vec<DocumentFormat> {
vec![
DocumentFormat::PDF,
DocumentFormat::Word,
DocumentFormat::Excel,
DocumentFormat::PowerPoint,
DocumentFormat::Image,
DocumentFormat::Audio,
DocumentFormat::HTML,
DocumentFormat::Text,
DocumentFormat::Txt,
DocumentFormat::Md,
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,861 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::RwLock;
use tracing::{info, instrument, warn};
use tracing_subscriber::EnvFilter;
use uuid::Uuid;
/// 日志级别
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum LogLevel {
Trace = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
}
impl std::fmt::Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LogLevel::Trace => write!(f, "TRACE"),
LogLevel::Debug => write!(f, "DEBUG"),
LogLevel::Info => write!(f, "INFO"),
LogLevel::Warn => write!(f, "WARN"),
LogLevel::Error => write!(f, "ERROR"),
}
}
}
impl From<&str> for LogLevel {
fn from(s: &str) -> Self {
match s.to_uppercase().as_str() {
"TRACE" => LogLevel::Trace,
"DEBUG" => LogLevel::Debug,
"INFO" => LogLevel::Info,
"WARN" => LogLevel::Warn,
"ERROR" => LogLevel::Error,
_ => LogLevel::Info,
}
}
}
/// 关联ID管理器
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CorrelationContext {
pub request_id: Option<String>,
pub task_id: Option<String>,
pub user_id: Option<String>,
pub session_id: Option<String>,
pub trace_id: Option<String>,
pub span_id: Option<String>,
}
impl CorrelationContext {
pub fn new() -> Self {
Self::default()
}
pub fn with_request_id(mut self, request_id: String) -> Self {
self.request_id = Some(request_id);
self
}
pub fn with_task_id(mut self, task_id: String) -> Self {
self.task_id = Some(task_id);
self
}
pub fn with_user_id(mut self, user_id: String) -> Self {
self.user_id = Some(user_id);
self
}
pub fn with_session_id(mut self, session_id: String) -> Self {
self.session_id = Some(session_id);
self
}
pub fn with_trace_id(mut self, trace_id: String) -> Self {
self.trace_id = Some(trace_id);
self
}
pub fn with_span_id(mut self, span_id: String) -> Self {
self.span_id = Some(span_id);
self
}
pub fn generate_request_id(&mut self) -> String {
let id = Uuid::new_v4().to_string();
self.request_id = Some(id.clone());
id
}
pub fn generate_trace_id(&mut self) -> String {
let id = Uuid::new_v4().to_string();
self.trace_id = Some(id.clone());
id
}
pub fn to_fields(&self) -> HashMap<String, String> {
let mut fields = HashMap::new();
if let Some(ref request_id) = self.request_id {
fields.insert("request_id".to_string(), request_id.clone());
}
if let Some(ref task_id) = self.task_id {
fields.insert("task_id".to_string(), task_id.clone());
}
if let Some(ref user_id) = self.user_id {
fields.insert("user_id".to_string(), user_id.clone());
}
if let Some(ref session_id) = self.session_id {
fields.insert("session_id".to_string(), session_id.clone());
}
if let Some(ref trace_id) = self.trace_id {
fields.insert("trace_id".to_string(), trace_id.clone());
}
if let Some(ref span_id) = self.span_id {
fields.insert("span_id".to_string(), span_id.clone());
}
fields
}
}
/// 结构化日志条目
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEntry {
pub id: String,
pub timestamp: SystemTime,
pub level: LogLevel,
pub message: String,
pub module: Option<String>,
pub file: Option<String>,
pub line: Option<u32>,
pub target: String,
pub fields: HashMap<String, serde_json::Value>,
pub correlation: CorrelationContext,
pub service_name: String,
pub service_version: String,
pub environment: String,
}
impl LogEntry {
pub fn new(level: LogLevel, message: String, target: String) -> Self {
Self {
id: Uuid::new_v4().to_string(),
timestamp: SystemTime::now(),
level,
message,
module: None,
file: None,
line: None,
target,
fields: HashMap::new(),
correlation: CorrelationContext::default(),
service_name: "document-parser".to_string(),
service_version: env!("CARGO_PKG_VERSION").to_string(),
environment: std::env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string()),
}
}
/// 添加字段
pub fn with_field<T: Serialize>(mut self, key: &str, value: T) -> Self {
if let Ok(json_value) = serde_json::to_value(value) {
self.fields.insert(key.to_string(), json_value);
}
self
}
/// 设置关联上下文
pub fn with_correlation(mut self, correlation: CorrelationContext) -> Self {
self.correlation = correlation;
self
}
/// 设置服务信息
pub fn with_service_info(mut self, name: String, version: String, environment: String) -> Self {
self.service_name = name;
self.service_version = version;
self.environment = environment;
self
}
/// 设置源码位置
pub fn with_location(mut self, module: String, file: String, line: u32) -> Self {
self.module = Some(module);
self.file = Some(file);
self.line = Some(line);
self
}
/// 脱敏处理
pub fn sanitize(&mut self) {
// 脱敏消息中的敏感信息
self.message = self.sanitize_string(&self.message);
// 脱敏字段中的敏感信息
let mut sanitized_fields = HashMap::new();
for (key, value) in &self.fields {
let sanitized_value = match value {
serde_json::Value::String(s) => serde_json::Value::String(self.sanitize_string(s)),
_ => value.clone(),
};
sanitized_fields.insert(key.clone(), sanitized_value);
}
self.fields = sanitized_fields;
}
/// 脱敏字符串
fn sanitize_string(&self, input: &str) -> String {
let mut result = input.to_string();
// 脱敏常见的敏感信息模式
let patterns = vec![
(r"password[\s]*[:=][\s]*[\S]+", "password: ***"),
(r"token[\s]*[:=][\s]*[\S]+", "token: ***"),
(r"key[\s]*[:=][\s]*[\S]+", "key: ***"),
(r"secret[\s]*[:=][\s]*[\S]+", "secret: ***"),
(
r"\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b",
"****-****-****-****",
), // 信用卡号
(r"\b\d{3}-\d{2}-\d{4}\b", "***-**-****"), // SSN
];
for (pattern, replacement) in patterns {
if let Ok(re) = regex::Regex::new(pattern) {
result = re.replace_all(&result, replacement).to_string();
}
}
result
}
/// 格式化为JSON
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
/// 格式化为人类可读格式
pub fn to_human_readable(&self) -> String {
let timestamp = self
.timestamp
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let location = if let (Some(file), Some(line)) = (&self.file, self.line) {
format!(" [{file}:{line}]")
} else {
String::new()
};
let context = if !self.fields.is_empty() {
format!(
" {}",
serde_json::to_string(&self.fields).unwrap_or_default()
)
} else {
String::new()
};
format!(
"{} [{}] {}{}: {}{}",
timestamp, self.level, self.target, location, self.message, context
)
}
}
/// 日志输出器trait
#[async_trait::async_trait]
pub trait LogOutput: Send + Sync {
async fn write_log(
&self,
entry: &LogEntry,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
async fn flush(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
}
/// 控制台日志输出器
pub struct ConsoleOutput {
use_json: bool,
}
impl ConsoleOutput {
pub fn new(use_json: bool) -> Self {
Self { use_json }
}
}
#[async_trait::async_trait]
impl LogOutput for ConsoleOutput {
async fn write_log(
&self,
entry: &LogEntry,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let output = if self.use_json {
entry.to_json()?
} else {
entry.to_human_readable()
};
println!("{}", output);
Ok(())
}
async fn flush(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use std::io::{self, Write};
io::stdout().flush()?;
Ok(())
}
}
/// 文件日志输出器
pub struct FileOutput {
file_path: String,
use_json: bool,
max_file_size: u64,
max_files: usize,
}
impl FileOutput {
pub fn new(file_path: String, use_json: bool, max_file_size: u64, max_files: usize) -> Self {
Self {
file_path,
use_json,
max_file_size,
max_files,
}
}
/// 检查并轮转日志文件
async fn rotate_if_needed(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use std::fs;
if let Ok(metadata) = fs::metadata(&self.file_path) {
if metadata.len() > self.max_file_size {
// 轮转日志文件
for i in (1..self.max_files).rev() {
let old_file = format!("{}.{}", self.file_path, i);
let new_file = format!("{}.{}", self.file_path, i + 1);
if fs::metadata(&old_file).is_ok() {
fs::rename(&old_file, &new_file)?;
}
}
// 移动当前文件
let backup_file = format!("{}.1", self.file_path);
fs::rename(&self.file_path, &backup_file)?;
}
}
Ok(())
}
}
#[async_trait::async_trait]
impl LogOutput for FileOutput {
async fn write_log(
&self,
entry: &LogEntry,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use std::fs::OpenOptions;
use std::io::Write;
self.rotate_if_needed().await?;
let output = if self.use_json {
format!("{}\n", entry.to_json()?)
} else {
format!("{}\n", entry.to_human_readable())
};
let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(&self.file_path)?;
file.write_all(output.as_bytes())?;
file.flush()?;
Ok(())
}
async fn flush(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// 文件输出器在每次写入时都会刷新
Ok(())
}
}
/// 日志配置
#[derive(Debug, Clone)]
pub struct LoggingConfig {
pub level: String,
pub format: LogFormat,
pub output: LogOutputTarget,
pub file_path: Option<String>,
pub max_file_size: u64,
pub max_files: usize,
pub enable_console: bool,
pub enable_json: bool,
pub enable_correlation: bool,
pub service_name: String,
pub service_version: String,
pub environment: String,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
level: "info".to_string(),
format: LogFormat::Human,
output: LogOutputTarget::Console,
file_path: None,
max_file_size: 100 * 1024 * 1024, // 100MB
max_files: 10,
enable_console: true,
enable_json: false,
enable_correlation: true,
service_name: "document-parser".to_string(),
service_version: env!("CARGO_PKG_VERSION").to_string(),
environment: std::env::var("ENVIRONMENT").unwrap_or_else(|_| "development".to_string()),
}
}
}
/// 日志格式
#[derive(Debug, Clone, PartialEq)]
pub enum LogFormat {
Human,
Json,
Compact,
}
/// 日志输出目标
#[derive(Debug, Clone, PartialEq)]
pub enum LogOutputTarget {
Console,
File,
Both,
}
/// 增强的日志系统
pub struct EnhancedLoggingSystem {
config: LoggingConfig,
correlation_context: Arc<RwLock<CorrelationContext>>,
_guards: Vec<tracing_appender::non_blocking::WorkerGuard>,
}
impl EnhancedLoggingSystem {
/// 初始化日志系统
#[instrument(skip(config))]
pub fn init(config: LoggingConfig) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let guards = Vec::new();
// 设置环境过滤器
let env_filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new(&config.level))
.unwrap_or_else(|_| EnvFilter::new("info"));
// 控制台输出层
if config.enable_console
&& (config.output == LogOutputTarget::Console || config.output == LogOutputTarget::Both)
{
// 简化的控制台层配置
// 在实际实现中,这里需要更复杂的配置
}
// 文件输出层
if let Some(ref _file_path) = config.file_path {
if config.output == LogOutputTarget::File || config.output == LogOutputTarget::Both {
// 简化的文件层配置
// 在实际实现中,这里需要更复杂的配置
}
}
// 简化的订阅者初始化
tracing_subscriber::fmt()
.with_env_filter(env_filter)
.with_target(true)
.with_thread_ids(true)
.with_file(true)
.with_line_number(true)
.init();
info!(
service_name = %config.service_name,
service_version = %config.service_version,
environment = %config.environment,
log_level = %config.level,
"Logging system initialized"
);
Ok(Self {
config,
correlation_context: Arc::new(RwLock::new(CorrelationContext::default())),
_guards: guards,
})
}
/// 设置关联上下文
pub async fn set_correlation_context(&self, context: CorrelationContext) {
let mut correlation = self.correlation_context.write().await;
*correlation = context;
}
/// 获取关联上下文
pub async fn get_correlation_context(&self) -> CorrelationContext {
let correlation = self.correlation_context.read().await;
correlation.clone()
}
/// 生成新的请求ID
pub async fn generate_request_id(&self) -> String {
let mut correlation = self.correlation_context.write().await;
correlation.generate_request_id()
}
/// 生成新的跟踪ID
pub async fn generate_trace_id(&self) -> String {
let mut correlation = self.correlation_context.write().await;
correlation.generate_trace_id()
}
/// 创建带有关联上下文的span
pub async fn create_span(&self, name: &str) -> tracing::Span {
let correlation = self.correlation_context.read().await;
let fields = correlation.to_fields();
let span = tracing::info_span!(
"custom_span",
name = name,
service_name = %self.config.service_name,
service_version = %self.config.service_version,
environment = %self.config.environment,
);
// 添加关联字段
for (key, value) in fields {
span.record(key.as_str(), tracing::field::display(&value));
}
span
}
/// 获取配置
pub fn config(&self) -> &LoggingConfig {
&self.config
}
}
/// 结构化日志记录器(保持向后兼容)
pub struct StructuredLogger {
min_level: LogLevel,
outputs: Vec<Arc<dyn LogOutput>>,
context: Arc<RwLock<HashMap<String, serde_json::Value>>>,
}
impl StructuredLogger {
/// 创建新的结构化日志器
pub fn new(min_level: LogLevel) -> Self {
Self {
min_level,
outputs: Vec::new(),
context: Arc::new(RwLock::new(HashMap::new())),
}
}
/// 添加输出器
pub fn add_output(mut self, output: Arc<dyn LogOutput>) -> Self {
self.outputs.push(output);
self
}
/// 设置全局上下文
pub async fn set_context<T: Serialize>(&self, key: &str, value: T) {
if let Ok(json_value) = serde_json::to_value(value) {
let mut context = self.context.write().await;
context.insert(key.to_string(), json_value);
}
}
/// 移除全局上下文
pub async fn remove_context(&self, key: &str) {
let mut context = self.context.write().await;
context.remove(key);
}
/// 记录日志
pub async fn log(&self, mut entry: LogEntry) {
if entry.level < self.min_level {
return;
}
// 添加全局上下文
{
let context = self.context.read().await;
for (key, value) in context.iter() {
entry.fields.insert(key.clone(), value.clone());
}
}
// 脱敏处理
entry.sanitize();
// 输出到所有输出器
for output in &self.outputs {
if let Err(e) = output.write_log(&entry).await {
eprintln!("Log output error: {e}");
}
}
}
/// 刷新所有输出器
pub async fn flush(&self) {
for output in &self.outputs {
if let Err(e) = output.flush().await {
eprintln!("Log refresh error: {e}");
}
}
}
/// 便捷方法记录trace级别日志
pub async fn trace(&self, message: &str, target: &str) {
let entry = LogEntry::new(LogLevel::Trace, message.to_string(), target.to_string());
self.log(entry).await;
}
/// 便捷方法记录debug级别日志
pub async fn debug(&self, message: &str, target: &str) {
let entry = LogEntry::new(LogLevel::Debug, message.to_string(), target.to_string());
self.log(entry).await;
}
/// 便捷方法记录info级别日志
pub async fn info(&self, message: &str, target: &str) {
let entry = LogEntry::new(LogLevel::Info, message.to_string(), target.to_string());
self.log(entry).await;
}
/// 便捷方法记录warn级别日志
pub async fn warn(&self, message: &str, target: &str) {
let entry = LogEntry::new(LogLevel::Warn, message.to_string(), target.to_string());
self.log(entry).await;
}
/// 便捷方法记录error级别日志
pub async fn error(&self, message: &str, target: &str) {
let entry = LogEntry::new(LogLevel::Error, message.to_string(), target.to_string());
self.log(entry).await;
}
}
/// 日志宏
#[macro_export]
macro_rules! structured_log {
($logger:expr, $level:expr, $message:expr) => {
{
let entry = $crate::utils::logging::LogEntry::new(
$level,
$message.to_string(),
module_path!().to_string(),
).with_location(
module_path!().to_string(),
file!().to_string(),
line!(),
);
$logger.log(entry).await;
}
};
($logger:expr, $level:expr, $message:expr, $($key:expr => $value:expr),+) => {
{
let mut entry = $crate::utils::logging::LogEntry::new(
$level,
$message.to_string(),
module_path!().to_string(),
).with_location(
module_path!().to_string(),
file!().to_string(),
line!(),
);
$(
entry = entry.with_field($key, $value);
)+
$logger.log(entry).await;
}
};
}
/// 便捷宏
#[macro_export]
macro_rules! log_trace {
($logger:expr, $message:expr) => {
structured_log!($logger, $crate::utils::logging::LogLevel::Trace, $message)
};
($logger:expr, $message:expr, $($key:expr => $value:expr),+) => {
structured_log!($logger, $crate::utils::logging::LogLevel::Trace, $message, $($key => $value),+)
};
}
#[macro_export]
macro_rules! log_debug {
($logger:expr, $message:expr) => {
structured_log!($logger, $crate::utils::logging::LogLevel::Debug, $message)
};
($logger:expr, $message:expr, $($key:expr => $value:expr),+) => {
structured_log!($logger, $crate::utils::logging::LogLevel::Debug, $message, $($key => $value),+)
};
}
#[macro_export]
macro_rules! log_info {
($logger:expr, $message:expr) => {
structured_log!($logger, $crate::utils::logging::LogLevel::Info, $message)
};
($logger:expr, $message:expr, $($key:expr => $value:expr),+) => {
structured_log!($logger, $crate::utils::logging::LogLevel::Info, $message, $($key => $value),+)
};
}
#[macro_export]
macro_rules! log_warn {
($logger:expr, $message:expr) => {
structured_log!($logger, $crate::utils::logging::LogLevel::Warn, $message)
};
($logger:expr, $message:expr, $($key:expr => $value:expr),+) => {
structured_log!($logger, $crate::utils::logging::LogLevel::Warn, $message, $($key => $value),+)
};
}
#[macro_export]
macro_rules! log_error {
($logger:expr, $message:expr) => {
structured_log!($logger, $crate::utils::logging::LogLevel::Error, $message)
};
($logger:expr, $message:expr, $($key:expr => $value:expr),+) => {
structured_log!($logger, $crate::utils::logging::LogLevel::Error, $message, $($key => $value),+)
};
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
struct TestOutput {
entries: Arc<Mutex<Vec<LogEntry>>>,
write_count: Arc<AtomicUsize>,
}
impl TestOutput {
fn new() -> Self {
Self {
entries: Arc::new(Mutex::new(Vec::new())),
write_count: Arc::new(AtomicUsize::new(0)),
}
}
fn get_entries(&self) -> Vec<LogEntry> {
self.entries.lock().unwrap().clone()
}
fn get_write_count(&self) -> usize {
self.write_count.load(Ordering::SeqCst)
}
}
#[async_trait::async_trait]
impl LogOutput for TestOutput {
async fn write_log(
&self,
entry: &LogEntry,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
self.entries.lock().unwrap().push(entry.clone());
self.write_count.fetch_add(1, Ordering::SeqCst);
Ok(())
}
async fn flush(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
}
#[tokio::test]
async fn test_structured_logger() {
let test_output = Arc::new(TestOutput::new());
let logger = StructuredLogger::new(LogLevel::Debug).add_output(test_output.clone());
// 测试基本日志记录
logger.info("测试消息", "test_module").await;
let entries = test_output.get_entries();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].level, LogLevel::Info);
assert_eq!(entries[0].message, "测试消息");
assert_eq!(entries[0].target, "test_module");
}
#[tokio::test]
async fn test_log_level_filtering() {
let test_output = Arc::new(TestOutput::new());
let logger = StructuredLogger::new(LogLevel::Warn).add_output(test_output.clone());
// 这些日志应该被过滤掉
logger.debug("debug消息", "test").await;
logger.info("info消息", "test").await;
// 这些日志应该被记录
logger.warn("warn消息", "test").await;
logger.error("error消息", "test").await;
let entries = test_output.get_entries();
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].level, LogLevel::Warn);
assert_eq!(entries[1].level, LogLevel::Error);
}
#[tokio::test]
async fn test_context() {
let test_output = Arc::new(TestOutput::new());
let logger = StructuredLogger::new(LogLevel::Debug).add_output(test_output.clone());
// 设置全局上下文
logger.set_context("service", "document-parser").await;
logger.set_context("version", "1.0.0").await;
logger.info("测试消息", "test").await;
let entries = test_output.get_entries();
assert_eq!(entries.len(), 1);
let entry = &entries[0];
assert!(entry.fields.contains_key("service"));
assert!(entry.fields.contains_key("version"));
}
#[test]
fn test_log_entry_sanitization() {
let mut entry = LogEntry::new(
LogLevel::Info,
"用户登录: password=secret123 token=abc123".to_string(),
"auth".to_string(),
);
entry.sanitize();
assert!(entry.message.contains("password: ***"));
assert!(entry.message.contains("token: ***"));
assert!(!entry.message.contains("secret123"));
assert!(!entry.message.contains("abc123"));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
// 工具模块
// TODO: 实现具体的工具函数
pub mod alerting;
pub mod environment_manager;
pub mod file_utils;
pub mod format_utils;
pub mod health_check;
pub mod logging;
pub mod metrics;
pub use environment_manager::{EnvironmentManager, EnvironmentStatus, InstallStage};
pub use alerting::*;
pub use file_utils::*;
pub use format_utils::*;
pub use health_check::*;
pub use metrics::*;