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