dango 0.1.1

Application skeleton
Documentation
//
// Copyright © 2022, Oleg Lelenkov
// License: BSD 3-Clause
// Authors: Oleg Lelenkov
//

use std::path::{Path, PathBuf};

use uninode::UniNode;

use tracing::Metadata;
use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
use tracing_appender::rolling::{RollingFileAppender, Rotation};
use tracing_subscriber::fmt::writer::{BoxMakeWriter, MakeWriter};

use super::LoggerError;

struct NonBlokingWriter {
    writer: NonBlocking,
    _guard: WorkerGuard,
}

impl<'a> MakeWriter<'a> for NonBlokingWriter {
    type Writer = NonBlocking;

    fn make_writer(&self) -> Self::Writer {
        self.writer.make_writer()
    }

    fn make_writer_for(&'a self, meta: &Metadata) -> Self::Writer {
        self.writer.make_writer_for(meta)
    }
}

fn create_console_appender(cfg: &UniNode) -> Result<BoxMakeWriter, LoggerError> {
    if cfg.find_bool("sync").unwrap_or(false) {
        Ok(BoxMakeWriter::new(std::io::stdout))
    } else {
        let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout());
        let writer = NonBlokingWriter {
            writer: non_blocking,
            _guard: guard,
        };
        Ok(BoxMakeWriter::new(writer))
    }
}

struct BlockingFileWriter {
    rotation: Rotation,
    directory: PathBuf,
    file_name_prefix: PathBuf,
}

impl<'a> MakeWriter<'a> for BlockingFileWriter {
    type Writer = RollingFileAppender;

    fn make_writer(&self) -> Self::Writer {
        RollingFileAppender::new(
            self.rotation.clone(),
            self.directory.clone(),
            self.file_name_prefix.clone(),
        )
    }
}

fn create_file_appender(cfg: &UniNode) -> Result<BoxMakeWriter, LoggerError> {
    let file_path = cfg
        .find_str("path")
        .map(Path::new)
        .ok_or(LoggerError::InvalidOuputPath)?;

    let dir = file_path
        .parent()
        .and_then(|dir| dir.canonicalize().ok())
        .ok_or(LoggerError::InvalidOuputPath)?;

    let file = file_path
        .file_name()
        .map(Path::new)
        .ok_or(LoggerError::InvalidOuputPath)?;

    let rotation = cfg
        .find_str("rotation")
        .map(|v| v.to_lowercase())
        .unwrap_or_else(|| String::from("never"));

    let rotation = match rotation.as_str() {
        "minutely" => Rotation::MINUTELY,
        "hourly" => Rotation::HOURLY,
        "daily" => Rotation::DAILY,
        _ => Rotation::NEVER,
    };

    if cfg.find_bool("sync").unwrap_or(false) {
        let writer = BlockingFileWriter {
            rotation,
            directory: dir,
            file_name_prefix: file.to_path_buf(),
        };
        Ok(BoxMakeWriter::new(writer))
    } else {
        let file_appender = RollingFileAppender::new(rotation, dir, file);
        let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
        Ok(BoxMakeWriter::new(NonBlokingWriter {
            writer: non_blocking,
            _guard: guard,
        }))
    }
}

pub fn create_appender(cfg: &UniNode) -> Result<BoxMakeWriter, LoggerError> {
    let appender = cfg
        .find_str("appender")
        .map(|v| v.to_lowercase())
        .ok_or(LoggerError::NotDefineAppender)?;

    match appender.as_str() {
        "file" => create_file_appender(cfg),
        "console" => create_console_appender(cfg),
        _ => Err(LoggerError::UnknownAppender(appender)),
    }
}