1use crate::paths;
2use crate::protocol::{Request, Response};
3use std::fs::File;
4use std::io::{BufRead, BufReader};
5
6#[allow(clippy::too_many_arguments)]
7pub async fn execute(
8 session: &str,
9 target: Option<&str>,
10 tail: usize,
11 follow: bool,
12 stderr: bool,
13 all: bool,
14 timeout: Option<u64>,
15 lines: Option<usize>,
16) -> i32 {
17 if follow {
18 return execute_follow(session, target, tail, stderr, all, timeout, lines).await;
19 }
20
21 let log_dir = paths::log_dir(session);
23
24 if all || target.is_none() {
25 return show_all_logs(&log_dir, tail, stderr);
26 }
27
28 let target = target.unwrap();
29 let stream = if stderr { "stderr" } else { "stdout" };
30 let path = log_dir.join(format!("{}.{}", target, stream));
31
32 match tail_file(&path, tail) {
33 Ok(lines) => {
34 for line in lines {
35 println!("{}", line);
36 }
37 0
38 }
39 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
40 eprintln!("error: no logs for process '{}' ({})", target, stream);
41 2
42 }
43 Err(e) => {
44 eprintln!("error reading logs: {}", e);
45 1
46 }
47 }
48}
49
50async fn execute_follow(
51 session: &str,
52 target: Option<&str>,
53 tail: usize,
54 stderr: bool,
55 all: bool,
56 timeout: Option<u64>,
57 lines: Option<usize>,
58) -> i32 {
59 if let Some(code) = replay_follow_tail(session, target, tail, stderr, all) {
60 return code;
61 }
62
63 let req = Request::Logs {
64 target: target.map(std::string::ToString::to_string),
65 tail: 0,
66 follow: true,
67 stderr,
68 all: all || target.is_none(),
69 timeout_secs: timeout.or(Some(30)), lines,
71 };
72
73 let show_prefix = all || target.is_none();
74 match crate::cli::stream_responses(session, &req, false, |process, _stream, line| {
75 if show_prefix {
76 println!("[{}] {}", process, line);
77 } else {
78 println!("{}", line);
79 }
80 })
81 .await
82 {
83 Ok(Response::Error { code, message }) => {
84 eprintln!("error: {}", message);
85 code.exit_code()
86 }
87 Ok(_) => 0,
88 Err(e) => {
89 eprintln!("error: {}", e);
90 1
91 }
92 }
93}
94
95fn replay_follow_tail(
96 session: &str,
97 target: Option<&str>,
98 tail: usize,
99 stderr: bool,
100 all: bool,
101) -> Option<i32> {
102 if tail == 0 {
103 return None;
104 }
105
106 let log_dir = paths::log_dir(session);
107 if all || target.is_none() {
108 let code = show_all_logs(&log_dir, tail, stderr);
109 return if code == 0 { None } else { Some(code) };
110 }
111
112 let target = target.expect("target should exist when not following all logs");
113 let stream = if stderr { "stderr" } else { "stdout" };
114 let path = log_dir.join(format!("{}.{}", target, stream));
115
116 match tail_file(&path, tail) {
117 Ok(lines) => {
118 for line in lines {
119 println!("{}", line);
120 }
121 None
122 }
123 Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
124 Err(e) => {
125 eprintln!("error reading logs: {}", e);
126 Some(1)
127 }
128 }
129}
130
131fn show_all_logs(log_dir: &std::path::Path, tail: usize, stderr: bool) -> i32 {
132 let entries = match std::fs::read_dir(log_dir) {
133 Ok(e) => e,
134 Err(e) => {
135 eprintln!("error: cannot read log dir: {}", e);
136 return 1;
137 }
138 };
139
140 let suffix = if stderr { ".stderr" } else { ".stdout" };
141 let mut all_lines: Vec<(String, String)> = Vec::new();
142 for entry in entries.flatten() {
143 let name = entry.file_name().to_string_lossy().to_string();
144 if !name.ends_with(suffix) {
145 continue;
146 }
147 let proc_name = name.trim_end_matches(suffix).to_string();
148 if let Ok(lines) = tail_file(&entry.path(), tail) {
149 for line in lines {
150 all_lines.push((proc_name.clone(), line));
151 }
152 }
153 }
154
155 for (proc_name, line) in &all_lines {
156 println!("[{}] {}", proc_name, line);
157 }
158 0
159}
160
161pub(crate) fn tail_file(path: &std::path::Path, n: usize) -> std::io::Result<Vec<String>> {
162 if n == 0 {
163 return Ok(Vec::new());
164 }
165
166 let file = File::open(path)?;
167 let mut ring: std::collections::VecDeque<String> = std::collections::VecDeque::with_capacity(n);
169 for line in BufReader::new(file).lines() {
170 let line = line?;
171 if ring.len() == n {
172 ring.pop_front();
173 }
174 ring.push_back(line);
175 }
176 Ok(ring.into_iter().collect())
177}