1use crate::paths;
2use crate::protocol::{Request, Response};
3use std::io::{BufRead, BufReader};
4use std::fs::File;
5
6#[allow(clippy::too_many_arguments)]
7pub async fn execute(
8 session: &str, target: Option<&str>, tail: usize,
9 follow: bool, stderr: bool, all: bool, timeout: Option<u64>, lines: Option<usize>,
10) -> i32 {
11 if follow {
12 return execute_follow(session, target, all, timeout, lines).await;
13 }
14
15 let log_dir = paths::log_dir(session);
17
18 if all || target.is_none() {
19 return show_all_logs(&log_dir, tail);
20 }
21
22 let target = target.unwrap();
23 let stream = if stderr { "stderr" } else { "stdout" };
24 let path = log_dir.join(format!("{}.{}", target, stream));
25
26 match tail_file(&path, tail) {
27 Ok(lines) => { for line in lines { println!("{}", line); } 0 }
28 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
29 eprintln!("error: no logs for process '{}' ({})", target, stream);
30 2
31 }
32 Err(e) => { eprintln!("error reading logs: {}", e); 1 }
33 }
34}
35
36async fn execute_follow(
37 session: &str, target: Option<&str>, all: bool, timeout: Option<u64>, lines: Option<usize>,
38) -> i32 {
39 let req = Request::Logs {
40 target: target.map(|t| t.to_string()),
41 tail: 0,
42 follow: true,
43 stderr: false,
44 all: all || target.is_none(),
45 timeout_secs: timeout.or(Some(30)), lines,
47 };
48
49 let show_prefix = all || target.is_none();
50 match crate::cli::stream_responses(session, &req, false, |process, _stream, line| {
51 if show_prefix {
52 println!("[{}] {}", process, line);
53 } else {
54 println!("{}", line);
55 }
56 }).await {
57 Ok(Response::LogEnd) => 0,
58 Ok(Response::Error { code, message }) => { eprintln!("error: {}", message); code }
59 Ok(_) => 0,
60 Err(e) => { eprintln!("error: {}", e); 1 }
61 }
62}
63
64fn show_all_logs(log_dir: &std::path::Path, tail: usize) -> i32 {
65 let entries = match std::fs::read_dir(log_dir) {
66 Ok(e) => e,
67 Err(e) => { eprintln!("error: cannot read log dir: {}", e); return 1; }
68 };
69
70 let mut all_lines: Vec<(String, String)> = Vec::new();
71 for entry in entries.flatten() {
72 let name = entry.file_name().to_string_lossy().to_string();
73 if !name.ends_with(".stdout") { continue; }
74 let proc_name = name.trim_end_matches(".stdout").to_string();
75 if let Ok(lines) = tail_file(&entry.path(), tail) {
76 for line in lines {
77 all_lines.push((proc_name.to_string(), line));
78 }
79 }
80 }
81
82 for (proc_name, line) in &all_lines {
83 println!("[{}] {}", proc_name, line);
84 }
85 0
86}
87
88fn tail_file(path: &std::path::Path, n: usize) -> std::io::Result<Vec<String>> {
89 let file = File::open(path)?;
90 let mut ring: std::collections::VecDeque<String> = std::collections::VecDeque::with_capacity(n);
92 for line in BufReader::new(file).lines() {
93 let line = line?;
94 if ring.len() == n {
95 ring.pop_front();
96 }
97 ring.push_back(line);
98 }
99 Ok(ring.into_iter().collect())
100}