Skip to main content

systemprompt_cli/commands/infrastructure/logs/
mod.rs

1//! `infra logs` command surface: querying, searching, and tracing the log
2//! store.
3//!
4//! Dispatches the [`LogsCommands`] subcommands (view, search, stream, export,
5//! cleanup, delete, summary, show, trace, governance, request, tools, audit)
6//! and defines
7//! the serializable output rows shared across them. On a `--database-url`
8//! invocation only the read-only subcommands are served; stream, cleanup, and
9//! delete require a full profile context.
10//!
11//! Copyright (c) systemprompt.io — Business Source License 1.1.
12//! See <https://systemprompt.io> for licensing details.
13
14mod 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}