daoyi_cloud_common/config/
log_config.rs

1// https://github.com/clia/tracing-config/blob/main/src/lib.rs
2use serde::Deserialize;
3use tracing_appender::non_blocking::WorkerGuard;
4use tracing_subscriber::fmt;
5
6use tracing_appender::rolling;
7
8use super::default_true;
9
10const FORMAT_PRETTY: &str = "pretty";
11const FORMAT_COMPACT: &str = "compact";
12const FORMAT_JSON: &str = "json";
13const FORMAT_FULL: &str = "full";
14
15#[derive(Deserialize, Clone, Debug)]
16pub struct LogConfig {
17    #[serde(default = "default_filter_level")]
18    pub filter_level: String,
19    #[serde(default = "default_true")]
20    pub with_ansi: bool,
21    #[serde(default = "default_true")]
22    pub stdout: bool,
23    #[serde(default = "default_directory")]
24    pub directory: String,
25    #[serde(default = "default_file_name")]
26    pub file_name: String,
27    #[serde(default = "default_rolling")]
28    pub rolling: String,
29    #[serde(default = "default_format")]
30    pub format: String,
31    #[serde(default = "default_true")]
32    pub with_level: bool,
33    #[serde(default = "default_true")]
34    pub with_target: bool,
35    #[serde(default = "default_true")]
36    pub with_thread_ids: bool,
37    #[serde(default = "default_true")]
38    pub with_thread_names: bool,
39    #[serde(default = "default_true")]
40    pub with_source_location: bool,
41}
42fn default_filter_level() -> String {
43    "info".into()
44}
45fn default_directory() -> String {
46    "./logs".into()
47}
48fn default_file_name() -> String {
49    "app.log".into()
50}
51fn default_rolling() -> String {
52    "daily".into()
53}
54fn default_format() -> String {
55    FORMAT_FULL.into()
56}
57
58impl Default for LogConfig {
59    fn default() -> Self {
60        Self {
61            filter_level: default_filter_level(),
62            with_ansi: true,
63            stdout: false,
64            directory: default_directory(),
65            file_name: default_file_name(),
66            rolling: default_rolling(),
67            format: default_format(),
68            with_level: true,
69            with_target: true,
70            with_thread_ids: true,
71            with_thread_names: true,
72            with_source_location: true,
73        }
74    }
75}
76
77#[allow(dead_code)]
78impl LogConfig {
79    /// Will try_from_default_env while not setted.
80    ///
81    /// You can use value like "info", or something like "mycrate=trace".
82    ///
83    /// Default value if "info".
84    ///
85    pub fn filter_level(mut self, filter_level: &str) -> Self {
86        self.filter_level = filter_level.to_owned();
87        self
88    }
89
90    /// Show ANSI color symbols.
91    pub fn with_ansi(mut self, with_ansi: bool) -> Self {
92        self.with_ansi = with_ansi;
93        self
94    }
95
96    /// Will append log to stdout.
97    pub fn stdout(mut self, stdout: bool) -> Self {
98        self.stdout = stdout;
99        self
100    }
101
102    /// Set log file directory.
103    pub fn directory(mut self, directory: impl Into<String>) -> Self {
104        self.directory = directory.into();
105        self
106    }
107
108    /// Set log file name.
109    pub fn file_name(mut self, file_name: impl Into<String>) -> Self {
110        self.file_name = file_name.into();
111        self
112    }
113
114    /// Valid values: minutely | hourly | daily | never
115    ///
116    /// Will panic on other values.
117    pub fn rolling(mut self, rolling: impl Into<String>) -> Self {
118        let rolling = rolling.into();
119        if !["minutely", "hourly", "daily", "never"].contains(&&*rolling) {
120            panic!("Unknown rolling")
121        }
122        self.rolling = rolling;
123        self
124    }
125
126    /// Valid values: pretty | compact | json | full
127    ///
128    /// Will panic on other values.
129    pub fn format(mut self, format: impl Into<String>) -> Self {
130        let format = format.into();
131        if format != FORMAT_PRETTY
132            && format != FORMAT_COMPACT
133            && format != FORMAT_JSON
134            && format != FORMAT_FULL
135        {
136            panic!("Unknown format")
137        }
138        self.format = format;
139        self
140    }
141
142    /// include levels in formatted output
143    pub fn with_level(mut self, with_level: bool) -> Self {
144        self.with_level = with_level;
145        self
146    }
147
148    /// include targets
149    pub fn with_target(mut self, with_target: bool) -> Self {
150        self.with_target = with_target;
151        self
152    }
153
154    /// include the thread ID of the current thread
155    pub fn with_thread_ids(mut self, with_thread_ids: bool) -> Self {
156        self.with_thread_ids = with_thread_ids;
157        self
158    }
159
160    /// include the name of the current thread
161    pub fn with_thread_names(mut self, with_thread_names: bool) -> Self {
162        self.with_thread_names = with_thread_names;
163        self
164    }
165
166    /// include source location
167    pub fn with_source_location(mut self, with_source_location: bool) -> Self {
168        self.with_source_location = with_source_location;
169        self
170    }
171
172    /// Init tracing log.
173    ///
174    /// Caller should hold the guard.
175    pub fn guard(&self) -> WorkerGuard {
176        // Tracing appender init.
177        let file_appender = match &*self.rolling {
178            "minutely" => rolling::minutely(&self.directory, &self.file_name),
179            "hourly" => rolling::hourly(&self.directory, &self.file_name),
180            "daily" => rolling::daily(&self.directory, &self.file_name),
181            "never" => rolling::never(&self.directory, &self.file_name),
182            _ => rolling::never(&self.directory, &self.file_name),
183        };
184        let (file_writer, guard) = tracing_appender::non_blocking(file_appender);
185
186        // Tracing subscriber init.
187        let subscriber = tracing_subscriber::fmt()
188            .with_env_filter(
189                tracing_subscriber::EnvFilter::try_from_default_env()
190                    .unwrap_or(tracing_subscriber::EnvFilter::new(&self.filter_level)),
191            )
192            .with_ansi(self.with_ansi);
193
194        if self.format == FORMAT_PRETTY {
195            let subscriber = subscriber.event_format(
196                fmt::format()
197                    .with_timer(fmt::time::OffsetTime::new(
198                        time::OffsetDateTime::now_local().unwrap().offset(),
199                        time::format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]").unwrap(),
200                    ))
201                    .pretty()
202                    .with_level(self.with_level)
203                    .with_target(self.with_target)
204                    .with_thread_ids(self.with_thread_ids)
205                    .with_thread_names(self.with_thread_names)
206                    .with_source_location(self.with_source_location),
207            );
208            if self.stdout {
209                subscriber.with_writer(std::io::stdout).init();
210            } else {
211                subscriber.with_writer(file_writer).init();
212            };
213        } else if self.format == FORMAT_COMPACT {
214            let subscriber = subscriber.event_format(
215                fmt::format()
216                    .with_timer(fmt::time::OffsetTime::new(
217                        time::OffsetDateTime::now_local().unwrap().offset(),
218                        time::format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]").unwrap(),
219                    ))
220                    .compact()
221                    .with_level(self.with_level)
222                    .with_target(self.with_target)
223                    .with_thread_ids(self.with_thread_ids)
224                    .with_thread_names(self.with_thread_names)
225                    .with_source_location(self.with_source_location),
226            );
227            if self.stdout {
228                subscriber.with_writer(std::io::stdout).init();
229            } else {
230                subscriber.with_writer(file_writer).init();
231            };
232        } else if self.format == FORMAT_JSON {
233            let subscriber = subscriber.event_format(
234                fmt::format()
235                    .with_timer(fmt::time::OffsetTime::new(
236                        time::OffsetDateTime::now_local().unwrap().offset(),
237                        time::format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]").unwrap(),
238                    ))
239                    .json()
240                    .with_level(self.with_level)
241                    .with_target(self.with_target)
242                    .with_thread_ids(self.with_thread_ids)
243                    .with_thread_names(self.with_thread_names)
244                    .with_source_location(self.with_source_location),
245            );
246            if self.stdout {
247                subscriber.json().with_writer(std::io::stdout).init();
248            } else {
249                subscriber.json().with_writer(file_writer).init();
250            };
251        } else if self.format == FORMAT_FULL {
252            let subscriber = subscriber.event_format(
253                fmt::format()
254                    .with_timer(fmt::time::OffsetTime::new(
255                        time::OffsetDateTime::now_local().unwrap().offset(),
256                        time::format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]").unwrap(),
257                    ))
258                    .with_level(self.with_level)
259                    .with_target(self.with_target)
260                    .with_thread_ids(self.with_thread_ids)
261                    .with_thread_names(self.with_thread_names)
262                    .with_source_location(self.with_source_location),
263            );
264            if self.stdout {
265                subscriber.with_writer(std::io::stdout).init();
266            } else {
267                subscriber.with_writer(file_writer).init();
268            };
269        }
270
271        // Caller should hold this handler.
272        guard
273    }
274}