demo_vk/app/
logging.rs

1use {
2    anyhow::{Context, Result},
3    flexi_logger::{
4        Criterion, DeferredNow, Duplicate, FileSpec, Logger, LoggerHandle,
5        Naming, Record, WriteMode,
6    },
7    regex::Regex,
8    std::{
9        fmt::Write as FmtWrite,
10        path::{Path, PathBuf},
11        sync::LazyLock,
12    },
13    textwrap::{termwidth, Options},
14};
15
16#[derive(Debug)]
17pub struct Location {
18    pub file: &'static str,
19    pub line: u32,
20    pub col: u32,
21}
22
23impl std::fmt::Display for Location {
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        f.write_fmt(format_args!("{}:{}:[{}]", self.file, self.line, self.col))
26    }
27}
28
29pub trait ErrorLocationMessage {
30    fn location(self, location: Location, msg: impl AsRef<str>) -> Self;
31}
32
33impl<T> ErrorLocationMessage for Result<T> {
34    fn location(self, location: Location, msg: impl AsRef<str>) -> Self {
35        self.with_context(|| format!("{} - {}", location, msg.as_ref()))
36    }
37}
38
39// Used to remove verbose base dir prefix from log messages with file
40// information.
41static RELATIVE_ROOT_DIR: LazyLock<PathBuf> =
42    LazyLock::new(|| std::env::current_dir().unwrap());
43
44static LAST_NEWLINE_DELIM_MACHER: LazyLock<Regex> =
45    LazyLock::new(|| Regex::new(r"(│)(.*)$").unwrap());
46
47/// A global handle to the initialized flexi_logger.
48///
49/// This gets setup on the first call to setup_logger().
50static LOGGER_HANDLE: LazyLock<LoggerHandle> = LazyLock::new(|| {
51    Logger::try_with_env_or_str(
52        "debug, tracing::span=off, winit=warn, sctk=warn",
53    )
54    .unwrap()
55    .log_to_stdout()
56    .duplicate_to_stdout(Duplicate::All)
57    .log_to_file(FileSpec::default().directory("logs"))
58    .rotate(
59        Criterion::AgeOrSize(flexi_logger::Age::Hour, 1024 * 1024 * 8),
60        Naming::Timestamps,
61        flexi_logger::Cleanup::KeepLogFiles(2),
62    )
63    .format(multiline_format)
64    .write_mode(WriteMode::Async)
65    .start()
66    .expect("Unable to start the logger!")
67});
68
69/// Initialize the multiline logger.
70pub fn setup() {
71    LOGGER_HANDLE.flush();
72}
73
74/// A multiline log format for flexi_logger.
75///
76/// Logs are automatically wrapped at terminal width and prefixed with unicode
77/// so it's easy to tell where a big log statement begins and ends.
78fn multiline_format(
79    w: &mut dyn std::io::Write,
80    now: &mut DeferredNow,
81    record: &Record,
82) -> Result<(), std::io::Error> {
83    let size = termwidth().min(80);
84    let wrap_options = Options::new(size)
85        .initial_indent("╭ ")
86        .subsequent_indent("│ ");
87
88    let file = Path::new(record.file().unwrap());
89    let file_message = if let Ok(relative_path) =
90        file.strip_prefix(RELATIVE_ROOT_DIR.as_path())
91    {
92        relative_path.to_str().unwrap().to_owned()
93    } else {
94        file.to_str().unwrap().to_owned()
95    };
96
97    let mut full_line = String::new();
98    writeln!(
99        full_line,
100        "{} [{}] {}:{}",
101        record.level(),
102        now.now().format("%H:%M:%S%.3f"),
103        file_message,
104        record.line().unwrap_or(0),
105    )
106    .expect("unable to format first log line");
107
108    write!(&mut full_line, "{}", &record.args())
109        .expect("unable to format log!");
110
111    let wrapped = textwrap::fill(&full_line, wrap_options);
112    let formatted = LAST_NEWLINE_DELIM_MACHER.replace(&wrapped, "╰$2");
113
114    writeln!(w, "{formatted}")
115}