1use std::sync::{Arc, Mutex};
8use tokio::sync::mpsc;
9
10use crate::server::middleware::RateLimiter;
11use crate::transport::traits::JsonRpcNotification;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
15pub enum LogLevel {
16 Emergency,
18 Alert,
20 Critical,
22 Error,
24 Warning,
26 Notice,
28 Info,
30 Debug,
32}
33
34impl LogLevel {
35 pub fn as_str(&self) -> &'static str {
37 match self {
38 LogLevel::Emergency => "emergency",
39 LogLevel::Alert => "alert",
40 LogLevel::Critical => "critical",
41 LogLevel::Error => "error",
42 LogLevel::Warning => "warning",
43 LogLevel::Notice => "notice",
44 LogLevel::Info => "info",
45 LogLevel::Debug => "debug",
46 }
47 }
48
49 pub fn from_str(s: &str) -> Option<Self> {
51 match s.to_lowercase().as_str() {
52 "emergency" => Some(Self::Emergency),
53 "alert" => Some(Self::Alert),
54 "critical" => Some(Self::Critical),
55 "error" => Some(Self::Error),
56 "warning" => Some(Self::Warning),
57 "notice" => Some(Self::Notice),
58 "info" => Some(Self::Info),
59 "debug" => Some(Self::Debug),
60 _ => None,
61 }
62 }
63}
64
65impl std::fmt::Display for LogLevel {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 write!(f, "{}", self.as_str())
68 }
69}
70
71#[derive(Clone)]
73pub struct LoggerConfig {
74 pub min_level: LogLevel,
76 pub rate_limiter: Option<Arc<RateLimiter>>,
78}
79
80impl Default for LoggerConfig {
81 fn default() -> Self {
82 Self {
83 min_level: LogLevel::Info,
84 rate_limiter: None, }
86 }
87}
88
89#[derive(Clone)]
103pub struct McpLogger {
104 tx: mpsc::UnboundedSender<JsonRpcNotification>,
105 logger_name: String,
106 config: Arc<Mutex<LoggerConfig>>,
107}
108
109impl McpLogger {
110 pub fn new(
112 tx: mpsc::UnboundedSender<JsonRpcNotification>,
113 logger_name: impl Into<String>,
114 ) -> Self {
115 Self::with_config(tx, logger_name, LoggerConfig::default())
116 }
117
118 pub fn with_config(
120 tx: mpsc::UnboundedSender<JsonRpcNotification>,
121 logger_name: impl Into<String>,
122 config: LoggerConfig,
123 ) -> Self {
124 Self {
125 tx,
126 logger_name: logger_name.into(),
127 config: Arc::new(Mutex::new(config)),
128 }
129 }
130
131 pub fn log(&self, level: LogLevel, message: impl Into<String>) -> bool {
133 {
135 let config = self.config.lock().unwrap();
136 if level > config.min_level {
137 return false;
138 }
139 if let Some(ref limiter) = config.rate_limiter {
140 if limiter.check().is_err() {
141 return false; }
143 }
144 }
145
146 let notification = JsonRpcNotification::new(
147 "notifications/message",
148 Some(serde_json::json!({
149 "level": level.as_str(),
150 "logger": self.logger_name,
151 "data": message.into()
152 })),
153 );
154 let _ = self.tx.send(notification);
155 true
156 }
157
158 pub fn set_min_level(&self, level: LogLevel) {
160 self.config.lock().unwrap().min_level = level;
161 }
162
163 pub fn emergency(&self, message: impl Into<String>) -> bool {
165 self.log(LogLevel::Emergency, message)
166 }
167
168 pub fn alert(&self, message: impl Into<String>) -> bool {
170 self.log(LogLevel::Alert, message)
171 }
172
173 pub fn critical(&self, message: impl Into<String>) -> bool {
175 self.log(LogLevel::Critical, message)
176 }
177
178 pub fn error(&self, message: impl Into<String>) -> bool {
180 self.log(LogLevel::Error, message)
181 }
182
183 pub fn warning(&self, message: impl Into<String>) -> bool {
185 self.log(LogLevel::Warning, message)
186 }
187
188 pub fn notice(&self, message: impl Into<String>) -> bool {
190 self.log(LogLevel::Notice, message)
191 }
192
193 pub fn info(&self, message: impl Into<String>) -> bool {
195 self.log(LogLevel::Info, message)
196 }
197
198 pub fn debug(&self, message: impl Into<String>) -> bool {
200 self.log(LogLevel::Debug, message)
201 }
202
203 pub fn child(&self, name: impl Into<String>) -> Self {
213 Self {
214 tx: self.tx.clone(),
215 logger_name: format!("{}.{}", self.logger_name, name.into()),
216 config: Arc::clone(&self.config),
217 }
218 }
219}
220
221pub fn send_log_notification(
230 tx: &mpsc::UnboundedSender<JsonRpcNotification>,
231 level: LogLevel,
232 logger: &str,
233 message: impl Into<String>,
234) {
235 let notification = JsonRpcNotification::new(
236 "notifications/message",
237 Some(serde_json::json!({
238 "level": level.as_str(),
239 "logger": logger,
240 "data": message.into()
241 })),
242 );
243 let _ = tx.send(notification);
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn test_log_level_as_str() {
252 assert_eq!(LogLevel::Debug.as_str(), "debug");
253 assert_eq!(LogLevel::Info.as_str(), "info");
254 assert_eq!(LogLevel::Warning.as_str(), "warning");
255 assert_eq!(LogLevel::Error.as_str(), "error");
256 }
257
258 #[test]
259 fn test_log_level_display() {
260 assert_eq!(format!("{}", LogLevel::Info), "info");
261 }
262
263 #[test]
264 fn test_logger_creation() {
265 let (tx, _rx) = mpsc::unbounded_channel();
266 let logger = McpLogger::new(tx, "test-logger");
267 assert_eq!(logger.logger_name, "test-logger");
268 }
269
270 #[test]
271 fn test_child_logger() {
272 let (tx, _rx) = mpsc::unbounded_channel();
273 let parent = McpLogger::new(tx, "parent");
274 let child = parent.child("child");
275 assert_eq!(child.logger_name, "parent.child");
276 }
277
278 #[test]
279 fn test_log_methods() {
280 let (tx, mut rx) = mpsc::unbounded_channel();
281 let config = LoggerConfig {
282 min_level: LogLevel::Debug,
283 rate_limiter: None,
284 };
285 let logger = McpLogger::with_config(tx, "test", config);
286
287 logger.debug("debug message");
288 logger.info("info message");
289 logger.warning("warning message");
290 logger.error("error message");
291
292 let mut count = 0;
294 while rx.try_recv().is_ok() {
295 count += 1;
296 }
297 assert_eq!(count, 4);
298 }
299
300 #[test]
301 fn test_send_log_notification() {
302 let (tx, mut rx) = mpsc::unbounded_channel();
303 send_log_notification(&tx, LogLevel::Info, "test", "test message");
304
305 let notification = rx.try_recv().unwrap();
306 assert_eq!(notification.method, "notifications/message");
307 }
308}