Skip to main content

systemprompt_cli/commands/infrastructure/logs/trace/
mod.rs

1//! `infra logs trace` subcommands for reconstructing execution traces.
2//!
3//! Exposes [`TraceCommands`] (list, show) and the family of serializable rows
4//! that describe a trace: per-event timeline ([`TraceEventRow`]), the AI / MCP
5//! / step summaries, and the assembled [`TraceViewOutput`] and
6//! [`AiTraceOutput`]. A trace is resolved either from log events or from an AI
7//! task, with the display split across the sibling `display`, `ai_*`,
8//! `summary`, and `json` modules.
9
10mod ai_artifacts;
11mod ai_display;
12mod ai_mcp;
13mod ai_trace_display;
14mod display;
15mod json;
16mod list;
17mod show;
18mod summary;
19
20pub use summary::{SummaryContext, print_summary};
21
22use anyhow::Result;
23use clap::Subcommand;
24use schemars::JsonSchema;
25use serde::{Deserialize, Serialize};
26use systemprompt_identifiers::{AiRequestId, TraceId};
27use systemprompt_runtime::DatabaseContext;
28
29use super::types::{MessageRow, ToolCallRow};
30use crate::CliConfig;
31use crate::shared::render_result;
32
33#[derive(Debug, Subcommand)]
34pub enum TraceCommands {
35    #[command(
36        about = "List recent traces",
37        after_help = "EXAMPLES:\n  systemprompt infra logs trace list\n  systemprompt infra logs \
38                      trace list --limit 50 --since 1h\n  systemprompt infra logs trace list \
39                      --agent researcher --status completed"
40    )]
41    List(list::ListArgs),
42
43    #[command(
44        about = "Show trace details",
45        after_help = "EXAMPLES:\n  systemprompt infra logs trace show abc123\n  systemprompt \
46                      infra logs trace show abc123 --verbose\n  systemprompt infra logs trace \
47                      show abc123 --steps --ai --mcp"
48    )]
49    Show(show::ShowArgs),
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
53pub struct TraceEventRow {
54    pub timestamp: String,
55    pub delta_ms: i64,
56    pub event_type: String,
57    pub details: String,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub latency_ms: Option<i64>,
60}
61
62#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
63pub struct AiSummaryRow {
64    pub request_count: i64,
65    pub total_tokens: i64,
66    pub input_tokens: i64,
67    pub output_tokens: i64,
68    pub cost_dollars: f64,
69    pub total_latency_ms: i64,
70}
71
72#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
73pub struct McpSummaryRow {
74    pub execution_count: i64,
75    pub total_execution_time_ms: i64,
76}
77
78#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
79pub struct StepSummaryRow {
80    pub total: i64,
81    pub completed: i64,
82    pub failed: i64,
83    pub pending: i64,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
87pub struct TraceViewOutput {
88    pub trace_id: TraceId,
89    pub events: Vec<TraceEventRow>,
90    pub ai_summary: AiSummaryRow,
91    pub mcp_summary: McpSummaryRow,
92    pub step_summary: StepSummaryRow,
93    #[serde(skip_serializing_if = "Option::is_none", rename = "task_id")]
94    pub task: Option<String>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub duration_ms: Option<i64>,
97    pub status: String,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
101pub struct TraceListRow {
102    pub trace_id: TraceId,
103    pub timestamp: String,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub agent: Option<String>,
106    pub status: String,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub duration_ms: Option<i64>,
109    pub ai_requests: i64,
110    pub mcp_calls: i64,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
114pub struct TraceListOutput {
115    pub traces: Vec<TraceListRow>,
116    pub total: u64,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
120pub struct TaskInfoRow {
121    #[serde(rename = "task_id")]
122    pub task: String,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub agent_name: Option<String>,
125    pub status: String,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub started_at: Option<String>,
128    #[serde(skip_serializing_if = "Option::is_none")]
129    pub duration_ms: Option<i64>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
133pub struct StepRow {
134    pub step_number: i32,
135    pub step_type: String,
136    pub title: String,
137    pub status: String,
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub duration_ms: Option<i64>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
143pub struct AiRequestRow {
144    pub request_id: AiRequestId,
145    pub model: String,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub max_tokens: Option<i32>,
148    pub tokens: String,
149    pub cost: String,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub latency_ms: Option<i64>,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
155pub struct ArtifactRow {
156    #[serde(rename = "artifact_id")]
157    pub artifact: String,
158    pub artifact_type: String,
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub name: Option<String>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub source: Option<String>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub tool_name: Option<String>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
168pub struct AiTraceOutput {
169    #[serde(rename = "task_id")]
170    pub task: String,
171    pub task_info: TaskInfoRow,
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub user_input: Option<String>,
174    pub execution_steps: Vec<StepRow>,
175    pub ai_requests: Vec<AiRequestRow>,
176    pub mcp_executions: Vec<ToolCallRow>,
177    pub artifacts: Vec<ArtifactRow>,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub agent_response: Option<String>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
183pub struct AiLookupOutput {
184    pub request_id: AiRequestId,
185    pub provider: String,
186    pub model: String,
187    pub input_tokens: i32,
188    pub output_tokens: i32,
189    pub cost_dollars: f64,
190    pub latency_ms: i64,
191    pub messages: Vec<MessageRow>,
192    pub linked_mcp_calls: Vec<ToolCallRow>,
193}
194
195pub async fn execute(command: TraceCommands, config: &CliConfig) -> Result<()> {
196    match command {
197        TraceCommands::List(args) => list::execute(args, config).await,
198        TraceCommands::Show(args) => {
199            let result = show::execute(args).await?;
200            render_result(&result);
201            Ok(())
202        },
203    }
204}
205
206pub async fn execute_with_pool(
207    command: TraceCommands,
208    db_ctx: &DatabaseContext,
209    config: &CliConfig,
210) -> Result<()> {
211    match command {
212        TraceCommands::List(args) => list::execute_with_pool(args, db_ctx, config).await,
213        TraceCommands::Show(args) => {
214            let result = show::execute_with_pool(args, db_ctx).await?;
215            render_result(&result);
216            Ok(())
217        },
218    }
219}