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