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