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;