ironflow_cli/commands/
logs.rs1use std::io::Write as _;
4
5use anyhow::Result;
6use clap::Args;
7use futures_util::StreamExt;
8use ironflow_sdk::IronflowClient;
9use ironflow_sdk::client::ListRunLogsFilter;
10use uuid::Uuid;
11
12#[derive(Debug, Args)]
14pub struct LogsArgs {
15 pub run_id: Uuid,
17 #[arg(long)]
19 pub follow: bool,
20 #[arg(long)]
22 pub step_id: Option<Uuid>,
23 #[arg(long)]
25 pub stream: Option<String>,
26 #[arg(long)]
28 pub limit: Option<u32>,
29}
30
31const TERMINAL_EVENTS: &[&str] = &["run_completed", "run_failed", "run_cancelled"];
33
34pub async fn execute(client: &IronflowClient, args: &LogsArgs, json_mode: bool) -> Result<()> {
43 if args.follow {
44 return execute_follow(client, args, json_mode).await;
45 }
46
47 let mut out = std::io::stdout().lock();
48 let mut cursor: Option<Uuid> = None;
49
50 loop {
51 let filter = ListRunLogsFilter {
52 step_id: args.step_id,
53 stream: args.stream.as_deref(),
54 cursor,
55 limit: args.limit,
56 };
57
58 let response = client.get_run_logs(args.run_id, &filter).await?;
59
60 for entry in &response.data {
61 if json_mode {
62 writeln!(out, "{}", serde_json::to_string(&entry)?)?;
63 } else {
64 writeln!(
65 out,
66 "[{}] [{}] {}",
67 entry.step_name, entry.stream, entry.line
68 )?;
69 }
70 }
71
72 let has_more = response
73 .meta
74 .as_ref()
75 .and_then(|m| m.extra.get("has_more"))
76 .and_then(|v| v.as_bool())
77 .unwrap_or(false);
78
79 if !has_more {
80 break;
81 }
82
83 cursor = response
84 .meta
85 .as_ref()
86 .and_then(|m| m.extra.get("next_cursor"))
87 .and_then(|v| v.as_str())
88 .and_then(|s| s.parse().ok());
89 }
90
91 Ok(())
92}
93
94async fn execute_follow(client: &IronflowClient, args: &LogsArgs, json_mode: bool) -> Result<()> {
96 let mut stream = client.events(Some(args.run_id), None).await?;
97 let mut out = std::io::stdout().lock();
98
99 while let Some(event) = stream.next().await {
100 match event {
101 Ok(ev) => {
102 if json_mode {
103 let obj = serde_json::json!({
104 "event": ev.event_type,
105 "data": ev.data,
106 });
107 writeln!(out, "{}", serde_json::to_string(&obj)?)?;
108 } else {
109 writeln!(out, "[{}] {}", ev.event_type, ev.data)?;
110 }
111
112 if TERMINAL_EVENTS.contains(&ev.event_type.as_str()) {
113 break;
114 }
115 }
116 Err(e) => {
117 return Err(anyhow::anyhow!("SSE stream error: {e}"));
118 }
119 }
120 }
121
122 Ok(())
123}