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