Skip to main content

agent_exec/
tail.rs

1//! Implementation of the `tail` sub-command.
2
3use anyhow::Result;
4
5use crate::jobstore::{JobDir, resolve_root};
6use crate::schema::{Response, TailData};
7
8/// Options for the `tail` sub-command.
9#[derive(Debug)]
10pub struct TailOpts<'a> {
11    pub job_id: &'a str,
12    pub root: Option<&'a str>,
13    /// Number of lines to show from the end of each log.
14    pub tail_lines: u64,
15    /// Maximum bytes to read from the end of each log.
16    pub max_bytes: u64,
17    pub compression_mode: crate::compress::CompressionMode,
18}
19
20impl<'a> Default for TailOpts<'a> {
21    fn default() -> Self {
22        TailOpts {
23            job_id: "",
24            root: None,
25            tail_lines: 50,
26            max_bytes: 65536,
27            compression_mode: crate::compress::CompressionMode::default(),
28        }
29    }
30}
31
32/// Execute `tail`: read log tails and emit JSON.
33pub fn execute(opts: TailOpts) -> Result<()> {
34    let root = resolve_root(opts.root);
35    let job_dir = JobDir::open(&root, opts.job_id)?;
36
37    let stdout_log_path = job_dir.stdout_path();
38    let stderr_log_path = job_dir.stderr_path();
39
40    // Use the shared helper so that byte metric calculation is in one place.
41    let stdout = job_dir.read_tail_metrics("stdout.log", opts.tail_lines, opts.max_bytes);
42    let stderr = job_dir.read_tail_metrics("stderr.log", opts.tail_lines, opts.max_bytes);
43    let meta = job_dir.read_meta()?;
44    let compression = crate::compress::compress(crate::compress::CompressionInput {
45        command: &meta.command,
46        stdout: &stdout.tail,
47        stderr: &stderr.tail,
48        stdout_original_bytes: stdout.observed_bytes,
49        stderr_original_bytes: stderr.observed_bytes,
50        mode: opts.compression_mode,
51    });
52
53    let response = Response::new(
54        "tail",
55        TailData {
56            job_id: job_dir.job_id.clone(),
57            stdout: stdout.tail,
58            stderr: stderr.tail,
59            encoding: "utf-8-lossy".to_string(),
60            stdout_log_path: stdout_log_path.display().to_string(),
61            stderr_log_path: stderr_log_path.display().to_string(),
62            stdout_range: stdout.range,
63            stderr_range: stderr.range,
64            stdout_total_bytes: stdout.observed_bytes,
65            stderr_total_bytes: stderr.observed_bytes,
66            compression,
67        },
68    );
69    response.print();
70    Ok(())
71}