Skip to main content

rotation_logger/rotation_logger/
settings.rs

1//! # Settings and support data for `Logger` setup.
2//!
3//! `Logs Formatter` support five `Mask Types`(mask_type) you can operate with:
4//! - timestamp: represent timestamp of logged data. Time will be taken when logged message received by logger, so it not 100% accurate when event occurred.
5//! - splitter: represent splitter symbol which will separate every `Mask`
6//! - modules: list of modules that was source of log data
7//! - message: log message it self
8//!
9//! Each `Mask Type` except `splitter` accept format syntax after `:` char:
10//! `{<mask_type:<mask_length>_<mask_width>_<mask_align>>}`
11//! - mask_length: length of string. On positive value limit string length from begin, on negative value from end.
12//! - mask_width: width of column for this Mask Type.
13//! - mask_align: vertical align for text on this column. Possible values: left, center, right.
14//!
15//! # Example:
16//!
17//! ```
18//! MessageFormatter::new(
19//!     "::",
20//!     "{timestamp:-6:30:right}{splitter}{modules:_:_:left}{splitter}{message}",
21//!     "%Y-%m-%d %H:%M:%S.%f",
22//! );
23//!
24//! ```
25//!
26//! `Logs Output` supported options: file, console, auto
27//! - file: all logs data will be store to logs file with declared settings.
28//! - console: output to console
29//! - auto: will use console when in develop mode and file on release.
30//!
31//! # Example:
32//!
33//! ```
34//! OutputChannel::file(
35//!     "./".into(),
36//!     10,
37//!     FileSize::from_megabytes(5),
38//!     "new_logger".into(),
39//!     "log".into(),
40//! );
41//! ```
42//!
43use std::{cmp::min, path::PathBuf};
44
45use chrono::Local;
46
47use crate::rotation_logger::logger::Message;
48
49/// Settings for data format and output of `Logger`.
50/// All Settings must be set before `Logger` start and cant be changed during work.
51/// `Enabled` or `Disabled` `Logger` can be used to log data, but in case of `Disabled Logger` nothing will happen.
52#[derive(Debug, Clone)]
53pub struct Settings {
54    /// Setting initial Logger Type
55    is_enabled: bool,
56    /// Format for output logging string
57    formatter: MessageFormatter,
58    /// Output direction to store logs
59    output: OutputChannel,
60    /// Accumulating buffer size.
61    /// Buffer actually is a `Vec<String>::len` window, which will be accumulated before flushing into file.
62    buffer_size: usize,
63}
64
65impl Settings {
66    pub fn new(
67        is_enabled: bool,
68        buffer_size: usize,
69        output: OutputChannel,
70        formatter: MessageFormatter,
71    ) -> Self {
72        Self {
73            is_enabled,
74            output,
75            formatter,
76            buffer_size,
77        }
78    }
79
80    pub fn format_message(&self, message: &Message) -> String {
81        self.formatter.format(message)
82    }
83
84    pub fn buffer_size(&self) -> usize {
85        self.buffer_size
86    }
87
88    pub fn is_enabled(&self) -> bool {
89        self.is_enabled
90    }
91
92    pub fn output(&self) -> &OutputChannel {
93        &self.output
94    }
95}
96
97impl Default for Settings {
98    fn default() -> Self {
99        Self {
100            is_enabled: true,
101            output: Default::default(),
102            formatter: Default::default(),
103            buffer_size: 2048,
104        }
105    }
106}
107
108/// File Size wrapper for easier declaration
109/// Store bits size.
110/// Inner data stored as Bits value.
111#[derive(Debug, Clone)]
112pub struct FileSize {
113    size: usize,
114}
115
116impl FileSize {
117    pub fn from_bytes(bytes: usize) -> Self {
118        Self { size: bytes * 8 }
119    }
120
121    pub fn from_kilobytes(kilobytes: usize) -> Self {
122        Self {
123            size: kilobytes * 8 * 1000,
124        }
125    }
126    pub fn from_megabytes(megabytes: usize) -> Self {
127        Self {
128            size: megabytes * 8 * 1000 * 1000,
129        }
130    }
131    pub fn from_gigabytes(gigabytes: usize) -> Self {
132        Self {
133            size: gigabytes * 8 * 1000 * 1000 * 1000,
134        }
135    }
136}
137
138impl Default for FileSize {
139    fn default() -> Self {
140        Self::from_megabytes(2)
141    }
142}
143
144impl PartialEq<u64> for FileSize {
145    fn eq(&self, other: &u64) -> bool {
146        *other == self.size as u64
147    }
148}
149
150/// Formatted for Log Message.
151#[derive(Debug, Clone)]
152pub struct MessageFormatter {
153    /// Timestamp format.
154    /// Support Chrono timestamp formats.
155    timestamp: String,
156    /// List of parsed Mask with set format values.
157    _masks: Vec<FormatMask>,
158    /// SPlitter symbols
159    splitter: String,
160}
161
162impl Default for MessageFormatter {
163    fn default() -> Self {
164        let format = "{timestamp} {splitter} {modules} {splitter} {message}";
165        Self {
166            timestamp: "%Y-%m-%d %H:%M:%S.%f".to_string(),
167            splitter: "::".into(),
168            _masks: Self::_set_masks(format),
169        }
170    }
171}
172
173impl MessageFormatter {
174    pub fn new(splitter: &str, format: &str, timestamp: &str) -> Self {
175        Self {
176            timestamp: timestamp.into(),
177            splitter: splitter.into(),
178            _masks: Self::_set_masks(format),
179        }
180    }
181
182    /// Process input message with rules.
183    pub fn format(&self, message: &Message) -> String {
184        let mut result = "".to_string();
185
186        let timestamp = if !self.timestamp.is_empty() {
187            let timestamp = Local::now().format(&self.timestamp).to_string();
188            // let timestamp = timestamp[0..timestamp.len() - self.timestamp.limiter()].to_string();
189            timestamp
190        } else {
191            "".to_string()
192        };
193
194        for mask in &self._masks {
195            match &mask.mask_type {
196                MaskType::Raw(value) => result = format!("{result}{value}"),
197                MaskType::Timestamp => {
198                    let timestamp = self._format_by_length(&timestamp, &mask.length);
199                    let timestamp =
200                        self._format_by_width_align(&timestamp, &mask.width, &mask.align);
201                    result = format!("{result}{timestamp}");
202                }
203                MaskType::Message => {
204                    let message = self._format_by_length(&message.text(), &mask.length);
205                    let message = self._format_by_width_align(&message, &mask.width, &mask.align);
206                    result = format!("{result}{message}");
207                }
208                MaskType::Splitter => {
209                    result = format!("{result}{}", self.splitter);
210                }
211                MaskType::Modules => {
212                    let modules = message
213                        .modules()
214                        .join(format!("{}", self.splitter).as_str());
215
216                    let modules = self._format_by_length(&modules, &mask.length);
217                    let modules = self._format_by_width_align(&modules, &mask.width, &mask.align);
218                    result = format!("{result}{modules}");
219                }
220            }
221        }
222        result
223    }
224
225    fn _format_by_length(&self, value: &str, length: &i32) -> String {
226        if *length > 0 {
227            value[0..min(*length as usize, value.len())].to_string()
228        } else {
229            value[0..value.len() - min(length.unsigned_abs() as usize, value.len())].to_string()
230        }
231    }
232
233    fn _format_by_width_align(&self, value: &str, width: &usize, align: &TextAlign) -> String {
234        if value.len() >= *width {
235            return value[0..*width].to_string();
236        };
237
238        let free_space = width - value.len();
239        let (left_space, right_space) = match align {
240            TextAlign::Left => ("".to_string(), " ".repeat(free_space)),
241            TextAlign::Center => {
242                let half = (free_space / 2) as usize;
243                (" ".repeat(half), " ".repeat(free_space - half))
244            }
245            TextAlign::Right => (" ".repeat(free_space), "".to_string()),
246        };
247        format!("{left_space}{value}{right_space}")
248    }
249
250    fn _set_masks(format: &str) -> Vec<FormatMask> {
251        let mut result = vec![];
252        let format = format.to_string();
253        let mut format = format.as_str();
254        if !format.contains("{") || !format.contains("}") {
255            panic!("Format String wrong syntax: {format}")
256        }
257        while !format.is_empty() {
258            let opening_delimiter = format.find("{");
259            if let None = opening_delimiter {
260                result.push(FormatMask::from(format));
261                return result;
262            }
263            let opening_delimiter = opening_delimiter.unwrap();
264            if format[0..opening_delimiter].to_string() != "" {
265                result.push(FormatMask::from(&format[0..opening_delimiter]));
266            }
267
268            let close_delimiter = format.find("}");
269            if let None = close_delimiter {
270                result.push(FormatMask::from(format));
271                return result;
272            }
273            let close_delimiter = close_delimiter.unwrap();
274            let scoped_value = &format[opening_delimiter + 1..close_delimiter];
275            result.push(FormatMask::from(scoped_value));
276            format = &format[close_delimiter + 1..format.len()];
277        }
278        result
279    }
280}
281
282/// Format Mask with rules.
283#[derive(Debug, Clone)]
284struct FormatMask {
285    mask_type: MaskType,
286    length: i32,
287    width: usize,
288    align: TextAlign,
289}
290
291impl From<&str> for FormatMask {
292    fn from(value: &str) -> Self {
293        let splitted_data: Vec<&str> = value.split(":").collect();
294        if splitted_data.len() > 4 {
295            panic!("Wrong Mask format: {value}")
296        }
297
298        let default_width = 30;
299        let default_length = 30;
300        let mask_type = splitted_data[0];
301        let length = splitted_data
302            .get(1)
303            .unwrap_or(&default_length.to_string().as_str())
304            .parse::<i32>()
305            .unwrap_or(default_length);
306        let width = splitted_data
307            .get(2)
308            .unwrap_or(&default_width.to_string().as_str())
309            .parse::<usize>()
310            .unwrap_or(default_width as usize);
311        let align = *splitted_data.get(3).unwrap_or(&"center");
312
313        Self {
314            mask_type: MaskType::from(mask_type),
315            length,
316            width,
317            align: TextAlign::from(align),
318        }
319    }
320}
321
322/// Type of Format Masks
323#[derive(Debug, Clone)]
324enum MaskType {
325    Raw(String),
326    Timestamp,
327    Message,
328    Splitter,
329    Modules,
330}
331
332impl From<&str> for MaskType {
333    fn from(value: &str) -> Self {
334        if value.to_lowercase() == "timestamp" {
335            Self::Timestamp
336        } else if value.to_lowercase() == "splitter" {
337            Self::Splitter
338        } else if value.to_lowercase() == "modules" {
339            Self::Modules
340        } else if value.to_lowercase() == "message" {
341            Self::Message
342        } else {
343            Self::Raw(value.to_string())
344        }
345    }
346}
347
348/// Text horizontal align.
349#[derive(Debug, Clone)]
350enum TextAlign {
351    Left,
352    Center,
353    Right,
354}
355
356impl From<&str> for TextAlign {
357    fn from(value: &str) -> Self {
358        if value.to_lowercase() == "left" {
359            Self::Left
360        } else if value.to_lowercase() == "right" {
361            Self::Right
362        } else if value.to_lowercase() == "center" {
363            Self::Center
364        } else {
365            Self::Center
366        }
367    }
368}
369
370/// Output Types for Logger.
371#[derive(Debug, Clone)]
372pub enum OutputChannel {
373    /// Store to files.
374    File(FileSettings),
375    /// Output to stdout.
376    Console,
377    /// If dev mode -> stdout, If release -> file
378    Auto(FileSettings),
379}
380
381impl Default for OutputChannel {
382    fn default() -> Self {
383        Self::Console
384    }
385}
386
387impl OutputChannel {
388    pub fn console() -> Self {
389        Self::Console
390    }
391    pub fn auto() -> Self {
392        Self::Console
393    }
394    pub fn file(
395        path: PathBuf,
396        capacity: usize,
397        file_size: FileSize,
398        filename: String,
399        file_extension: String,
400    ) -> Self {
401        Self::File(FileSettings::new(
402            path,
403            capacity,
404            file_size,
405            filename,
406            file_extension,
407        ))
408    }
409
410    pub fn settings(&self) -> Option<&FileSettings> {
411        match &self {
412            OutputChannel::File(file_output) => Some(file_output),
413            OutputChannel::Console => None,
414            OutputChannel::Auto(file_output) => Some(file_output),
415        }
416    }
417}
418
419/// Settings for logs files and rotation.
420#[derive(Debug, Clone)]
421pub struct FileSettings {
422    path: PathBuf,
423    capacity: usize,
424    file_size: FileSize,
425    filename: String,
426    file_extension: String,
427}
428
429impl FileSettings {
430    pub fn new(
431        path: PathBuf,
432        capacity: usize,
433        file_size: FileSize,
434        filename: String,
435        file_extension: String,
436    ) -> Self {
437        Self {
438            path,
439            capacity,
440            file_size,
441            filename,
442            file_extension,
443        }
444    }
445    pub fn path(&self) -> &PathBuf {
446        &self.path
447    }
448    pub fn filename(&self) -> &String {
449        &self.filename
450    }
451    pub fn file_extension(&self) -> &String {
452        &self.file_extension
453    }
454    pub fn file_size(&self) -> u64 {
455        self.file_size.size as u64
456    }
457    pub fn capacity(&self) -> usize {
458        self.capacity
459    }
460}
461
462impl Default for FileSettings {
463    fn default() -> Self {
464        Self {
465            path: PathBuf::from("./logs"),
466            capacity: 10,
467            file_size: Default::default(),
468            filename: "logger".into(),
469            file_extension: "log".into(),
470        }
471    }
472}