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_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}