Skip to main content

things3_cli/mcp/middleware/
logging.rs

1//! Logging middleware
2
3use super::{McpMiddleware, MiddlewareContext, MiddlewareResult};
4use crate::mcp::{CallToolRequest, CallToolResult, McpError, McpResult};
5
6pub struct LoggingMiddleware {
7    level: LogLevel,
8}
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum LogLevel {
12    Debug,
13    Info,
14    Warn,
15    Error,
16}
17
18impl LoggingMiddleware {
19    /// Create a new logging middleware
20    #[must_use]
21    pub fn new(level: LogLevel) -> Self {
22        Self { level }
23    }
24
25    /// Create with debug level
26    #[must_use]
27    pub fn debug() -> Self {
28        Self::new(LogLevel::Debug)
29    }
30
31    /// Create with info level
32    #[must_use]
33    pub fn info() -> Self {
34        Self::new(LogLevel::Info)
35    }
36
37    /// Create with warn level
38    #[must_use]
39    pub fn warn() -> Self {
40        Self::new(LogLevel::Warn)
41    }
42
43    /// Create with error level
44    #[must_use]
45    pub fn error() -> Self {
46        Self::new(LogLevel::Error)
47    }
48
49    pub(super) fn should_log(&self, level: LogLevel) -> bool {
50        matches!(
51            (self.level, level),
52            (LogLevel::Debug, _)
53                | (
54                    LogLevel::Info,
55                    LogLevel::Info | LogLevel::Warn | LogLevel::Error
56                )
57                | (LogLevel::Warn, LogLevel::Warn | LogLevel::Error)
58                | (LogLevel::Error, LogLevel::Error)
59        )
60    }
61
62    fn log(&self, level: LogLevel, message: &str) {
63        if self.should_log(level) {
64            match level {
65                LogLevel::Debug => println!("[DEBUG] {message}"),
66                LogLevel::Info => println!("[INFO] {message}"),
67                LogLevel::Warn => println!("[WARN] {message}"),
68                LogLevel::Error => println!("[ERROR] {message}"),
69            }
70        }
71    }
72}
73
74#[async_trait::async_trait]
75impl McpMiddleware for LoggingMiddleware {
76    fn name(&self) -> &'static str {
77        "logging"
78    }
79
80    fn priority(&self) -> i32 {
81        100 // runs after auth (10), rate-limit (20), and validation (50)
82    }
83
84    async fn before_request(
85        &self,
86        request: &CallToolRequest,
87        context: &mut MiddlewareContext,
88    ) -> McpResult<MiddlewareResult> {
89        self.log(
90            LogLevel::Info,
91            &format!(
92                "Request started: {} (ID: {})",
93                request.name, context.request_id
94            ),
95        );
96        Ok(MiddlewareResult::Continue)
97    }
98
99    async fn after_request(
100        &self,
101        request: &CallToolRequest,
102        response: &mut CallToolResult,
103        context: &mut MiddlewareContext,
104    ) -> McpResult<MiddlewareResult> {
105        let elapsed = context.elapsed();
106        let status = if response.is_error {
107            "ERROR"
108        } else {
109            "SUCCESS"
110        };
111
112        self.log(
113            LogLevel::Info,
114            &format!(
115                "Request completed: {} (ID: {}) - {} in {:?}",
116                request.name, context.request_id, status, elapsed
117            ),
118        );
119        Ok(MiddlewareResult::Continue)
120    }
121
122    async fn on_error(
123        &self,
124        request: &CallToolRequest,
125        error: &McpError,
126        context: &mut MiddlewareContext,
127    ) -> McpResult<MiddlewareResult> {
128        self.log(
129            LogLevel::Error,
130            &format!(
131                "Request failed: {} (ID: {}) - {}",
132                request.name, context.request_id, error
133            ),
134        );
135        Ok(MiddlewareResult::Continue)
136    }
137}