code_analyze_mcp/
logging.rs1use 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#[must_use]
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#[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
65 .log_level_filter
66 .lock()
67 .unwrap_or_else(std::sync::PoisonError::into_inner);
68 if level > *filter_level {
69 return;
70 }
71 drop(filter_level);
72
73 let mut fields = Map::new();
75 let mut visitor = MessageVisitor(&mut fields);
76 event.record(&mut visitor);
77
78 let mcp_level = level_to_mcp(&level);
79 let logger = target.to_string();
80 let data = Value::Object(fields);
81
82 let log_event = LogEvent {
84 level: mcp_level,
85 logger,
86 data,
87 };
88
89 let _ = self.event_tx.send(log_event);
91 }
92
93 fn register_callsite(&self, metadata: &'static tracing::Metadata<'static>) -> Interest {
94 let filter_level = self
95 .log_level_filter
96 .lock()
97 .unwrap_or_else(std::sync::PoisonError::into_inner);
98 if *metadata.level() <= *filter_level {
99 Interest::always()
100 } else {
101 Interest::never()
102 }
103 }
104
105 fn enabled(&self, metadata: &tracing::Metadata<'_>, _ctx: Context<'_, S>) -> bool {
106 let filter_level = self
107 .log_level_filter
108 .lock()
109 .unwrap_or_else(std::sync::PoisonError::into_inner);
110 *metadata.level() <= *filter_level
111 }
112}
113
114struct MessageVisitor<'a>(&'a mut Map<String, Value>);
116
117impl tracing::field::Visit for MessageVisitor<'_> {
118 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
119 self.0.insert(
120 field.name().to_string(),
121 Value::String(format!("{value:?}")),
122 );
123 }
124
125 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
126 self.0
127 .insert(field.name().to_string(), Value::String(value.to_string()));
128 }
129
130 fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
131 self.0
132 .insert(field.name().to_string(), Value::Number(value.into()));
133 }
134
135 fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
136 self.0
137 .insert(field.name().to_string(), Value::Number(value.into()));
138 }
139
140 fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
141 self.0.insert(field.name().to_string(), Value::Bool(value));
142 }
143}