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