dango/system/logging/
appender.rs

1//
2// Copyright © 2022, Oleg Lelenkov
3// License: BSD 3-Clause
4// Authors: Oleg Lelenkov
5//
6
7use std::path::{Path, PathBuf};
8
9use uninode::UniNode;
10
11use tracing::Metadata;
12use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
13use tracing_appender::rolling::{RollingFileAppender, Rotation};
14use tracing_subscriber::fmt::writer::{BoxMakeWriter, MakeWriter};
15
16use super::LoggerError;
17
18struct NonBlokingWriter {
19    writer: NonBlocking,
20    _guard: WorkerGuard,
21}
22
23impl<'a> MakeWriter<'a> for NonBlokingWriter {
24    type Writer = NonBlocking;
25
26    fn make_writer(&self) -> Self::Writer {
27        self.writer.make_writer()
28    }
29
30    fn make_writer_for(&'a self, meta: &Metadata) -> Self::Writer {
31        self.writer.make_writer_for(meta)
32    }
33}
34
35fn create_console_appender(cfg: &UniNode) -> Result<BoxMakeWriter, LoggerError> {
36    if cfg.find_bool("sync").unwrap_or(false) {
37        Ok(BoxMakeWriter::new(std::io::stdout))
38    } else {
39        let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout());
40        let writer = NonBlokingWriter {
41            writer: non_blocking,
42            _guard: guard,
43        };
44        Ok(BoxMakeWriter::new(writer))
45    }
46}
47
48struct BlockingFileWriter {
49    rotation: Rotation,
50    directory: PathBuf,
51    file_name_prefix: PathBuf,
52}
53
54impl<'a> MakeWriter<'a> for BlockingFileWriter {
55    type Writer = RollingFileAppender;
56
57    fn make_writer(&self) -> Self::Writer {
58        RollingFileAppender::new(
59            self.rotation.clone(),
60            self.directory.clone(),
61            self.file_name_prefix.clone(),
62        )
63    }
64}
65
66fn create_file_appender(cfg: &UniNode) -> Result<BoxMakeWriter, LoggerError> {
67    let file_path = cfg
68        .find_str("path")
69        .map(Path::new)
70        .ok_or(LoggerError::InvalidOuputPath)?;
71
72    let dir = file_path
73        .parent()
74        .and_then(|dir| dir.canonicalize().ok())
75        .ok_or(LoggerError::InvalidOuputPath)?;
76
77    let file = file_path
78        .file_name()
79        .map(Path::new)
80        .ok_or(LoggerError::InvalidOuputPath)?;
81
82    let rotation = cfg
83        .find_str("rotation")
84        .map(|v| v.to_lowercase())
85        .unwrap_or_else(|| String::from("never"));
86
87    let rotation = match rotation.as_str() {
88        "minutely" => Rotation::MINUTELY,
89        "hourly" => Rotation::HOURLY,
90        "daily" => Rotation::DAILY,
91        _ => Rotation::NEVER,
92    };
93
94    if cfg.find_bool("sync").unwrap_or(false) {
95        let writer = BlockingFileWriter {
96            rotation,
97            directory: dir,
98            file_name_prefix: file.to_path_buf(),
99        };
100        Ok(BoxMakeWriter::new(writer))
101    } else {
102        let file_appender = RollingFileAppender::new(rotation, dir, file);
103        let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
104        Ok(BoxMakeWriter::new(NonBlokingWriter {
105            writer: non_blocking,
106            _guard: guard,
107        }))
108    }
109}
110
111pub fn create_appender(cfg: &UniNode) -> Result<BoxMakeWriter, LoggerError> {
112    let appender = cfg
113        .find_str("appender")
114        .map(|v| v.to_lowercase())
115        .ok_or(LoggerError::NotDefineAppender)?;
116
117    match appender.as_str() {
118        "file" => create_file_appender(cfg),
119        "console" => create_console_appender(cfg),
120        _ => Err(LoggerError::UnknownAppender(appender)),
121    }
122}