1use std::str::FromStr;
8use std::sync::{Arc, Mutex};
9use tokio::sync::mpsc;
10
11use crate::server::middleware::RateLimiter;
12use crate::transport::traits::JsonRpcNotification;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
16pub enum LogLevel {
17 Emergency,
19 Alert,
21 Critical,
23 Error,
25 Warning,
27 Notice,
29 Info,
31 Debug,
33}
34
35impl LogLevel {
36 pub fn as_str(&self) -> &'static str {
38 match self {
39 LogLevel::Emergency => "emergency",
40 LogLevel::Alert => "alert",
41 LogLevel::Critical => "critical",
42 LogLevel::Error => "error",
43 LogLevel::Warning => "warning",
44 LogLevel::Notice => "notice",
45 LogLevel::Info => "info",
46 LogLevel::Debug => "debug",
47 }
48 }
49}
50
51impl FromStr for LogLevel {
52 type Err = String;
53
54 fn from_str(s: &str) -> Result<Self, Self::Err> {
56 match s.to_lowercase().as_str() {
57 "emergency" => Ok(Self::Emergency),
58 "alert" => Ok(Self::Alert),
59 "critical" => Ok(Self::Critical),
60 "error" => Ok(Self::Error),
61 "warning" => Ok(Self::Warning),
62 "notice" => Ok(Self::Notice),
63 "info" => Ok(Self::Info),
64 "debug" => Ok(Self::Debug),
65 _ => Err(format!("Invalid log level: {}", s)),
66 }
67 }
68}
69
70impl std::fmt::Display for LogLevel {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 write!(f, "{}", self.as_str())
73 }
74}
75
76#[derive(Clone)]
78pub struct LoggerConfig {
79 pub min_level: LogLevel,
81 pub rate_limiter: Option<Arc<RateLimiter>>,
83}
84
85impl Default for LoggerConfig {
86 fn default() -> Self {
87 Self {
88 min_level: LogLevel::Info,
89 rate_limiter: None, }
91 }
92}
93
94#[derive(Clone)]
108pub struct McpLogger {
109 tx: mpsc::UnboundedSender<JsonRpcNotification>,
110 logger_name: String,
111 config: Arc<Mutex<LoggerConfig>>,
112}
113
114impl McpLogger {
115 pub fn new(
117 tx: mpsc::UnboundedSender<JsonRpcNotification>,
118 logger_name: impl Into<String>,
119 ) -> Self {
120 Self::with_config(tx, logger_name, LoggerConfig::default())
121 }
122
123 pub fn with_config(
125 tx: mpsc::UnboundedSender<JsonRpcNotification>,
126 logger_name: impl Into<String>,
127 config: LoggerConfig,
128 ) -> Self {
129 Self {
130 tx,
131 logger_name: logger_name.into(),
132 config: Arc::new(Mutex::new(config)),
133 }
134 }
135
136 pub fn log(&self, level: LogLevel, message: impl Into<String>) -> bool {
138 {
140 let config = self.config.lock().unwrap();
141 if level > config.min_level {
142 return false;
143 }
144 if let Some(ref limiter) = config.rate_limiter
145 && limiter.check().is_err()
146 {
147 return false; }
149 }
150
151 let notification = JsonRpcNotification::new(
152 "notifications/message",
153 Some(serde_json::json!({
154 "level": level.as_str(),
155 "logger": self.logger_name,
156 "data": message.into()
157 })),
158 );
159 let _ = self.tx.send(notification);
160 true
161 }
162
163 pub fn set_min_level(&self, level: LogLevel) {
165 self.config.lock().unwrap().min_level = level;
166 }
167
168 pub fn send_notification(
184 &self,
185 method: impl Into<String>,
186 params: Option<serde_json::Value>,
187 ) -> bool {
188 let notification = JsonRpcNotification::new(method, params);
189 self.tx.send(notification).is_ok()
190 }
191
192 pub fn emergency(&self, message: impl Into<String>) -> bool {
194 self.log(LogLevel::Emergency, message)
195 }
196
197 pub fn alert(&self, message: impl Into<String>) -> bool {
199 self.log(LogLevel::Alert, message)
200 }
201
202 pub fn critical(&self, message: impl Into<String>) -> bool {
204 self.log(LogLevel::Critical, message)
205 }
206
207 pub fn error(&self, message: impl Into<String>) -> bool {
209 self.log(LogLevel::Error, message)
210 }
211
212 pub fn warning(&self, message: impl Into<String>) -> bool {
214 self.log(LogLevel::Warning, message)
215 }
216
217 pub fn notice(&self, message: impl Into<String>) -> bool {
219 self.log(LogLevel::Notice, message)
220 }
221
222 pub fn info(&self, message: impl Into<String>) -> bool {
224 self.log(LogLevel::Info, message)
225 }
226
227 pub fn debug(&self, message: impl Into<String>) -> bool {
229 self.log(LogLevel::Debug, message)
230 }
231
232 pub fn child(&self, name: impl Into<String>) -> Self {
242 Self {
243 tx: self.tx.clone(),
244 logger_name: format!("{}.{}", self.logger_name, name.into()),
245 config: Arc::clone(&self.config),
246 }
247 }
248}
249
250pub fn send_log_notification(
259 tx: &mpsc::UnboundedSender<JsonRpcNotification>,
260 level: LogLevel,
261 logger: &str,
262 message: impl Into<String>,
263) {
264 let notification = JsonRpcNotification::new(
265 "notifications/message",
266 Some(serde_json::json!({
267 "level": level.as_str(),
268 "logger": logger,
269 "data": message.into()
270 })),
271 );
272 let _ = tx.send(notification);
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
280 fn test_log_level_as_str() {
281 assert_eq!(LogLevel::Debug.as_str(), "debug");
282 assert_eq!(LogLevel::Info.as_str(), "info");
283 assert_eq!(LogLevel::Warning.as_str(), "warning");
284 assert_eq!(LogLevel::Error.as_str(), "error");
285 }
286
287 #[test]
288 fn test_log_level_display() {
289 assert_eq!(format!("{}", LogLevel::Info), "info");
290 }
291
292 #[test]
293 fn test_logger_creation() {
294 let (tx, _rx) = mpsc::unbounded_channel();
295 let logger = McpLogger::new(tx, "test-logger");
296 assert_eq!(logger.logger_name, "test-logger");
297 }
298
299 #[test]
300 fn test_child_logger() {
301 let (tx, _rx) = mpsc::unbounded_channel();
302 let parent = McpLogger::new(tx, "parent");
303 let child = parent.child("child");
304 assert_eq!(child.logger_name, "parent.child");
305 }
306
307 #[test]
308 fn test_log_methods() {
309 let (tx, mut rx) = mpsc::unbounded_channel();
310 let config = LoggerConfig {
311 min_level: LogLevel::Debug,
312 rate_limiter: None,
313 };
314 let logger = McpLogger::with_config(tx, "test", config);
315
316 logger.debug("debug message");
317 logger.info("info message");
318 logger.warning("warning message");
319 logger.error("error message");
320
321 let mut count = 0;
323 while rx.try_recv().is_ok() {
324 count += 1;
325 }
326 assert_eq!(count, 4);
327 }
328
329 #[test]
330 fn test_send_log_notification() {
331 let (tx, mut rx) = mpsc::unbounded_channel();
332 send_log_notification(&tx, LogLevel::Info, "test", "test message");
333
334 let notification = rx.try_recv().unwrap();
335 assert_eq!(notification.method, "notifications/message");
336 }
337}