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};
27
28use super::types::{MessageRow, ToolCallRow};
29use crate::context::CommandContext;
30use crate::shared::render_result;
31
32#[derive(Debug, Subcommand)]
33pub enum TraceCommands {
34    #[command(
35        about = "List recent traces",
36        after_help = "EXAMPLES:\n  systemprompt infra logs trace list\n  systemprompt infra logs \
37                      trace list --limit 50 --since 1h\n  systemprompt infra logs trace list \
38                      --agent researcher --status completed"
39    )]
40    List(list::ListArgs),
41
42    #[command(
43        about = "Show trace details",
44        after_help = "EXAMPLES:\n  systemprompt infra logs trace show abc123\n  systemprompt \
45                      infra logs trace show abc123 --verbose\n  systemprompt infra logs trace \
46                      show abc123 --steps --ai --mcp"
47    )]
48    Show(show::ShowArgs),
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
52pub struct TraceEventRow {
53    pub timestamp: String,
54    pub delta_ms: i64,
55    pub event_type: String,
56    pub details: String,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub latency_ms: Option<i64>,
59}
60
61#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
62pub struct AiSummaryRow {
63    pub request_count: i64,
64    pub total_tokens: i64,
65    pub input_tokens: i64,
66    pub output_tokens: i64,
67    pub cost_dollars: f64,
68    pub total_latency_ms: i64,
69}
70
71#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
72pub struct McpSummaryRow {
73    pub execution_count: i64,
74    pub total_execution_time_ms: i64,
75}
76
77#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
78pub struct StepSummaryRow {
79    pub total: i64,
80    pub completed: i64,
81    pub failed: i64,
82    pub pending: i64,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
86pub struct TraceViewOutput {
87    pub trace_id: TraceId,
88    pub events: Vec<TraceEventRow>,
89    pub ai_summary: AiSummaryRow,
90    pub mcp_summary: McpSummaryRow,
91    pub step_summary: StepSummaryRow,
92    #[serde(skip_serializing_if = "Option::is_none", rename = "task_id")]
93    pub task: Option<String>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub duration_ms: Option<i64>,
96    pub status: String,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
100pub struct TraceListRow {
101    pub trace_id: TraceId,
102    pub timestamp: String,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub agent: Option<String>,
105    pub status: String,
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub duration_ms: Option<i64>,
108    pub ai_requests: i64,
109    pub mcp_calls: i64,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
113pub struct TraceListOutput {
114    pub traces: Vec<TraceListRow>,
115    pub total: u64,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
119pub struct TaskInfoRow {
120    #[serde(rename = "task_id")]
121    pub task: String,
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub agent_name: Option<String>,
124    pub status: String,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub started_at: Option<String>,
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub duration_ms: Option<i64>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
132pub struct StepRow {
133    pub step_number: i32,
134    pub step_type: String,
135    pub title: String,
136    pub status: String,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub duration_ms: Option<i64>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
142pub struct AiRequestRow {
143    pub request_id: AiRequestId,
144    pub model: String,
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub max_tokens: Option<i32>,
147    pub tokens: String,
148    pub cost: String,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub latency_ms: Option<i64>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
154pub struct ArtifactRow {
155    #[serde(rename = "artifact_id")]
156    pub artifact: String,
157    pub artifact_type: String,
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub name: Option<String>,
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub source: Option<String>,
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub tool_name: Option<String>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
167pub struct AiTraceOutput {
168    #[serde(rename = "task_id")]
169    pub task: String,
170    pub task_info: TaskInfoRow,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub user_input: Option<String>,
173    pub execution_steps: Vec<StepRow>,
174    pub ai_requests: Vec<AiRequestRow>,
175    pub mcp_executions: Vec<ToolCallRow>,
176    pub artifacts: Vec<ArtifactRow>,
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub agent_response: Option<String>,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
182pub struct AiLookupOutput {
183    pub request_id: AiRequestId,
184    pub provider: String,
185    pub model: String,
186    pub input_tokens: i32,
187    pub output_tokens: i32,
188    pub cost_dollars: f64,
189    pub latency_ms: i64,
190    pub messages: Vec<MessageRow>,
191    pub linked_mcp_calls: Vec<ToolCallRow>,
192}
193
194pub async fn execute(command: TraceCommands, ctx: &CommandContext) -> Result<()> {
195    match command {
196        TraceCommands::List(args) => list::execute(args, ctx).await,
197        TraceCommands::Show(args) => {
198            let result = show::execute(args, ctx).await?;
199            render_result(&result, &ctx.cli);
200            Ok(())
201        },
202    }
203}