Skip to main content

ma_core/config/
logging.rs

1//! Logging initialisation for ma-core-based daemons.
2//!
3//! [`Config::init_logging`] sets up a two-layer [`tracing_subscriber`]
4//! registry:
5//! - A **file layer** that writes structured log lines at `config.log_level`
6//!   to `config.effective_log_file()`.
7//! - A **stdout layer** that writes human-readable log lines at
8//!   `config.log_level_stdout`.
9//!
10//! Call this once, early in `main`, before spawning any tasks.
11
12use tracing_subscriber::{
13    filter::LevelFilter, fmt, layer::SubscriberExt, prelude::*, util::SubscriberInitExt,
14};
15
16use crate::error::{Error, Result};
17
18use super::Config;
19
20impl Config {
21    /// Initialise the global `tracing` subscriber.
22    ///
23    /// Sets up:
24    /// - A file appender writing at `self.log_level` to
25    ///   `self.effective_log_file()`.
26    /// - A stdout writer writing at `self.log_level_stdout`.
27    ///
28    /// Uses [`try_init`](tracing_subscriber::util::SubscriberInitExt::try_init)
29    /// so that calling this more than once (e.g. in tests) does not panic.
30    pub fn init_logging(&self) -> Result<()> {
31        let file_level: LevelFilter = self
32            .log_level
33            .parse()
34            .map_err(|_| Error::Config(format!("invalid log_level: {}", self.log_level)))?;
35
36        let stdout_level: LevelFilter = self.log_level_stdout.parse().map_err(|_| {
37            Error::Config(format!(
38                "invalid log_level_stdout: {}",
39                self.log_level_stdout
40            ))
41        })?;
42
43        let log_path = self.effective_log_file()?;
44
45        // Ensure the log directory exists.
46        if let Some(parent) = log_path.parent() {
47            std::fs::create_dir_all(parent).map_err(|e| {
48                Error::Config(format!(
49                    "failed to create log directory {}: {e}",
50                    parent.display()
51                ))
52            })?;
53        }
54
55        let log_file = std::fs::OpenOptions::new()
56            .create(true)
57            .append(true)
58            .open(&log_path)
59            .map_err(|e| {
60                Error::Config(format!(
61                    "failed to open log file {}: {e}",
62                    log_path.display()
63                ))
64            })?;
65
66        let file_layer = fmt::layer()
67            .with_writer(std::sync::Mutex::new(log_file))
68            .with_ansi(false)
69            .with_filter(file_level);
70
71        let stdout_layer = fmt::layer()
72            .with_writer(std::io::stdout)
73            .with_filter(stdout_level);
74
75        tracing_subscriber::registry()
76            .with(file_layer)
77            .with(stdout_layer)
78            .try_init()
79            .map_err(|e| Error::Config(format!("failed to initialise logging: {e}")))?;
80
81        Ok(())
82    }
83}