code_analyze_mcp/
logging.rs1use 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
17pub 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#[derive(Clone, Debug)]
29pub struct LogEvent {
30 pub level: LoggingLevel,
31 pub logger: String,
32 pub data: Value,
33}
34
35pub 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 let filter_level = self.log_level_filter.lock().unwrap();
65 if level > *filter_level {
66 return;
67 }
68 drop(filter_level);
69
70 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 let log_event = LogEvent {
81 level: mcp_level,
82 logger,
83 data,
84 };
85
86 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
107struct 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}