code_analyze_mcp/
logging.rs1use rmcp::model::LoggingLevel;
9use serde_json::{Map, Value};
10use std::sync::{Arc, Mutex};
11use tokio::sync::mpsc;
12use tracing::subscriber::Interest;
13use tracing::{Level, Subscriber};
14use tracing_subscriber::Layer;
15use tracing_subscriber::filter::LevelFilter;
16use tracing_subscriber::layer::Context;
17
18#[must_use]
20pub fn level_to_mcp(level: &Level) -> LoggingLevel {
21 match *level {
22 Level::TRACE | Level::DEBUG => LoggingLevel::Debug,
23 Level::INFO => LoggingLevel::Info,
24 Level::WARN => LoggingLevel::Warning,
25 Level::ERROR => LoggingLevel::Error,
26 }
27}
28
29#[derive(Clone, Debug)]
31pub struct LogEvent {
32 pub level: LoggingLevel,
33 pub logger: String,
34 pub data: Value,
35}
36
37pub struct McpLoggingLayer {
40 event_tx: mpsc::UnboundedSender<LogEvent>,
41 log_level_filter: Arc<Mutex<LevelFilter>>,
42}
43
44impl McpLoggingLayer {
45 pub fn new(
46 event_tx: mpsc::UnboundedSender<LogEvent>,
47 log_level_filter: Arc<Mutex<LevelFilter>>,
48 ) -> Self {
49 Self {
50 event_tx,
51 log_level_filter,
52 }
53 }
54}
55
56impl<S> Layer<S> for McpLoggingLayer
57where
58 S: Subscriber,
59{
60 fn on_event(&self, event: &tracing::Event<'_>, _ctx: Context<'_, S>) {
61 let metadata = event.metadata();
62 let level = *metadata.level();
63 let target = metadata.target();
64
65 let filter_level = self
67 .log_level_filter
68 .lock()
69 .unwrap_or_else(std::sync::PoisonError::into_inner);
70 if level > *filter_level {
71 return;
72 }
73 drop(filter_level);
74
75 let mut fields = Map::new();
77 let mut visitor = MessageVisitor(&mut fields);
78 event.record(&mut visitor);
79
80 let mcp_level = level_to_mcp(&level);
81 let logger = target.to_string();
82 let data = Value::Object(fields);
83
84 let log_event = LogEvent {
86 level: mcp_level,
87 logger,
88 data,
89 };
90
91 let _ = self.event_tx.send(log_event);
93 }
94
95 fn register_callsite(&self, metadata: &'static tracing::Metadata<'static>) -> Interest {
96 let filter_level = self
97 .log_level_filter
98 .lock()
99 .unwrap_or_else(std::sync::PoisonError::into_inner);
100 if *metadata.level() <= *filter_level {
101 Interest::always()
102 } else {
103 Interest::never()
104 }
105 }
106
107 fn enabled(&self, metadata: &tracing::Metadata<'_>, _ctx: Context<'_, S>) -> bool {
108 let filter_level = self
109 .log_level_filter
110 .lock()
111 .unwrap_or_else(std::sync::PoisonError::into_inner);
112 *metadata.level() <= *filter_level
113 }
114}
115
116struct MessageVisitor<'a>(&'a mut Map<String, Value>);
118
119impl tracing::field::Visit for MessageVisitor<'_> {
120 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
121 self.0.insert(
122 field.name().to_string(),
123 Value::String(format!("{value:?}")),
124 );
125 }
126
127 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
128 self.0
129 .insert(field.name().to_string(), Value::String(value.to_string()));
130 }
131
132 fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
133 self.0
134 .insert(field.name().to_string(), Value::Number(value.into()));
135 }
136
137 fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
138 self.0
139 .insert(field.name().to_string(), Value::Number(value.into()));
140 }
141
142 fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
143 self.0.insert(field.name().to_string(), Value::Bool(value));
144 }
145}