prettylogger/
output.rs

1//! Provides log stream implementations for directing log output to various
2//! destinations, such as files, standard error, or a log buffer.
3
4/// Provides log stream implementations for directing log output to various
5/// destinations, such as files, standard error, or a log buffer.
6use std::{
7    fs::OpenOptions,
8    sync::Mutex
9};
10
11use serde::{
12    Serialize,
13    Deserialize
14};
15
16use crate::{
17    Error,
18    config::{
19        LogStruct,
20        OnDropPolicy
21    },
22    format::LogFormatter,
23    fileio::{
24        append_to_file,
25        overwrite_file
26    },
27};
28
29/// Common trait for toggleable objects.
30pub trait Toggleable {
31    /// Enables the object.
32    fn enable(&mut self);
33    /// Disables the object.
34    fn disable(&mut self);
35    /// Returns whether the object is enabled.
36    fn is_enabled(&self) -> &bool;
37}
38
39/// Wraps `StderrStream`, `BufferStream` and `FileStream` in one object used
40/// internally by `Logger`.
41///
42/// # Examples
43///
44/// Printing log to `stderr`:
45/// ```
46/// # use prettylogger::{
47/// #     output::LogOutput,
48/// #     format::LogFormatter,
49/// #     config::LogStruct,
50/// # };
51/// // Required by `LogOutput` for parsing logs
52/// let mut formatter = LogFormatter::default();
53///
54/// // By default, only output to `stderr` is enabled
55/// let mut log_output = LogOutput::default();
56///
57/// // Print "Hello, World!" in a neat log format
58/// log_output.out(&LogStruct::debug("Hello, World!"), &mut formatter);
59/// ```
60#[derive(Debug, Serialize, Deserialize)]
61pub struct LogOutput {
62    /// The `stderr` output stream.
63    pub stderr_output: StderrStream,
64    /// File output stream for writing logs to a file.
65    pub file_output: Mutex<FileStream>,
66    /// Buffer stream for storing log messages.
67    pub buffer_output: Mutex<BufferStream>,
68
69    enabled: bool,
70}
71
72/// Used for printing logs to `stderr`.
73///
74/// # Examples
75///
76/// Printing a log to `stderr`:
77/// ```
78/// # use prettylogger::{
79/// #     output::StderrStream,
80/// #     format::LogFormatter,
81/// #     config::LogStruct,
82/// # };
83/// // Required by `StderrStream` for parsing logs
84/// let mut formatter = LogFormatter::default();
85///
86/// // Enabled by default
87/// let mut stderr_output = StderrStream::default();
88///
89/// // Print "Hello, World!" in a neat log format
90/// stderr_output.out(&LogStruct::debug("Hello, World!"), &mut formatter);
91/// ```
92#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize,
93    Deserialize)]
94pub struct StderrStream {
95    enabled: bool,
96}
97
98/// Used to output logs to a file.
99///
100/// # Examples
101///
102/// Writing a log to a file:
103/// ```
104/// # use prettylogger::{
105/// #     output::{FileStream, Toggleable},
106/// #     format::LogFormatter,
107/// #     config::LogStruct,
108/// # };
109/// # let mut path = std::env::temp_dir();
110/// # path.push("libprettylogger-tests/fo-struct-doc.log");
111/// # let path = &path.to_str().unwrap().to_string();
112/// // Required by `FileStream` for parsing logs
113/// let mut formatter = LogFormatter::default();
114///
115/// let mut file_output = FileStream::default();
116///
117/// // Set the log file path **first**
118/// file_output.set_log_file_path(&path)
119///     .expect("Failed to set the log file path!");
120///
121/// // Enable the output
122/// file_output.enable()
123///     .expect("Failed to enable the output!");
124///
125/// // Write to the log file buffer
126/// file_output.out(&LogStruct::debug("Hello from file!"), &mut formatter)
127///     .expect("Failed to write to the buffer!");
128///
129/// // Flush the logs from the buffer to the log file
130/// file_output.flush();
131/// ```
132#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize,
133    Deserialize)]
134pub struct FileStream {
135    enabled: bool,
136    max_buffer_size: Option<usize>,
137    on_drop_policy: OnDropPolicy,
138
139    #[serde(skip)]
140    lock_enabled: bool,
141    #[serde(skip)]
142    log_file_path: String,
143    #[serde(skip)]
144    log_buffer: Vec<String>,
145}
146
147/// Used for storing logs in a buffer for later use.
148///
149/// # Examples
150/// ```
151/// # use prettylogger::{
152/// #     output::{BufferStream, Toggleable},
153/// #     config::LogStruct,
154/// # };
155/// let mut buffer_output = BufferStream::default();
156///
157/// // `BufferStream` is disabled by default
158/// buffer_output.enable();
159///
160/// // A formatter is not needed since `BufferStream` stores raw logs
161/// buffer_output.out(&LogStruct::debug("Hello from buffer!"));
162///
163/// // Obtain a reference to the log buffer
164/// let buffer = buffer_output.get_log_buffer();
165/// ````
166#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize,
167    Deserialize, Default)]
168pub struct BufferStream {
169    enabled: bool,
170
171    #[serde(skip)]
172    pub(crate) log_buffer: Vec<LogStruct>,
173}
174
175impl Drop for FileStream {
176    fn drop(&mut self) {
177        let _ = self.internal_flush(true);
178    }
179}
180
181impl PartialEq for LogOutput {
182    fn eq(&self, other: &Self) -> bool {
183        return self.enabled == other.enabled &&
184            self.stderr_output == other.stderr_output;
185    }
186}
187
188impl Default for LogOutput {
189    fn default() -> Self {
190        LogOutput {
191            enabled: true,
192            stderr_output: StderrStream::default(),
193            file_output: FileStream::default().into(),
194            buffer_output: BufferStream::default().into(),
195        }
196    }
197}
198
199impl Default for StderrStream {
200    fn default() -> Self {
201        StderrStream {
202            enabled: true,
203        }
204    }
205}
206
207impl Default for FileStream {
208    fn default() -> Self {
209        FileStream {
210            enabled: false,
211            max_buffer_size: Some(128),
212            on_drop_policy: OnDropPolicy::default(),
213
214            lock_enabled: false,
215            log_file_path: String::from(""),
216            log_buffer: Vec::new(),
217        }
218    }
219}
220
221impl Toggleable for LogOutput {
222    /// Enables the output.
223    fn enable(&mut self) {
224        self.enabled = true;
225    }
226
227    /// Disables the output.
228    fn disable(&mut self) {
229        self.enabled = false;
230    }
231
232    /// Returns whether the output is enabled.
233    fn is_enabled(&self) -> &bool {
234        return &self.enabled;
235    }
236}
237
238impl Toggleable for StderrStream {
239    /// Enables the output.
240    fn enable(&mut self) {
241        self.enabled = true;
242    }
243
244    /// Disables the output.
245    fn disable(&mut self) {
246        self.enabled = false;
247    }
248
249    /// Returns whether the output is enabled.
250    fn is_enabled(&self) -> &bool {
251        return &self.enabled;
252    }
253}
254
255impl Toggleable for BufferStream {
256    /// Enables the output.
257    fn enable(&mut self) {
258        self.enabled = true;
259    }
260
261    /// Disables the output.
262    fn disable(&mut self) {
263        self.enabled = false;
264    }
265
266    /// Returns whether the output is enabled.
267    fn is_enabled(&self) -> &bool {
268        return &self.enabled;
269    }
270}
271
272impl LogOutput {
273    /// Passes the log and its formatter to child streams for processing.
274    pub fn out(&self, log: &LogStruct, formatter: &mut LogFormatter) {
275        if self.enabled {
276            self.stderr_output.out(log, formatter);
277            let _ = self.file_output.lock().unwrap().out(log, formatter);
278            self.buffer_output.lock().unwrap().out(log);
279        }
280    }
281}
282
283impl StderrStream {
284    /// Formats the given log using a formatter and prints it to `stderr`.
285    pub fn out(self, log: &LogStruct, formatter: &mut LogFormatter) {
286        if self.enabled {
287            eprint!("{}", formatter.format_log(log));
288        }
289    }
290}
291
292impl FileStream {
293    fn push_to_buffer(&mut self, log: String) -> Result<(), Error> {
294        if !self.enabled {
295            return Err(Error::new("Output disabled!"));
296        }
297
298        self.log_buffer.push(log);
299
300        match self.max_buffer_size {
301            Some(size) => {
302                if self.log_buffer.len() >= size {
303                    return self.internal_flush(false);
304                }
305                else {
306                    return Ok(());
307                }
308            },
309            None => Ok(()),
310        }
311    }
312
313    /// Write contents of the log buffer to the log file and clear the buffer.
314    fn append_to_log_file(&mut self) -> Result<(), Error> {
315        let buf = self.log_buffer.join("");
316        self.log_buffer = Vec::new();
317        return append_to_file(&self.log_file_path, &buf);
318    }
319
320    /// Handle flushing logic internally.
321    pub(crate) fn internal_flush(&mut self, is_drop_flush: bool) -> Result<(), Error> {
322        if !self.enabled {
323            return Err(Error::new("Output not enabled!"));
324        }
325
326        if self.log_buffer.is_empty() {
327            return Err(Error::new("Log buffer is empty!"));
328        }
329
330        if is_drop_flush {
331            if self.lock_enabled {
332                if self.on_drop_policy == OnDropPolicy::IgnoreLogFileLock {
333                    return self.append_to_log_file();
334                }
335                else {
336                    return Err(Error::new(
337                        &format!("Lock is enabled and on drop policy se to '{}'!",
338                        self.on_drop_policy)));
339                }
340            }
341            else {
342                return self.append_to_log_file();
343            }
344        }
345
346        if self.lock_enabled {
347            return Err(Error::new("Lock is enabled."));
348        }
349        else {
350            return self.append_to_log_file();
351        }
352    }
353
354    pub(crate) fn drop_flush(&mut self) {
355        let _ = self.internal_flush(true);
356    }
357
358    /// Sets the log file path.
359    ///
360    /// # Examples
361    /// ```
362    /// # use prettylogger::{
363    /// #     output::{FileStream, Toggleable},
364    /// #     format::LogFormatter,
365    /// #     config::LogStruct,
366    /// # };
367    /// # let mut path = std::env::temp_dir();
368    /// # path.push("libprettylogger-tests/fo-set_log_file_path-doc.log");
369    /// # let path = &path.to_str().unwrap().to_string();
370    /// # let formatter = LogFormatter::default();
371    /// # let mut file_output = FileStream::default();
372    ///
373    /// // Set the log file path **first**
374    /// file_output.set_log_file_path(&path)
375    ///     .expect("Failed to set the log file path!");
376    ///
377    /// // And then enable the output
378    /// file_output.enable()
379    ///     .expect("Failed to enable the output!");
380    /// ```
381    pub fn set_log_file_path(&mut self, path: &str) -> Result<(), Error> {
382        match OpenOptions::new().write(true).create(true).truncate(true).open(path) {
383            Ok(_) => {
384                self.log_file_path = path.to_string();
385                match overwrite_file(path, "") {
386                    Ok(_) => Ok(()),
387                    Err(e) => {
388                        return Err(Error::new(&e.message));
389                    }
390                }
391            },
392            Err(e) => Err(Error::new(&format!("{}", e))),
393        }
394    }
395
396    /// Formats the given log using a formatter and stores it in a buffer until
397    /// it is flushed.
398    ///
399    /// # Examples
400    /// ```
401    /// # use prettylogger::{
402    /// #     output::{FileStream, Toggleable},
403    /// #     format::LogFormatter,
404    /// #     config::LogStruct,
405    /// # };
406    /// # let mut path = std::env::temp_dir();
407    /// # path.push("libprettylogger-tests/fo-out-doc.log");
408    /// # let path = &path.to_str().unwrap().to_string();
409    /// # let mut formatter = LogFormatter::default();
410    /// # let mut file_output = FileStream::default();
411    ///
412    /// // Set the log file path **first**
413    /// file_output.set_log_file_path(&path)
414    ///     .expect("Failed to set the log file path!");
415    ///
416    /// // And then enable the output
417    /// file_output.enable()
418    ///     .expect("Failed to enable the output!");
419    ///
420    /// // Write to the buffer 100 times
421    /// for i in 0..100 {
422    ///     file_output.out(&LogStruct::debug(&format!("Log number {}", i)),
423    ///         &mut formatter).expect("Failed to write to the buffer!");
424    /// }
425    ///
426    /// // Write the log buffer contents to the log file
427    /// file_output.flush();
428    /// ```
429    pub fn out(&mut self, log: &LogStruct, formatter: &mut LogFormatter)
430        -> Result<(), Error> {
431        return self.push_to_buffer(formatter.format_log(log));
432    }
433
434    /// Flush the contents of the log buffer to the log file.
435    ///
436    /// # Examples
437    /// ```
438    /// # use prettylogger::{
439    /// #     output::{FileStream, Toggleable},
440    /// #     format::LogFormatter,
441    /// #     config::LogStruct,
442    /// # };
443    /// # let mut path = std::env::temp_dir();
444    /// # path.push("libprettylogger-tests/fo-out-doc.log");
445    /// # let path = &path.to_str().unwrap().to_string();
446    /// # let mut formatter = LogFormatter::default();
447    /// # let mut file_output = FileStream::default();
448    ///
449    /// // Set the log file path **first**
450    /// file_output.set_log_file_path(&path)
451    ///     .expect("Failed to set the log file path!");
452    ///
453    /// // And then enable the output
454    /// file_output.enable()
455    ///     .expect("Failed to enable the output!");
456    ///
457    /// file_output.out(&LogStruct::debug(&format!("Hello from file!")),
458    ///     &mut formatter).expect("Failed to write to the buffer!");
459    ///
460    /// // Write the log buffer contents to the log file
461    /// file_output.flush();
462    /// ```
463    pub fn flush(&mut self) -> Result<(), Error> {
464        return self.internal_flush(false);
465    }
466
467    /// Sets the maximum size of the log buffer.
468    ///
469    /// When the buffer exceeds this size, its contents are written to a file
470    /// and then cleared.
471    ///
472    /// # Examples
473    /// ```
474    /// # use prettylogger::{
475    /// #     output::{FileStream, Toggleable},
476    /// #     format::LogFormatter,
477    /// #     config::LogStruct,
478    /// # };
479    /// # let mut path = std::env::temp_dir();
480    /// # path.push("libprettylogger-tests/fo-set_max_buffer_size-doc.log");
481    /// # let path = &path.to_str().unwrap().to_string();
482    /// # let mut formatter = LogFormatter::default();
483    /// # let mut file_output = FileStream::default();
484    /// // Set the log file path **first**
485    /// file_output.set_log_file_path(&path)
486    ///     .expect("Failed to set the log file path!");
487    ///
488    /// // And then enable the output
489    /// file_output.enable()
490    ///     .expect("Failed to enable the output!");
491    ///
492    /// // Define the maximum buffer size
493    /// let max_size = 128;
494    /// file_output.set_max_buffer_size(Some(max_size));
495    /// for i in 0..max_size {
496    ///     // Write to the buffer
497    ///     file_output.out(&LogStruct::debug(&format!("Log number {}", i)),
498    ///         &mut formatter).expect("Failed to write to the buffer!");
499    /// }
500    /// // Here the buffer will be flushed to the log file.
501    /// ```
502    pub fn set_max_buffer_size<I: Into<Option<usize>>>(&mut self, size: I) {
503        self.max_buffer_size = size.into();
504    }
505
506    /// Enables the output.
507    ///
508    /// Returns an error if the log file is not writable.
509    ///
510    /// # Examples
511    /// ```
512    /// # use prettylogger::{
513    /// #     output::{FileStream, Toggleable},
514    /// #     format::LogFormatter,
515    /// #     config::LogStruct,
516    /// # };
517    /// # let mut path = std::env::temp_dir();
518    /// # path.push("libprettylogger-tests/fo-enable-doc.log");
519    /// # let path = &path.to_str().unwrap().to_string();
520    /// # let formatter = LogFormatter::default();
521    /// # let mut file_output = FileStream::default();
522    ///
523    /// // Set the log file path **first**
524    /// file_output.set_log_file_path(&path)
525    ///     .expect("Failed to set the log file path!");
526    ///
527    /// // And then enable the output
528    /// file_output.enable()
529    ///     .expect("Failed to enable the output!");
530    /// ```
531    pub fn enable(&mut self) -> Result<(), Error> {
532        if self.enabled {
533            return Ok(());
534        }
535        else {
536            match OpenOptions::new().write(true).create(true).truncate(true)
537            .open(&self.log_file_path) {
538                Ok(_) => {
539                    self.enabled = true;
540                    return Ok(());
541                },
542                Err(e) => Err(Error::new(&format!("{}", e))),
543            }
544        }
545    }
546
547    /// Disables the output.
548    pub fn disable(&mut self) {
549        self.enabled = false;
550    }
551
552    /// Sets the policy for handling the log buffer lock when the stream is
553    /// dropped.
554    pub fn set_on_drop_policy<I: Into<OnDropPolicy>>(&mut self, policy: I) {
555        self.on_drop_policy = policy.into();
556    }
557
558    /// Locks the log file, preventing it from being written to.
559    pub fn lock_file(&mut self) {
560        self.lock_enabled = true;
561    }
562
563    /// Unlocks the log file, allowing the stream to write to it.
564    pub fn unlock_file(&mut self) {
565        self.lock_enabled = false;
566    }
567
568    /// Returns whether the output is enabled.
569    pub fn is_enabled(&self) -> &bool {
570        return &self.enabled;
571    }
572}
573
574impl BufferStream {
575    /// Formats the given log using a formatter and stores it in a buffer.
576    pub fn out(&mut self, log: &LogStruct) {
577        if self.enabled {
578            self.log_buffer.push(log.clone());
579        }
580    }
581
582    /// Returns a reference to the internal log struct buffer.
583    pub fn get_log_buffer(&self) -> &Vec<LogStruct> {
584        return &self.log_buffer;
585    }
586
587    /// Clears the log buffer.
588    pub fn clear(&mut self) {
589        self.log_buffer = Vec::new();
590    }
591}