rotation_logger/rotation_logger/
settings.rs1use std::{cmp::min, path::PathBuf};
44
45use chrono::Local;
46
47use crate::rotation_logger::logger::Message;
48
49#[derive(Debug, Clone)]
53pub struct Settings {
54 is_enabled: bool,
56 formatter: MessageFormatter,
58 output: OutputChannel,
60 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#[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#[derive(Debug, Clone)]
152pub struct MessageFormatter {
153 timestamp: String,
156 _masks: Vec<FormatMask>,
158 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 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 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(×tamp, &mask.length);
199 let timestamp =
200 self._format_by_width_align(×tamp, &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#[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#[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#[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#[derive(Debug, Clone)]
372pub enum OutputChannel {
373 File(FileSettings),
375 Console,
377 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#[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}