Skip to main content

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

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