Skip to main content

xianyun_cli/commands/
logs.rs

1//! `caproni logs`: view edit mode reloader logs.
2//!
3//! Port of Go's `cmd/logs` package.
4
5use anyhow::Context as _;
6use clap::Args;
7use xianyun_core::config::global::Context;
8use xianyun_core::config::latest::CaproniConfig;
9
10#[derive(Args, Debug)]
11pub struct LogsArgs {
12    /// Number of lines to show from the end of the logs.
13    #[arg(short = 'n', long = "tail", default_value_t = 25)]
14    pub tail: i64,
15
16    /// Don't print prefix in logs.
17    #[arg(long = "no-log-prefix")]
18    pub no_log_prefix: bool,
19
20    /// Process names to filter logs to.
21    pub proc_names: Vec<String>,
22}
23
24pub fn run(args: &LogsArgs, ctx: &Context) -> anyhow::Result<()> {
25    run_logs(ctx, args.proc_names.clone(), args.tail, args.no_log_prefix)
26}
27
28/// Port of `logs.runLogs`: loads the configuration, resolves which
29/// edit-mode process logs to tail (explicit names, or every mirror
30/// reloader's edit-mode processes when none are given), and prints each
31/// tailed line to stdout.
32pub fn run_logs(
33    ctx: &Context,
34    proc_names: Vec<String>,
35    max_lines: i64,
36    no_log_prefix: bool,
37) -> anyhow::Result<()> {
38    let container =
39        xianyun_core::config::caproni::load_from_context(ctx).context("loading configuration")?;
40
41    let logs_dir = std::path::Path::new(&container.global.config_file)
42        .parent()
43        .map_or_else(
44            || xianyun_executor::editmodelogger::DIR_NAME.to_string(),
45            |dir| {
46                dir.join(xianyun_executor::editmodelogger::DIR_NAME)
47                    .to_string_lossy()
48                    .into_owned()
49            },
50        );
51
52    let proc_names = if proc_names.is_empty() {
53        get_edit_mode_process_names(&container.caproni)
54    } else {
55        proc_names
56    };
57
58    for proc_name in proc_names {
59        let lines = xianyun_executor::editmodelogger::tail(&proc_name, &logs_dir, max_lines)
60            .context("tailing logs")?;
61
62        for line in lines {
63            let line = if no_log_prefix {
64                line.strip_prefix(&format!("[{proc_name}] "))
65                    .unwrap_or(&line)
66                    .to_string()
67            } else {
68                line
69            };
70
71            println!("{line}");
72        }
73    }
74
75    Ok(())
76}
77
78/// Port of `logs.getEditModeProcessNames`: collects the names of every
79/// edit-mode process configured across every mirror reloader with
80/// `edit_mode: true`.
81#[must_use]
82pub fn get_edit_mode_process_names(caproni_cfg: &CaproniConfig) -> Vec<String> {
83    let mut names = Vec::new();
84
85    if let Some(reloaders) = &caproni_cfg.reloaders {
86        for reloader in reloaders.values() {
87            if reloader.edit_mode
88                && let Some(mirror) = &reloader.mirror
89                && let Some(processes) = &mirror.edit_mode_processes
90            {
91                names.extend(processes.keys());
92            }
93        }
94    }
95
96    names
97}