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