baichun_framework_logger/
lib.rs1pub mod appender;
5pub mod config;
6pub mod error;
7
8#[cfg(test)]
9mod tests;
10
11use std::sync::Arc;
12use tracing_subscriber::layer::SubscriberExt;
13use tracing_subscriber::util::SubscriberInitExt;
14
15use crate::{
16 appender::LogAppender,
17 config::{LogFormat, LogLevel, RollingPolicy},
18 error::Result,
19};
20
21use self::appender::rolling::{RollingFileAppender, RollingStrategy};
22
23pub use config::{
24 ConsoleConfig, JsonFormatConfig, LoggerConfig, SizeRollingConfig, TextFormatConfig, TimePeriod,
25 TimeRollingConfig,
26};
27pub use error::LoggerError;
28
29#[derive(Clone, Debug)]
31pub struct Logger {
32 config: config::LoggerConfig,
34 appender: Arc<dyn LogAppender>,
36}
37
38impl Logger {
39 pub fn new(config: config::LoggerConfig) -> Result<Self> {
41 std::fs::create_dir_all(&config.dir)?;
43
44 let appender: Arc<dyn LogAppender> = if config.split_by_level {
45 let mut appenders = Vec::new();
47 for level in [
48 LogLevel::Trace,
49 LogLevel::Debug,
50 LogLevel::Info,
51 LogLevel::Warn,
52 LogLevel::Error,
53 ] {
54 let filename = format!("{}.{:?}", config.filename, level).to_lowercase();
55 let path = config.dir.join(&filename);
56
57 let appender: Arc<dyn LogAppender> = match config.rolling_policy {
58 RollingPolicy::Time(ref time_config) => Arc::new(RollingFileAppender::new(
59 path,
60 RollingStrategy::Time(time_config.clone()),
61 )?),
62 RollingPolicy::Size(ref size_config) => Arc::new(RollingFileAppender::new(
63 path,
64 RollingStrategy::Size(size_config.clone()),
65 )?),
66 RollingPolicy::Compound { ref time, ref size } => {
67 let time_appender = RollingFileAppender::new(
68 path.clone(),
69 RollingStrategy::Time(time.clone()),
70 )?;
71 let size_appender =
72 RollingFileAppender::new(path, RollingStrategy::Size(size.clone()))?;
73 Arc::new(CompoundAppender::new(time_appender, size_appender)?)
74 }
75 };
76 appenders.push((level, appender));
77 }
78 Arc::new(LevelSplitAppender::new(appenders))
79 } else {
80 let path = config.dir.join(&config.filename);
81 match config.rolling_policy {
82 RollingPolicy::Time(ref time_config) => Arc::new(RollingFileAppender::new(
83 path,
84 RollingStrategy::Time(time_config.clone()),
85 )?),
86 RollingPolicy::Size(ref size_config) => Arc::new(RollingFileAppender::new(
87 path,
88 RollingStrategy::Size(size_config.clone()),
89 )?),
90 RollingPolicy::Compound { ref time, ref size } => {
91 let time_appender = RollingFileAppender::new(
92 path.clone(),
93 RollingStrategy::Time(time.clone()),
94 )?;
95 let size_appender =
96 RollingFileAppender::new(path, RollingStrategy::Size(size.clone()))?;
97 Arc::new(CompoundAppender::new(time_appender, size_appender)?)
98 }
99 }
100 };
101
102 Ok(Self { config, appender })
103 }
104
105 pub fn write(&self, level: LogLevel, message: &str) -> Result<()> {
107 if level as u8 >= self.config.level as u8 {
109 let formatted_message = self.format_message(level, message)?;
110 self.appender.write(level, &formatted_message)?;
111 }
112 Ok(())
113 }
114
115 pub fn flush(&self) -> Result<()> {
117 self.appender.flush()
118 }
119
120 fn format_message(&self, level: LogLevel, message: &str) -> Result<String> {
122 match &self.config.format {
123 LogFormat::Text(config) => {
124 let mut parts = Vec::new();
125
126 let now = chrono::Local::now();
128 parts.push(now.format(&config.time_format).to_string());
129
130 if config.show_level {
132 parts.push(format!("[{}]", level));
133 }
134
135 if config.show_thread_id {
137 let thread = std::thread::current();
138 parts.push(format!("[Thread-{:?}]", thread.id()));
139 }
140
141 if config.show_target {
143 parts.push("[ruoyi-logger]".to_string());
144 }
145
146 if config.show_file || config.show_line {
148 let location = std::panic::Location::caller();
149 let mut file_info = String::new();
150 if config.show_file {
151 file_info.push_str(&format!("[{}]", location.file()));
152 }
153 if config.show_line {
154 file_info.push_str(&format!(":{}", location.line()));
155 }
156 parts.push(file_info);
157 }
158
159 parts.push(message.to_string());
161
162 Ok(parts.join(" "))
163 }
164 LogFormat::Json(config) => {
165 use serde_json::json;
166 let message_json = if message.trim().starts_with('{') {
168 match serde_json::from_str(message) {
169 Ok(json) => json,
170 Err(_) => json!(message),
171 }
172 } else {
173 json!(message)
174 };
175
176 let mut log_entry = json!({
177 "timestamp": chrono::Local::now().format(&config.time_format).to_string(),
178 "level": format!("{}", level),
179 });
180
181 if let Some(obj) = message_json.as_object() {
183 for (key, value) in obj {
184 log_entry[key] = value.clone();
185 }
186 } else {
187 log_entry["message"] = message_json;
188 }
189
190 if config.include_thread {
191 let thread = std::thread::current();
192 log_entry["thread_id"] = json!(format!("{:?}", thread.id()));
193 }
194
195 if config.include_caller {
196 let location = std::panic::Location::caller();
197 log_entry["caller"] = json!({
198 "file": location.file(),
199 "line": location.line(),
200 });
201 }
202
203 if config.pretty {
204 Ok(serde_json::to_string_pretty(&log_entry)?)
205 } else {
206 Ok(serde_json::to_string(&log_entry)?)
207 }
208 }
209 }
210 }
211}
212
213#[derive(Debug)]
215struct CompoundAppender {
216 time_appender: RollingFileAppender,
217 size_appender: RollingFileAppender,
218}
219
220impl CompoundAppender {
221 fn new(time_appender: RollingFileAppender, size_appender: RollingFileAppender) -> Result<Self> {
222 Ok(Self {
223 time_appender,
224 size_appender,
225 })
226 }
227}
228
229impl LogAppender for CompoundAppender {
230 fn write(&self, level: LogLevel, message: &str) -> Result<()> {
231 self.time_appender.write(level, message)?;
232 self.size_appender.write(level, message)
233 }
234
235 fn flush(&self) -> Result<()> {
236 self.time_appender.flush()?;
237 self.size_appender.flush()
238 }
239}
240
241impl Clone for CompoundAppender {
242 fn clone(&self) -> Self {
243 Self {
244 time_appender: self.time_appender.clone(),
245 size_appender: self.size_appender.clone(),
246 }
247 }
248}
249
250#[derive(Debug)]
252struct LevelSplitAppender {
253 appenders: Vec<(LogLevel, Arc<dyn LogAppender>)>,
254}
255
256impl LevelSplitAppender {
257 fn new(appenders: Vec<(LogLevel, Arc<dyn LogAppender>)>) -> Self {
258 Self { appenders }
259 }
260}
261
262impl LogAppender for LevelSplitAppender {
263 fn write(&self, level: LogLevel, message: &str) -> Result<()> {
264 if let Some((_, appender)) = self.appenders.iter().find(|(l, _)| *l == level) {
266 appender.write(level, message)?;
267 }
268 Ok(())
269 }
270
271 fn flush(&self) -> Result<()> {
272 for (_, appender) in &self.appenders {
273 appender.flush()?;
274 }
275 Ok(())
276 }
277}
278
279impl Clone for LevelSplitAppender {
280 fn clone(&self) -> Self {
281 Self {
282 appenders: self.appenders.clone(),
283 }
284 }
285}
286
287pub fn init(config: LoggerConfig) -> Result<()> {
289 let logger = Logger::new(config.clone())?;
291
292 let layer = LogLayer::new(logger);
294
295 tracing_subscriber::registry()
297 .with(layer)
298 .try_init()
299 .map_err(|e| error::LoggerError::Other(e.to_string()))?;
300
301 Ok(())
302}
303
304#[derive(Debug)]
306struct LogLayer {
307 logger: Logger,
308}
309
310impl LogLayer {
311 fn new(logger: Logger) -> Self {
312 Self { logger }
313 }
314}
315
316impl<S> tracing_subscriber::Layer<S> for LogLayer
317where
318 S: tracing::Subscriber,
319{
320 fn on_event(
321 &self,
322 event: &tracing::Event<'_>,
323 _ctx: tracing_subscriber::layer::Context<'_, S>,
324 ) {
325 let level = event.metadata().level();
327 let level = match *level {
328 tracing::Level::ERROR => LogLevel::Error,
329 tracing::Level::WARN => LogLevel::Warn,
330 tracing::Level::INFO => LogLevel::Info,
331 tracing::Level::DEBUG => LogLevel::Debug,
332 tracing::Level::TRACE => LogLevel::Trace,
333 };
334
335 let mut message = String::new();
337 let mut visitor = MessageVisitor(&mut message);
338 event.record(&mut visitor);
339
340 if let Err(e) = self.logger.write(level, &message) {
342 eprintln!("Failed to write log: {}", e);
343 }
344 }
345}
346
347struct MessageVisitor<'a>(&'a mut String);
349
350impl<'a> tracing::field::Visit for MessageVisitor<'a> {
351 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
352 if field.name() == "message" {
353 self.0.push_str(&format!("{:?}", value));
354 }
355 }
356
357 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
358 if field.name() == "message" {
359 self.0.push_str(value);
360 }
361 }
362}