Skip to main content

rlg/config/
log_rotation.rs

1// log_rotation.rs
2// Copyright © 2024-2026 RustLogs (RLG). All rights reserved.
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! The [`LogRotation`] policy and its `size:N` / `time:N` / `date` /
6//! `count:N` string form.
7
8use super::ConfigError;
9use serde::{Deserialize, Serialize};
10use std::fmt;
11use std::num::NonZeroU64;
12use std::str::FromStr;
13
14/// Log rotation policy variants.
15#[derive(
16    Clone,
17    Copy,
18    Debug,
19    Deserialize,
20    Serialize,
21    Eq,
22    PartialEq,
23    Ord,
24    PartialOrd,
25    Hash,
26)]
27pub enum LogRotation {
28    /// Size-based log rotation.
29    Size(NonZeroU64),
30    /// Time-based log rotation.
31    Time(NonZeroU64),
32    /// Date-based log rotation.
33    Date,
34    /// Count-based log rotation.
35    Count(u32),
36}
37
38impl FromStr for LogRotation {
39    type Err = ConfigError;
40
41    fn from_str(s: &str) -> Result<Self, Self::Err> {
42        let trimmed = s.trim();
43        let (kind, value) = trimmed
44            .split_once(':')
45            .map_or((trimmed, None), |(k, v)| (k, Some(v)));
46        match kind.to_lowercase().as_str() {
47            "size" => rotation_amount(value, "size").map(Self::Size),
48            "time" => rotation_amount(value, "time").map(Self::Time),
49            "date" => Ok(Self::Date),
50            "count" => rotation_count(value).map(Self::Count),
51            _ => Err(ConfigError::ValidationError(format!(
52                "Invalid log rotation option: '{s}'"
53            ))),
54        }
55    }
56}
57
58/// The non-zero number after `size:` or `time:`; `what` names it in
59/// the error messages.
60fn rotation_amount(
61    value: Option<&str>,
62    what: &str,
63) -> Result<NonZeroU64, ConfigError> {
64    let invalid = |msg: String| ConfigError::ValidationError(msg);
65    let text = value.ok_or_else(|| {
66        invalid(format!("Missing {what} value for log rotation"))
67    })?;
68    let n = text.parse::<u64>().map_err(|_| {
69        invalid(format!(
70            "Invalid {what} value for log rotation: '{text}'"
71        ))
72    })?;
73    NonZeroU64::new(n).ok_or_else(|| {
74        invalid(format!("Log rotation {what} must be greater than 0"))
75    })
76}
77
78/// The non-zero count after `count:`, saturating at `u32::MAX`.
79fn rotation_count(value: Option<&str>) -> Result<u32, ConfigError> {
80    let invalid = |msg: String| ConfigError::ValidationError(msg);
81    let text = value.ok_or_else(|| {
82        invalid("Missing count value for log rotation".to_string())
83    })?;
84    let n = text.parse::<usize>().map_err(|_| {
85        invalid(format!(
86            "Invalid count value for log rotation: '{text}'"
87        ))
88    })?;
89    if n == 0 {
90        return Err(invalid(
91            "Log rotation count must be greater than 0".to_string(),
92        ));
93    }
94    Ok(n.try_into().unwrap_or(u32::MAX))
95}
96
97impl fmt::Display for LogRotation {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        match self {
100            Self::Size(size) => write!(f, "Size: {size} bytes"),
101            Self::Time(seconds) => write!(f, "Time: {seconds} seconds"),
102            Self::Date => write!(f, "Date-based rotation"),
103            Self::Count(count) => write!(f, "Count: {count} logs"),
104        }
105    }
106}