aether/
logger.rs

1use std::{collections::HashMap, fs::File, io::BufWriter, marker::PhantomData};
2
3use crate::{
4    builders::LoggerBuilder,
5    endpoint::{Endpoint, EndpointHash},
6    EndpointSuper, LOGGER,
7};
8
9pub struct Logger {
10    pub(crate) fmt: Box<dyn std::any::Any + std::marker::Send + std::marker::Sync>,
11    pub(crate) endpoints: HashMap<EndpointHash, Endpoint>,
12}
13
14impl Logger {
15    pub(crate) fn log(&mut self, target: EndpointHash, message: String) {
16        let endpoint = self
17            .endpoints
18            .get_mut(&target)
19            .expect("Attempted to use un-initialized endpoint");
20
21        if endpoint.disabled {
22            return;
23        }
24
25        if !endpoint.silent {
26            println!("{}", message);
27        }
28
29        if let Some(file) = &mut endpoint.file {
30            use std::io::Write;
31            writeln!(file, "{}", message).unwrap();
32        }
33    }
34}
35
36/// Dropping this will also drop the current logger.
37pub struct KeepAlive(pub(crate) PhantomData<()>);
38
39impl Drop for KeepAlive {
40    fn drop(&mut self) {
41        let mut guard = LOGGER.lock().unwrap();
42        let mut logger = std::mem::take(&mut *guard).unwrap();
43        for endpoint in logger.endpoints.values_mut() {
44            if let Some(file) = &mut endpoint.file {
45                use std::io::Write;
46                file.flush().unwrap();
47            }
48        }
49    }
50}
51
52pub(crate) fn setup_logger<EP: EndpointSuper>(builder: LoggerBuilder<EP>) {
53    if let Some(path) = &builder.base_path {
54        std::fs::create_dir_all(path).unwrap();
55    }
56
57    let mut existing = Vec::new();
58
59    for ep_builder in builder.endpoints.values() {
60        if let Some(path) = &ep_builder.path {
61            let path = builder.base_path.as_ref().unwrap().join(path);
62            if path.exists() {
63                existing.push(path);
64            }
65        }
66    }
67
68    if !existing.is_empty() {
69        #[cfg(feature = "archive")]
70        let mut archives = HashMap::new();
71
72        for path in existing {
73            #[cfg(feature = "archive")]
74            {
75                use std::io::{BufRead, BufReader};
76
77                let mut file = BufReader::new(File::open(&path).unwrap());
78                let mut header = String::new();
79                let date = match file.read_line(&mut header) {
80                        Ok(_) if header.trim_start().starts_with('%') && header.trim_end().ends_with('%') => {
81                            chrono::DateTime::parse_from_rfc2822(header.trim().trim_start_matches('%').trim_end_matches('%').trim()).expect("Unable to parse `time` field in old log header, aborting since we can't safely archive the target file.")
82                        },
83                        e => panic!("Failed to read log header, aborting since we can't safely archive the target file. {:?}", e)
84                    };
85
86                let archive = archives.entry(date).or_insert_with(|| {
87                    zip::ZipWriter::new(
88                        File::create(
89                            builder
90                                .base_path
91                                .as_ref()
92                                .unwrap()
93                                .join(format!("{}.zip", date)),
94                        )
95                        .unwrap(),
96                    )
97                });
98
99                archive
100                    .start_file(
101                        path.file_name().unwrap().to_str().unwrap(),
102                        zip::write::FileOptions::default(),
103                    )
104                    .unwrap();
105                std::io::copy(&mut file, archive).unwrap();
106            }
107
108            #[cfg(not(feature = "archive"))]
109            {
110                let mut new_path = path.clone();
111                while new_path.exists() {
112                    new_path.set_extension(
113                        new_path
114                            .extension()
115                            .unwrap_or_default()
116                            .to_str()
117                            .unwrap()
118                            .to_owned()
119                            + ".bk",
120                    );
121                }
122
123                std::fs::rename(path, new_path).unwrap();
124            }
125        }
126
127        #[cfg(feature = "archive")]
128        for (_, mut archive) in archives {
129            archive.finish().unwrap();
130        }
131    }
132
133    let creation_date = chrono::Utc::now().to_rfc2822();
134    let mut endpoints = HashMap::new();
135    for (hash, ep_builder) in builder.endpoints {
136        let file = ep_builder.path.map(|path| {
137            use std::io::Write;
138
139            let mut file = BufWriter::new(
140                File::create(builder.base_path.as_ref().unwrap().join(path))
141                    .expect("Unable to create log output file."),
142            );
143            writeln!(&mut file, "% {creation_date} %").unwrap();
144            file.flush().unwrap();
145            file
146        });
147
148        endpoints.insert(
149            hash,
150            Endpoint {
151                file,
152                silent: ep_builder.silent,
153                disabled: ep_builder.disabled,
154            },
155        );
156    }
157
158    let mut logger = LOGGER.lock().unwrap();
159    if logger
160        .replace(Logger {
161            fmt: Box::new(builder.fmt),
162            endpoints,
163        })
164        .is_some()
165    {
166        panic!("Logger was already initialized!");
167    }
168}