shared-framework 0.0.17

Reusable building blocks for HTTP services — Hyper routing, SeaORM data layer, validation, OpenAPI docs, jobs, queues, cache.
Documentation
//! Logger trait, console logger, and timed events.
//!
//! [`ILogger`] is the logging interface used across the framework.
//! [`ConsoleLogger`] is the `tracing`-backed implementation, [`MonitorLogger`]
//! delegates to it, and [`TimeEvent`] measures the duration of an operation.
//!
//! ```ignore
//! let log = ConsoleLogger::global();
//! log.info("server started").await;
//! let timer = log.time("load config");
//! let elapsed = timer.end();
//! ```

use async_trait::async_trait;
use std::time::Instant;

/// Logging interface with leveled async logging plus timed events.
#[async_trait]
pub trait ILogger: Send + Sync {
    /// Logs at info level.
    async fn info(&self, msg: &str);
    /// Logs at debug level (suppressed by [`ConsoleLogger`] in production).
    async fn debug(&self, msg: &str);
    /// Logs at warn level.
    async fn warn(&self, msg: &str);
    /// Logs at error level.
    async fn error(&self, msg: &str);
    /// Logs an executed action at info level with an `exec` marker.
    async fn exec(&self, msg: &str);
    /// Starts a [`TimeEvent`] that logs its duration when ended.
    fn time(&self, msg: &str) -> TimeEvent;
}

/// A named duration measurement. The duration is reported when [`TimeEvent::end`] runs.
pub struct TimeEvent {
    start: Instant,
    message: String,
    // callback on end
    on_end: Option<Box<dyn FnOnce(std::time::Duration) + Send>>,
}

impl TimeEvent {
    /// Starts a timer with `message` and an optional callback invoked by [`TimeEvent::end`].
    pub fn new(message: impl Into<String>, on_end: Option<Box<dyn FnOnce(std::time::Duration) + Send>>) -> Self {
        Self { start: Instant::now(), message: message.into(), on_end }
    }

    /// Returns the elapsed duration, consuming the event without invoking the callback.
    pub fn stop(self) -> std::time::Duration {
        self.start.elapsed()
    }

    /// Returns the elapsed duration and invokes the `on_end` callback, if any.
    pub fn end(self) -> std::time::Duration {
        let d = self.start.elapsed();
        if let Some(cb) = self.on_end {
            cb(d);
        }
        d
    }

    /// Returns the elapsed duration without consuming the event.
    pub fn elapsed(&self) -> std::time::Duration {
        self.start.elapsed()
    }

    /// Returns the label attached at creation.
    pub fn message(&self) -> &str {
        &self.message
    }
}

/// `tracing`-backed logger. Debug output is suppressed when `is_production` is true.
#[derive(Clone)]
pub struct ConsoleLogger {
    // In production debug is suppressed
    is_production: bool,
}

impl ConsoleLogger {
    /// Creates a logger; when `is_production` is true, [`ILogger::debug`] is a no-op.
    pub fn new(is_production: bool) -> Self {
        Self { is_production }
    }

    /// Creates a logger using the current [`AppEnvironment`](crate::env::AppEnvironment),
    /// defaulting to non-production when the environment is unavailable.
    pub fn global() -> Self {
        // Check env lazily
        let prod = crate::env::AppEnvironment::try_get().map(|e| e.is_production()).unwrap_or(false);
        Self::new(prod)
    }

    fn prefix() -> String {
        // Prefix log lines with the Tokio task id when available.
        "framework".to_string()
    }
}

#[async_trait]
impl ILogger for ConsoleLogger {
    async fn info(&self, msg: &str) {
        tracing::info!(target: "console", executor = %Self::prefix(), "{}", msg);
    }
    async fn debug(&self, msg: &str) {
        if self.is_production {
            return;
        }
        tracing::debug!(target: "console", executor = %Self::prefix(), "{}", msg);
    }
    async fn warn(&self, msg: &str) {
        tracing::warn!(target: "console", executor = %Self::prefix(), "{}", msg);
    }
    async fn error(&self, msg: &str) {
        tracing::error!(target: "console", executor = %Self::prefix(), "{}", msg);
    }
    async fn exec(&self, msg: &str) {
        tracing::info!(target: "console", executor = %Self::prefix(), exec = true, "{}", msg);
    }
    fn time(&self, msg: &str) -> TimeEvent {
        let msg_owned = msg.to_string();
        let label = msg_owned.clone();
        TimeEvent::new(msg_owned, Some(Box::new(move |d| {
            tracing::info!(target: "console", operation = %label, duration_ms = d.as_millis() as u64, "Operation completed");
        })))
    }
}

/// Structured logger that currently delegates to [`ConsoleLogger`].
pub struct MonitorLogger {
    inner: ConsoleLogger,
    // batching state omitted for brevity — retains interface
}

impl MonitorLogger {
    /// Creates a monitor logger; `is_production` controls debug suppression as in [`ConsoleLogger`].
    pub fn new(is_production: bool) -> Self {
        Self { inner: ConsoleLogger::new(is_production) }
    }
}

#[async_trait]
impl ILogger for MonitorLogger {
    async fn info(&self, msg: &str) { self.inner.info(msg).await }
    async fn debug(&self, msg: &str) { self.inner.debug(msg).await }
    async fn warn(&self, msg: &str) { self.inner.warn(msg).await }
    async fn error(&self, msg: &str) { self.inner.error(msg).await }
    async fn exec(&self, msg: &str) { self.inner.exec(msg).await }
    fn time(&self, msg: &str) -> TimeEvent { self.inner.time(msg) }
}