Skip to main content

code_analyze_mcp/
logging.rs

1//! MCP logging integration via tracing.
2//!
3//! Provides a custom tracing subscriber that forwards log events to MCP clients.
4//! Maps Rust tracing levels to MCP LoggingLevel.
5
6use rmcp::model::LoggingLevel;
7use serde_json::{Map, Value};
8use std::sync::{Arc, Mutex};
9use tokio::sync::mpsc;
10use tracing::subscriber::Interest;
11use tracing::{Level, Subscriber};
12use tracing_subscriber::Layer;
13use tracing_subscriber::filter::LevelFilter;
14use tracing_subscriber::layer::Context;
15
16/// Maps tracing::Level to MCP LoggingLevel.
17pub fn level_to_mcp(level: &Level) -> LoggingLevel {
18    match *level {
19        Level::TRACE | Level::DEBUG => LoggingLevel::Debug,
20        Level::INFO => LoggingLevel::Info,
21        Level::WARN => LoggingLevel::Warning,
22        Level::ERROR => LoggingLevel::Error,
23    }
24}
25
26/// Lightweight event sent from McpLoggingLayer to consumer task via unbounded channel.
27#[derive(Clone, Debug)]
28pub struct LogEvent {
29    pub level: LoggingLevel,
30    pub logger: String,
31    pub data: Value,
32}
33
34/// Custom tracing Layer that bridges tracing events to MCP client via unbounded channel.
35/// Sends lightweight LogEvent to channel; consumer task in on_initialized drains with recv_many.
36pub struct McpLoggingLayer {
37    event_tx: mpsc::UnboundedSender<LogEvent>,
38    log_level_filter: Arc<Mutex<LevelFilter>>,
39}
40
41impl McpLoggingLayer {
42    pub fn new(
43        event_tx: mpsc::UnboundedSender<LogEvent>,
44        log_level_filter: Arc<Mutex<LevelFilter>>,
45    ) -> Self {
46        Self {
47            event_tx,
48            log_level_filter,
49        }
50    }
51}
52
53impl<S> Layer<S> for McpLoggingLayer
54where
55    S: Subscriber,
56{
57    fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
58        let metadata = event.metadata();
59        let level = *metadata.level();
60        let target = metadata.target();
61
62        // Check if event level passes the current filter before processing
63        let filter_level = self.log_level_filter.lock().unwrap();
64        if level > *filter_level {
65            return;
66        }
67        drop(filter_level);
68
69        // Extract fields from the event using a visitor that collects into a Map.
70        let mut fields = Map::new();
71        let mut visitor = MessageVisitor(&mut fields);
72        event.record(&mut visitor);
73
74        let mcp_level = level_to_mcp(&level);
75        let logger = target.to_string();
76        let data = Value::Object(fields);
77
78        // Send LogEvent to channel without blocking on_event.
79        let log_event = LogEvent {
80            level: mcp_level,
81            logger,
82            data,
83        };
84
85        // Ignore send error if receiver is dropped (channel closed).
86        let _ = self.event_tx.send(log_event);
87    }
88
89    fn register_callsite(&self, metadata: &'static tracing::Metadata<'static>) -> Interest {
90        let filter_level = self.log_level_filter.lock().unwrap();
91        if *metadata.level() <= *filter_level {
92            Interest::always()
93        } else {
94            Interest::never()
95        }
96    }
97
98    fn enabled(&self, metadata: &tracing::Metadata<'_>, _ctx: Context<'_, S>) -> bool {
99        let filter_level = self.log_level_filter.lock().unwrap();
100        *metadata.level() <= *filter_level
101    }
102}
103
104/// Visitor to extract fields from tracing event into a JSON Map.
105struct MessageVisitor<'a>(&'a mut Map<String, Value>);
106
107impl<'a> tracing::field::Visit for MessageVisitor<'a> {
108    fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
109        self.0.insert(
110            field.name().to_string(),
111            Value::String(format!("{:?}", value)),
112        );
113    }
114
115    fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
116        self.0
117            .insert(field.name().to_string(), Value::String(value.to_string()));
118    }
119
120    fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
121        self.0
122            .insert(field.name().to_string(), Value::Number(value.into()));
123    }
124
125    fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
126        self.0
127            .insert(field.name().to_string(), Value::Number(value.into()));
128    }
129
130    fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
131        self.0.insert(field.name().to_string(), Value::Bool(value));
132    }
133}