Skip to main content

shared_framework/logging/
logger.rs

1//! Logger trait, console logger, and timed events.
2//!
3//! [`ILogger`] is the logging interface used across the framework.
4//! [`ConsoleLogger`] is the `tracing`-backed implementation, [`MonitorLogger`]
5//! delegates to it, and [`TimeEvent`] measures the duration of an operation.
6//!
7//! ```ignore
8//! let log = ConsoleLogger::global();
9//! log.info("server started").await;
10//! let timer = log.time("load config");
11//! let elapsed = timer.end();
12//! ```
13
14use async_trait::async_trait;
15use std::time::Instant;
16
17/// Logging interface with leveled async logging plus timed events.
18#[async_trait]
19pub trait ILogger: Send + Sync {
20    /// Logs at info level.
21    async fn info(&self, msg: &str);
22    /// Logs at debug level (suppressed by [`ConsoleLogger`] in production).
23    async fn debug(&self, msg: &str);
24    /// Logs at warn level.
25    async fn warn(&self, msg: &str);
26    /// Logs at error level.
27    async fn error(&self, msg: &str);
28    /// Logs an executed action at info level with an `exec` marker.
29    async fn exec(&self, msg: &str);
30    /// Starts a [`TimeEvent`] that logs its duration when ended.
31    fn time(&self, msg: &str) -> TimeEvent;
32}
33
34/// A named duration measurement. The duration is reported when [`TimeEvent::end`] runs.
35pub struct TimeEvent {
36    start: Instant,
37    message: String,
38    // callback on end
39    on_end: Option<Box<dyn FnOnce(std::time::Duration) + Send>>,
40}
41
42impl TimeEvent {
43    /// Starts a timer with `message` and an optional callback invoked by [`TimeEvent::end`].
44    pub fn new(message: impl Into<String>, on_end: Option<Box<dyn FnOnce(std::time::Duration) + Send>>) -> Self {
45        Self { start: Instant::now(), message: message.into(), on_end }
46    }
47
48    /// Returns the elapsed duration, consuming the event without invoking the callback.
49    pub fn stop(self) -> std::time::Duration {
50        self.start.elapsed()
51    }
52
53    /// Returns the elapsed duration and invokes the `on_end` callback, if any.
54    pub fn end(self) -> std::time::Duration {
55        let d = self.start.elapsed();
56        if let Some(cb) = self.on_end {
57            cb(d);
58        }
59        d
60    }
61
62    /// Returns the elapsed duration without consuming the event.
63    pub fn elapsed(&self) -> std::time::Duration {
64        self.start.elapsed()
65    }
66
67    /// Returns the label attached at creation.
68    pub fn message(&self) -> &str {
69        &self.message
70    }
71}
72
73/// `tracing`-backed logger. Debug output is suppressed when `is_production` is true.
74#[derive(Clone)]
75pub struct ConsoleLogger {
76    // In production debug is suppressed
77    is_production: bool,
78}
79
80impl ConsoleLogger {
81    /// Creates a logger; when `is_production` is true, [`ILogger::debug`] is a no-op.
82    pub fn new(is_production: bool) -> Self {
83        Self { is_production }
84    }
85
86    /// Creates a logger using the current [`AppEnvironment`](crate::env::AppEnvironment),
87    /// defaulting to non-production when the environment is unavailable.
88    pub fn global() -> Self {
89        // Check env lazily
90        let prod = crate::env::AppEnvironment::try_get().map(|e| e.is_production()).unwrap_or(false);
91        Self::new(prod)
92    }
93
94    fn prefix() -> String {
95        // Prefix log lines with the Tokio task id when available.
96        "framework".to_string()
97    }
98}
99
100#[async_trait]
101impl ILogger for ConsoleLogger {
102    async fn info(&self, msg: &str) {
103        tracing::info!(target: "console", executor = %Self::prefix(), "{}", msg);
104    }
105    async fn debug(&self, msg: &str) {
106        if self.is_production {
107            return;
108        }
109        tracing::debug!(target: "console", executor = %Self::prefix(), "{}", msg);
110    }
111    async fn warn(&self, msg: &str) {
112        tracing::warn!(target: "console", executor = %Self::prefix(), "{}", msg);
113    }
114    async fn error(&self, msg: &str) {
115        tracing::error!(target: "console", executor = %Self::prefix(), "{}", msg);
116    }
117    async fn exec(&self, msg: &str) {
118        tracing::info!(target: "console", executor = %Self::prefix(), exec = true, "{}", msg);
119    }
120    fn time(&self, msg: &str) -> TimeEvent {
121        let msg_owned = msg.to_string();
122        let label = msg_owned.clone();
123        TimeEvent::new(msg_owned, Some(Box::new(move |d| {
124            tracing::info!(target: "console", operation = %label, duration_ms = d.as_millis() as u64, "Operation completed");
125        })))
126    }
127}
128
129/// Structured logger that currently delegates to [`ConsoleLogger`].
130pub struct MonitorLogger {
131    inner: ConsoleLogger,
132    // batching state omitted for brevity — retains interface
133}
134
135impl MonitorLogger {
136    /// Creates a monitor logger; `is_production` controls debug suppression as in [`ConsoleLogger`].
137    pub fn new(is_production: bool) -> Self {
138        Self { inner: ConsoleLogger::new(is_production) }
139    }
140}
141
142#[async_trait]
143impl ILogger for MonitorLogger {
144    async fn info(&self, msg: &str) { self.inner.info(msg).await }
145    async fn debug(&self, msg: &str) { self.inner.debug(msg).await }
146    async fn warn(&self, msg: &str) { self.inner.warn(msg).await }
147    async fn error(&self, msg: &str) { self.inner.error(msg).await }
148    async fn exec(&self, msg: &str) { self.inner.exec(msg).await }
149    fn time(&self, msg: &str) -> TimeEvent { self.inner.time(msg) }
150}