1mod global;
5pub use global::*;
6pub use syslog_client::syslog::Facility;
7
8use chrono::prelude::*;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13use std::fmt::Debug;
14use std::fs::File;
15use std::io::{self, BufWriter, Write};
16use std::os::unix::fs::PermissionsExt;
17use std::path::Path;
18use std::sync::{Arc, Mutex};
19
20use syslog_client::syslog::header::*;
21use syslog_client::writer::transport::*;
22use syslog_client::*;
23
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
27pub enum LogLevel {
28 Debug,
29 Info,
30 Warning,
31 Error,
32 Critical,
33}
34
35pub trait LogOutput: Debug + Write + Send + Sync {}
36impl<T: Debug + Write + Send + Sync> LogOutput for T {}
37
38pub trait Logging: Send + Sync {
39 fn output(&self, level: LogLevel, text: &str) -> Result<(), io::Error>;
41
42 fn flush(&self) -> Result<(), io::Error>;
44
45 fn error(&self, text: &str) -> Result<(), io::Error> {
47 self.output(LogLevel::Error, text)
48 }
49
50 fn warning(&self, text: &str) -> Result<(), io::Error> {
52 self.output(LogLevel::Warning, text)
53 }
54
55 fn info(&self, text: &str) -> Result<(), io::Error> {
57 self.output(LogLevel::Info, text)
58 }
59
60 fn debug(&self, text: &str) -> Result<(), io::Error> {
62 self.output(LogLevel::Debug, text)
63 }
64
65 fn critical(&self, text: &str) -> Result<(), io::Error> {
67 self.output(LogLevel::Critical, text)
68 }
69}
70
71#[derive(Default)]
74pub struct VoidLog {}
75
76impl Logging for VoidLog {
77 fn output(&self, _: LogLevel, _: &str) -> Result<(), io::Error> {
78 Ok(())
79 }
80
81 fn flush(&self) -> Result<(), io::Error> {
82 Ok(())
83 }
84}
85
86pub struct SysLog {
88 writer: Mutex<Rfc3164BufferedLogger<Unix<'static>>>,
89 min_level: LogLevel,
90}
91
92impl SysLog {
93 const MAPPING: [Severity; 5] = [
94 Severity::LOG_DEBUG,
95 Severity::LOG_INFO,
96 Severity::LOG_WARNING,
97 Severity::LOG_ERR,
98 Severity::LOG_CRIT,
99 ];
100 pub fn new(facility: Facility, tag: &str, min_logging_level: LogLevel) -> Option<Self> {
102 let hostname = hostname::get().unwrap().into_string().ok()?;
103 let syslog = Syslog::new(
104 facility,
105 Hostname::new(&hostname)?,
106 Tag::new(tag)?,
107 );
108 Some(Self {
109 min_level: min_logging_level,
110 writer: Mutex::new(
111 Rfc3164Logger::new(syslog, Unix::new_system()?).with_buffer(),
112 ),
113 })
114 }
115}
116
117impl Logging for SysLog {
118 fn output(&self, level: LogLevel, text: &str) -> Result<(), io::Error> {
119 if level < self.min_level {
120 return Ok(());
121 }
122 let writer = &mut *self.writer.lock().unwrap();
123 writer.write_str(Self::MAPPING[level as usize], text)
124 }
125
126 fn flush(&self) -> Result<(), io::Error> {
127 Ok(())
128 }
129}
130
131pub struct GenericLog {
132 file: Arc<Mutex<dyn LogOutput>>,
133 timestamp: bool,
134 min_logging_level: LogLevel,
135}
136
137impl GenericLog {
138 pub fn from_writer<T: LogOutput + 'static>(
140 writer: T,
141 timestamp: bool,
142 min_logging_level: LogLevel,
143 ) -> Self {
144 Self {
145 file: Arc::new(Mutex::new(writer)),
146 timestamp,
147 min_logging_level,
148 }
149 }
150}
151
152impl Logging for GenericLog {
153 fn output(&self, level: LogLevel, text: &str) -> Result<(), io::Error> {
154 if level < self.min_logging_level {
155 return Ok(());
156 }
157 let writer = &mut *self.file.lock().unwrap();
158 match self.timestamp {
159 true => writer.write_all(
160 format!(
161 "({}) [{level:?}]: {text}\n",
162 Utc::now().format("%Y.%m.%d-%H:%M:%S")
163 )
164 .as_bytes(),
165 )?,
166 false => writer.write_all(format!("[{level:?}]: {text}\n").as_bytes())?,
167 };
168 Ok(())
169 }
170
171 fn flush(&self) -> Result<(), io::Error> {
172 (*self.file.lock().unwrap()).flush()
173 }
174}
175
176impl Drop for GenericLog {
177 fn drop(&mut self) {
178 self.flush()
179 .map_err(|e| eprintln!("Error flushing log file! {e}"))
180 .ok();
181 }
182}
183
184pub struct FileLog {
186 file: Mutex<BufWriter<File>>,
187 timestamp: bool,
188 min_logging_level: LogLevel,
189}
190
191impl FileLog {
192 #[cfg(target_family = "unix")]
193 const FILE_MODE: u32 = 0o664;
194
195 pub fn new<P: AsRef<Path> + ?Sized>(
198 file: &P,
199 timestamp: bool,
200 min_logging_level: LogLevel,
201 ) -> Result<Self, io::Error> {
202 let fd = Self::get_file(file).map_err(|e| {
203 io::Error::new(
204 e.kind(),
205 format!(
206 "Logfile '{}' couldn't be opened/created.",
207 file.as_ref().display()
208 ),
209 )
210 })?;
211 Ok(Self {
212 file: Mutex::new(BufWriter::new(fd)),
213 timestamp,
214 min_logging_level,
215 })
216 }
217
218 fn get_file<P: AsRef<Path> + ?Sized>(file: &P) -> Result<File, io::Error> {
219 match file.as_ref().exists() {
220 true => std::fs::OpenOptions::new()
221 .append(true)
222 .read(false)
223 .open(file),
224 false => {
225 let f = File::create(file)?;
226 #[cfg(target_family = "unix")]
227 Self::set_perms(file)?;
228 Ok(f)
229 }
230 }
231 }
232
233 #[cfg(target_family = "unix")]
234 fn set_perms<P: AsRef<Path> + ?Sized>(path: &P) -> Result<(), io::Error> {
235 let perms = PermissionsExt::from_mode(Self::FILE_MODE);
236 std::fs::set_permissions(path, perms)
237 }
238
239 pub fn custom<S: AsRef<str>>(&self, text: S) -> Result<(), io::Error> {
241 let writer = &mut *self.file.lock().unwrap();
242 writer.write_all(text.as_ref().as_bytes())
243 }
244}
245
246impl Logging for FileLog {
247 fn output(&self, level: LogLevel, text: &str) -> Result<(), io::Error> {
248 if level < self.min_logging_level {
249 return Ok(());
250 }
251 let writer = &mut *self.file.lock().unwrap();
252 match self.timestamp {
253 true => writer.write_all(
254 format!(
255 "({}) [{level:?}]: {text}\n",
256 Utc::now().format("%Y.%m.%d-%H:%M:%S")
257 )
258 .as_bytes(),
259 )?,
260 false => writer.write_all(format!("[{level:?}]: {text}\n").as_bytes())?,
261 }
262 Ok(())
263 }
264
265 fn flush(&self) -> Result<(), io::Error> {
266 (*self.file.lock().unwrap()).flush()
267 }
268}
269
270impl Drop for FileLog {
271 fn drop(&mut self) {
272 self.flush()
273 .map_err(|e| eprintln!("Error flushing log file! {e}"))
274 .ok();
275 }
276}
277
278#[cfg(test)]
279mod test {
280 use crate::LogLevel;
281 #[test]
282 fn check_level() {
283 assert!(LogLevel::Info < LogLevel::Warning);
284 assert!(LogLevel::Error < LogLevel::Critical);
285 assert!(LogLevel::Error > LogLevel::Warning);
286 assert!(LogLevel::Info > LogLevel::Debug);
287 }
288}