systemprompt_cli/commands/infrastructure/logs/
mod.rs1mod audit;
2mod audit_display;
3mod cleanup;
4mod delete;
5pub mod duration;
6mod export;
7pub mod request;
8mod search;
9mod search_queries;
10pub mod shared;
11mod show;
12mod stream;
13mod summary;
14pub mod tools;
15pub mod trace;
16pub mod types;
17mod view;
18
19pub use shared::{
20 cost_microdollars_to_dollars, display_log_row, format_duration_ms, format_timestamp,
21 truncate_id,
22};
23pub use types::{MessageRow, ToolCallRow};
24
25use anyhow::{bail, Result};
26use clap::Subcommand;
27use schemars::JsonSchema;
28use serde::{Deserialize, Serialize};
29use systemprompt_runtime::DatabaseContext;
30
31use crate::CliConfig;
32
33#[derive(Debug, Subcommand)]
34pub enum LogsCommands {
35 #[command(
36 about = "View log entries",
37 after_help = "EXAMPLES:\n systemprompt infra logs view --tail 20\n systemprompt infra \
38 logs view --level error\n systemprompt infra logs view --since 1h"
39 )]
40 View(view::ViewArgs),
41
42 #[command(
43 about = "Search logs by pattern",
44 after_help = "EXAMPLES:\n systemprompt infra logs search \"error\"\n systemprompt infra \
45 logs search \"timeout\" --level error --since 1h"
46 )]
47 Search(search::SearchArgs),
48
49 #[command(
50 about = "Stream logs in real-time (like tail -f)",
51 visible_alias = "follow",
52 after_help = "EXAMPLES:\n systemprompt infra logs stream\n systemprompt infra logs \
53 stream --level error --module agent\n systemprompt infra logs follow"
54 )]
55 Stream(stream::StreamArgs),
56
57 #[command(
58 about = "Export logs to file",
59 after_help = "EXAMPLES:\n systemprompt infra logs export --format json --since 24h\n \
60 systemprompt infra logs export --format csv -o logs.csv"
61 )]
62 Export(export::ExportArgs),
63
64 #[command(about = "Clean up old log entries")]
65 Cleanup(cleanup::CleanupArgs),
66
67 #[command(about = "Delete all log entries")]
68 Delete(delete::DeleteArgs),
69
70 #[command(
71 about = "Show logs summary statistics",
72 after_help = "EXAMPLES:\n systemprompt infra logs summary\n systemprompt infra logs \
73 summary --since 24h"
74 )]
75 Summary(summary::SummaryArgs),
76
77 #[command(
78 about = "Show details of a log entry or all logs for a trace",
79 after_help = "EXAMPLES:\n systemprompt infra logs show log_abc123\n systemprompt infra \
80 logs show trace_def456"
81 )]
82 Show(show::ShowArgs),
83
84 #[command(subcommand, about = "Debug execution traces")]
85 Trace(trace::TraceCommands),
86
87 #[command(subcommand, about = "Inspect AI requests")]
88 Request(request::RequestCommands),
89
90 #[command(subcommand, about = "List and search MCP tool executions")]
91 Tools(tools::ToolsCommands),
92
93 #[command(
94 about = "Full audit of an AI request with all messages and tool calls",
95 after_help = "EXAMPLES:\n systemprompt infra logs audit abc123\n systemprompt infra \
96 logs audit abc123 --full\n systemprompt infra logs audit task-xyz --json"
97 )]
98 Audit(audit::AuditArgs),
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
102pub struct LogEntryRow {
103 pub id: String,
104 pub trace_id: String,
105 pub timestamp: String,
106 pub level: String,
107 pub module: String,
108 pub message: String,
109 #[serde(skip_serializing_if = "Option::is_none")]
110 pub metadata: Option<serde_json::Value>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
114pub struct LogFilters {
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub level: Option<String>,
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub module: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 pub since: Option<String>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 pub pattern: Option<String>,
123 pub tail: i64,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
127pub struct LogViewOutput {
128 pub logs: Vec<LogEntryRow>,
129 pub total: u64,
130 pub filters: LogFilters,
131}
132
133#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
134pub struct LogDeleteOutput {
135 pub deleted_count: u64,
136 pub vacuum_performed: bool,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
140pub struct LogCleanupOutput {
141 pub deleted_count: u64,
142 pub dry_run: bool,
143 pub cutoff_date: String,
144 pub vacuum_performed: bool,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
148pub struct LogExportOutput {
149 pub exported_count: u64,
150 pub format: String,
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub file_path: Option<String>,
153}
154
155pub async fn execute(command: LogsCommands, config: &CliConfig) -> Result<()> {
156 match command {
157 LogsCommands::View(args) => view::execute(args, config).await,
158 LogsCommands::Search(args) => search::execute(args, config).await,
159 LogsCommands::Stream(args) => stream::execute(args, config).await,
160 LogsCommands::Export(args) => export::execute(args, config).await,
161 LogsCommands::Cleanup(args) => cleanup::execute(args, config).await,
162 LogsCommands::Delete(args) => delete::execute(args, config).await,
163 LogsCommands::Summary(args) => summary::execute(args, config).await,
164 LogsCommands::Show(args) => show::execute(args, config).await,
165 LogsCommands::Trace(cmd) => trace::execute(cmd, config).await,
166 LogsCommands::Request(cmd) => request::execute(cmd, config).await,
167 LogsCommands::Tools(cmd) => tools::execute(cmd, config).await,
168 LogsCommands::Audit(args) => audit::execute(args, config).await,
169 }
170}
171
172pub async fn execute_with_db(
173 command: LogsCommands,
174 db_ctx: &DatabaseContext,
175 config: &CliConfig,
176) -> Result<()> {
177 match command {
178 LogsCommands::View(args) => view::execute_with_pool(args, db_ctx, config).await,
179 LogsCommands::Search(args) => search::execute_with_pool(args, db_ctx, config).await,
180 LogsCommands::Summary(args) => summary::execute_with_pool(args, db_ctx, config).await,
181 LogsCommands::Export(args) => export::execute_with_pool(args, db_ctx, config).await,
182 LogsCommands::Show(args) => show::execute_with_pool(args, db_ctx, config).await,
183 LogsCommands::Trace(cmd) => trace::execute_with_pool(cmd, db_ctx, config).await,
184 LogsCommands::Request(cmd) => request::execute_with_pool(cmd, db_ctx, config).await,
185 LogsCommands::Tools(cmd) => tools::execute_with_pool(cmd, db_ctx, config).await,
186 LogsCommands::Audit(args) => audit::execute_with_pool(args, db_ctx, config).await,
187 LogsCommands::Stream(_) | LogsCommands::Cleanup(_) | LogsCommands::Delete(_) => {
188 bail!("This logs command requires full profile context")
189 },
190 }
191}