systemprompt_cli/commands/infrastructure/logs/trace/
mod.rs1mod 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}