Skip to main content

acta/config/
mod.rs

1use std::collections::HashMap;
2use smart_default::SmartDefault;
3use std::path::PathBuf;
4
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
7#[derive(Clone, Debug, Default)]
8#[non_exhaustive]
9pub enum LogFormat {
10    Pretty,
11    #[default]
12    Compact,
13    Json,
14}
15
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
18#[derive(Clone, Copy, Debug, Default)]
19#[non_exhaustive]
20pub enum LogRotation {
21    #[default]
22    None,
23    Rename,
24    #[cfg(feature = "compress")]
25    Compress,
26}
27#[derive(Clone, Debug, PartialEq, Eq, Hash, derive_more::Display, derive_more::From)]
28pub struct FilterDirective(String);
29
30impl FilterDirective {
31    pub fn new(value: impl Into<String>) -> Self {
32        Self(value.into())
33    }
34
35    pub fn as_str(&self) -> &str {
36        &self.0
37    }
38}
39
40impl AsRef<str> for FilterDirective {
41    fn as_ref(&self) -> &str {
42        &self.0
43    }
44}
45
46#[derive(Clone, Debug)]
47#[non_exhaustive]
48pub enum LogLevel {
49    Error,
50    Warn,
51    Info,
52    Debug,
53    Trace,
54    Off,
55    Custom(FilterDirective),
56}
57
58impl LogLevel {
59    pub fn as_filter_directive(&self) -> &str {
60        match self {
61            Self::Error => "error",
62            Self::Warn => "warn",
63            Self::Info => "info",
64            Self::Debug => "debug",
65            Self::Trace => "trace",
66            Self::Off => "off",
67            Self::Custom(directive) => directive.as_str(),
68        }
69    }
70}
71
72#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73#[derive(Clone, Debug)]
74pub struct LogFilter {
75    pub level: LogLevel,
76    pub targets: HashMap<String, LogLevel>,
77}
78
79impl LogFilter {
80    pub fn new(level: LogLevel) -> Self {
81        Self {
82            level,
83            targets: HashMap::new(),
84        }
85    }
86
87    pub fn with_target_level(mut self, target: impl Into<String>, level: LogLevel) -> Self {
88        self.set_target_level(target, level);
89        self
90    }
91
92    pub fn set_target_level(&mut self, target: impl Into<String>, level: LogLevel) {
93        self.targets.insert(target.into(), level);
94    }
95
96    pub fn remove_target_level(&mut self, target: &str) -> bool {
97        self.targets.remove(target).is_some()
98    }
99
100    pub fn as_filter_directive(&self) -> String {
101        let mut directive = String::from(self.level.as_filter_directive());
102        for (target, level) in &self.targets {
103            directive.push(',');
104            directive.push_str(target);
105            directive.push('=');
106            directive.push_str(level.as_filter_directive());
107        }
108        directive
109    }
110}
111
112impl From<LogLevel> for LogFilter {
113    fn from(level: LogLevel) -> Self {
114        Self::new(level)
115    }
116}
117
118#[cfg(feature = "serde")]
119impl serde::Serialize for LogLevel {
120    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
121        serializer.serialize_str(self.as_filter_directive())
122    }
123}
124
125#[cfg(feature = "serde")]
126impl<'de> serde::Deserialize<'de> for LogLevel {
127    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
128        let s = String::deserialize(deserializer)?;
129        Ok(match s.as_str() {
130            "error" => Self::Error,
131            "warn" => Self::Warn,
132            "info" => Self::Info,
133            "debug" => Self::Debug,
134            "trace" => Self::Trace,
135            "off" => Self::Off,
136            other => Self::Custom(FilterDirective::new(other)),
137        })
138    }
139}
140
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142#[derive(Clone, Debug)]
143pub struct FileLoggingConfig {
144    pub path: PathBuf,
145    #[cfg_attr(feature = "serde", serde(default))]
146    pub rotation: LogRotation,
147}
148
149#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
150#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
151#[derive(Clone, Debug, Default)]
152#[non_exhaustive]
153pub enum ConsoleWriter {
154    #[default]
155    Stdout,
156    Stderr,
157    #[cfg(any(feature = "custom-async", feature = "native-async"))]
158    AsyncStdout(AsyncWriterMode),
159    #[cfg(any(feature = "custom-async", feature = "native-async"))]
160    AsyncStderr(AsyncWriterMode),
161}
162
163#[cfg(any(feature = "custom-async", feature = "native-async"))]
164#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
165#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
166#[derive(Clone, Copy, Debug, Default)]
167pub enum AsyncWriterMode {
168    #[cfg(feature = "custom-async")]
169    #[default]
170    Custom,
171    #[cfg(feature = "native-async")]
172    Native,
173}
174
175#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
176#[derive(Clone, Debug, SmartDefault)]
177pub struct ConsoleConfig {
178    #[default(LogFormat::default())]
179    pub format: LogFormat,
180    #[default = true]
181    #[cfg_attr(feature = "serde", serde(default = "default_true"))]
182    pub ansi: bool,
183    #[cfg_attr(feature = "serde", serde(default))]
184    pub writer: ConsoleWriter,
185    #[default = true]
186    #[cfg_attr(feature = "serde", serde(default = "default_true"))]
187    pub show_path: bool,
188    #[default = true]
189    #[cfg_attr(feature = "serde", serde(default = "default_true"))]
190    pub show_spans: bool,
191    #[cfg_attr(feature = "serde", serde(default))]
192    pub time_format: Option<String>,
193}
194
195#[cfg(feature = "serde")]
196fn default_true() -> bool {
197    true
198}
199
200#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
201#[derive(Clone, Debug, SmartDefault)]
202pub struct LoggingConfig {
203    #[default(LogLevel::Info)]
204    pub level: LogLevel,
205    #[default(Some(ConsoleConfig::default()))]
206    #[cfg_attr(feature = "serde", serde(default = "default_console"))]
207    pub console: Option<ConsoleConfig>,
208    #[cfg_attr(feature = "serde", serde(default))]
209    pub file: Option<FileLoggingConfig>,
210}
211
212#[cfg(feature = "serde")]
213fn default_console() -> Option<ConsoleConfig> {
214    Some(ConsoleConfig::default())
215}
216
217#[cfg(test)]
218mod test;