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