Skip to main content

rtb_cli/
runtime.rs

1//! Runtime wiring helpers: tracing registry install, signal binding.
2
3use std::io::IsTerminal;
4use std::sync::Once;
5
6use tokio_util::sync::CancellationToken;
7use tracing_subscriber::prelude::*;
8use tracing_subscriber::EnvFilter;
9
10/// Ensure the tracing subscriber is installed exactly once per process.
11static TRACING_INIT: Once = Once::new();
12
13/// Log format selector — driven by the `--log-format` flag or the
14/// `log.format` config key.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum LogFormat {
17    /// Pretty, colourised, human-readable. Default on TTY stderr.
18    Pretty,
19    /// Newline-delimited JSON. Default when stderr is not a TTY.
20    Json,
21}
22
23impl LogFormat {
24    /// Auto-select based on stderr TTY detection.
25    #[must_use]
26    pub fn auto() -> Self {
27        if std::io::stderr().is_terminal() {
28            Self::Pretty
29        } else {
30            Self::Json
31        }
32    }
33}
34
35/// Install the framework's `tracing-subscriber` registry. Idempotent —
36/// a second call is a no-op (respecting the `Once`-gated install).
37pub fn install_tracing(format: LogFormat) {
38    TRACING_INIT.call_once(|| {
39        let env_filter =
40            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
41
42        let registry = tracing_subscriber::registry().with(env_filter);
43
44        match format {
45            LogFormat::Pretty => {
46                let layer = tracing_subscriber::fmt::layer()
47                    .with_target(false)
48                    .compact()
49                    .with_writer(std::io::stderr);
50                let _ = registry.with(layer).try_init();
51            }
52            LogFormat::Json => {
53                let layer = tracing_subscriber::fmt::layer()
54                    .json()
55                    .with_target(true)
56                    .with_writer(std::io::stderr);
57                let _ = registry.with(layer).try_init();
58            }
59        }
60    });
61}
62
63/// Spawn a task that cancels `token` on `SIGINT` (and on Unix,
64/// `SIGTERM`). Returns immediately; the spawned task lives until
65/// either signal fires or the runtime shuts down.
66pub fn bind_shutdown_signals(token: CancellationToken) {
67    tokio::spawn(async move {
68        let ctrl_c = async {
69            if let Err(e) = tokio::signal::ctrl_c().await {
70                tracing::warn!(error = %e, "failed to install Ctrl-C handler");
71            }
72        };
73
74        #[cfg(unix)]
75        {
76            use tokio::signal::unix::{signal, SignalKind};
77            let mut term = match signal(SignalKind::terminate()) {
78                Ok(s) => s,
79                Err(e) => {
80                    tracing::warn!(error = %e, "failed to install SIGTERM handler");
81                    ctrl_c.await;
82                    token.cancel();
83                    return;
84                }
85            };
86            tokio::select! {
87                () = ctrl_c => tracing::info!("received Ctrl-C — shutting down"),
88                _ = term.recv() => tracing::info!("received SIGTERM — shutting down"),
89            }
90        }
91
92        #[cfg(not(unix))]
93        {
94            ctrl_c.await;
95            tracing::info!("received Ctrl-C — shutting down");
96        }
97
98        token.cancel();
99    });
100}