sinan 0.1.0

A Boilerplate for Rapid Axum Web Service Deployment.
Documentation
use tracing_appender::non_blocking::WorkerGuard;
use tracing_appender::rolling::{RollingFileAppender, Rotation};
use tracing_subscriber::{fmt, layer::SubscriberExt};
use tracing_subscriber::fmt::time::OffsetTime;
use tracing_subscriber::util::SubscriberInitExt;

use crate::config::Format;
use crate::contracts::{Application, Service};
use crate::foundation::application::config;

pub struct Logger(WorkerGuard);

impl Service for Logger {
    fn register<A: Application + ?Sized>() -> Self
    where
        Self: Sized,
    {
        let logger = config().unwrap().get::<crate::config::Logger>("logger").unwrap();

        let local_offset_time = OffsetTime::local_rfc_3339().expect("could not get local offset");

        let (non_blocking_appender, worker_guard) = match logger.channel.as_str() {
            "file" => tracing_appender::non_blocking(file_layer(&logger)),
            "stdout" => tracing_appender::non_blocking(std::io::stdout()),
            _ => panic!("invalid log channel"),
        };

        let fmt_layer = fmt::layer()
            .with_level(true)
            .with_ansi(logger.display_ansi)
            .with_file(logger.display_file)
            .with_line_number(logger.display_line_number)
            .with_thread_ids(logger.display_thread_ids)
            .with_thread_names(logger.display_thread_names)
            .with_target(logger.display_target)
            .with_timer(local_offset_time)
            .with_writer(non_blocking_appender);

        let subscriber = tracing_subscriber::registry();

        match logger.format {
            Format::Compact => subscriber.with(fmt_layer.compact()).init(),
            Format::Pretty => subscriber.with(fmt_layer.pretty()).init(),
            _ => subscriber.with(fmt_layer).init(),
        }

        Self(worker_guard)
    }
}

fn file_layer(config: &crate::config::Logger) -> RollingFileAppender {
    let log_rolling_period = std::time::Duration::from_secs(3600 * 24 * 90); // 90 days
    let log_rotation = "daily";

    let rolling_period_minutes = log_rolling_period.as_secs().div_ceil(60);
    let (rotation, max_log_files) = match log_rotation {
        "minutely" => (Rotation::MINUTELY, rolling_period_minutes),
        "hourly" => (Rotation::HOURLY, rolling_period_minutes.div_ceil(60)),
        "daily" => (Rotation::DAILY, rolling_period_minutes.div_ceil(60 * 24)),
        _ => (Rotation::NEVER, 1),
    };

    RollingFileAppender::builder()
        .rotation(rotation)
        .filename_prefix(&config.file_prefix)
        .filename_suffix("log")
        .max_log_files(max_log_files.try_into().unwrap_or(1))
        .build(&config.file_path)
        .expect("fail to initialize the rolling file appender")
}