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