Skip to main content

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

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