Skip to main content

systemprompt_cli/commands/plugins/mcp/
logs.rs

1use std::path::PathBuf;
2
3use anyhow::Result;
4use clap::{Args, ValueEnum};
5
6use super::logs_db::execute_db_mode;
7use super::logs_disk::{execute_disk_mode, execute_follow_mode};
8use crate::CliConfig;
9use crate::interactive::Prompter;
10use crate::shared::CommandOutput;
11use systemprompt_config::ProfileBootstrap;
12use systemprompt_models::AppPaths;
13
14#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
15pub enum LogLevel {
16    Debug,
17    Info,
18    Warn,
19    Error,
20}
21
22impl LogLevel {
23    pub fn matches(&self, level_str: &str) -> bool {
24        let level_upper = level_str.to_uppercase();
25        match self {
26            Self::Debug => true,
27            Self::Info => !level_upper.contains("DEBUG"),
28            Self::Warn => level_upper.contains("WARN") || level_upper.contains("ERROR"),
29            Self::Error => level_upper.contains("ERROR"),
30        }
31    }
32}
33
34#[derive(Debug, Args)]
35pub struct LogsArgs {
36    #[arg(help = "MCP server name (optional - shows all MCP logs if not specified)")]
37    pub server: Option<String>,
38
39    #[arg(
40        long,
41        short = 'n',
42        visible_alias = "tail",
43        default_value = "50",
44        help = "Number of lines to show"
45    )]
46    pub lines: usize,
47
48    #[arg(long, short, help = "Follow log output continuously (disk only)")]
49    pub follow: bool,
50
51    #[arg(long, help = "Force reading from disk files instead of database")]
52    pub disk: bool,
53
54    #[arg(long, help = "Custom logs directory path")]
55    pub logs_dir: Option<String>,
56
57    #[arg(
58        long,
59        value_enum,
60        help = "Filter by log level: debug, info, warn, error"
61    )]
62    pub level: Option<LogLevel>,
63}
64
65fn get_default_logs_dir() -> PathBuf {
66    ProfileBootstrap::get()
67        .ok()
68        .and_then(|p| AppPaths::from_profile(&p.paths).ok())
69        .map_or_else(|| PathBuf::from("/var/log"), |paths| paths.system().logs())
70}
71
72pub(super) async fn execute(
73    args: LogsArgs,
74    prompter: &dyn Prompter,
75    config: &CliConfig,
76) -> Result<CommandOutput> {
77    let logs_path = args
78        .logs_dir
79        .as_ref()
80        .map_or_else(get_default_logs_dir, PathBuf::from);
81
82    if args.follow {
83        return execute_follow_mode(&args, prompter, config, &logs_path);
84    }
85
86    if args.disk {
87        return execute_disk_mode(&args, prompter, config, &logs_path);
88    }
89
90    match execute_db_mode(&args, config).await {
91        Ok(result) => Ok(result),
92        Err(e) => {
93            tracing::debug!(error = %e, "DB log query failed, falling back to disk");
94            execute_disk_mode(&args, prompter, config, &logs_path)
95        },
96    }
97}