systemprompt_cli/commands/plugins/mcp/
logs.rs1use 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}