flexi_logger/logger_handle.rs
1#[cfg(feature = "buffer_writer")]
2use crate::writers::Snapshot;
3use crate::{
4 primary_writer::PrimaryWriter,
5 util::{eprint_err, ErrorCode},
6 writers::{FileLogWriterBuilder, FileLogWriterConfig, LogWriter},
7 Duplicate, FlexiLoggerError, LogSpecification,
8};
9#[cfg(feature = "specfile")]
10use notify_debouncer_mini::{notify::RecommendedWatcher, Debouncer};
11#[cfg(feature = "specfile")]
12use std::sync::Mutex;
13use std::{
14 collections::HashMap,
15 path::PathBuf,
16 sync::{Arc, RwLock},
17};
18
19/// Allows reconfiguring the logger while the program is running, and
20/// **shuts down the logger when it is dropped**.
21///
22/// A `LoggerHandle` is returned from `Logger::start()` and from `Logger::start_with_specfile()`.
23///
24/// Keep it alive until the very end of your program, because it shuts down the logger when
25/// its dropped!
26/// (This is only relevant if you use one of
27/// `Logger::log_to_file`, `Logger::log_to_writer`, or `Logger::log_to_file_and_writer`, or
28/// a buffering or asynchronous [`WriteMode`](crate::WriteMode)).
29///
30/// `LoggerHandle` offers methods to modify the log specification programmatically,
31/// to flush the logger explicitly, and to reconfigure the used `FileLogWriter` --
32/// if one is used.
33///
34/// # Examples
35///
36/// In more trivial configurations, dropping the `LoggerHandle` has no effect and then
37/// you can safely ignore the return value of `Logger::start()`:
38///
39/// ```rust
40/// use flexi_logger::Logger;
41/// use std::error::Error;
42/// fn main() -> Result<(), Box<dyn Error>> {
43/// Logger::try_with_str("info")?.start()?;
44/// // do work
45/// Ok(())
46/// }
47/// ```
48///
49/// When logging to a file or another writer,
50/// and/or if you use a buffering or asynchronous [`WriteMode`](crate::WriteMode),
51/// keep the `LoggerHandle` alive until the program ends:
52///
53/// ```rust
54/// use flexi_logger::{FileSpec, Logger};
55/// use std::error::Error;
56/// fn main() -> Result<(), Box<dyn Error>> {
57/// let _logger = Logger::try_with_str("info")?
58/// .log_to_file(FileSpec::default())
59/// .start()?;
60/// // do work
61/// Ok(())
62/// }
63/// ```
64///
65/// You can use the logger handle to permanently exchange the log specification programmatically,
66/// anywhere in your code:
67///
68/// ```rust
69/// # use flexi_logger::Logger;
70/// # use std::error::Error;
71/// # fn main() -> Result<(), Box<dyn Error>> {
72/// let logger = Logger::try_with_str("info")?.start()?;
73/// // ...
74/// logger.parse_new_spec("warn");
75/// // ...
76/// # Ok(())
77/// # }
78/// ```
79///
80/// However, when debugging, you might want to modify the log spec only temporarily, for
81/// one or few method calls only; this is easier done with the following method, because
82/// it allows switching back to the previous spec:
83///
84/// ```rust
85/// # use flexi_logger::Logger;
86/// # use std::error::Error;
87/// # fn main() -> Result<(), Box<dyn Error>> {
88/// let mut logger = Logger::try_with_str("info")?.start()?;
89/// logger.parse_and_push_temp_spec("trace");
90/// // ...
91/// // critical calls
92/// // ...
93/// logger.pop_temp_spec();
94/// // Continue with the log spec you had before.
95/// // ...
96/// # Ok(())
97/// # }
98/// ```
99#[derive(Clone)]
100pub struct LoggerHandle
101where
102 Self: Send + Sync,
103 // Note: we demand Send and Sync explicitly because we want to be able to move a
104 // `LoggerHandle` between threads.
105 // At least with notify_debouncer_mini version 0.4.1 this would not be given if we omitted
106 // the Mutex (which we don't need otherwise): we'd then get
107 // `std::sync::mpsc::Sender<notify_debouncer_mini::InnerEvent>` cannot be shared \
108 // between threads safely
109{
110 pub(crate) writers_handle: WritersHandle,
111 #[cfg(feature = "specfile")]
112 pub(crate) oam_specfile_watcher: Option<Arc<Mutex<Debouncer<RecommendedWatcher>>>>,
113}
114impl LoggerHandle {
115 pub(crate) fn new(
116 spec: Arc<RwLock<LogSpecification>>,
117 primary_writer: Arc<PrimaryWriter>,
118 other_writers: Arc<HashMap<String, Box<dyn LogWriter>>>,
119 ) -> Self {
120 Self {
121 writers_handle: WritersHandle {
122 spec,
123 spec_stack: Vec::default(),
124 primary_writer,
125 other_writers,
126 },
127 #[cfg(feature = "specfile")]
128 oam_specfile_watcher: None,
129 }
130 }
131
132 //
133 pub(crate) fn reconfigure(&self, max_level: log::LevelFilter) {
134 self.writers_handle.reconfigure(max_level);
135 }
136
137 /// Replaces the active `LogSpecification`.
138 pub fn set_new_spec(&self, new_spec: LogSpecification) {
139 self.writers_handle
140 .set_new_spec(new_spec)
141 .map_err(|e| eprint_err(ErrorCode::Poison, "rwlock on log spec is poisoned", &e))
142 .ok();
143 }
144
145 /// Tries to replace the active `LogSpecification` with the result from parsing the given String.
146 ///
147 /// # Errors
148 ///
149 /// [`FlexiLoggerError::Parse`] if the input is malformed.
150 pub fn parse_new_spec(&self, spec: &str) -> Result<(), FlexiLoggerError> {
151 self.set_new_spec(LogSpecification::parse(spec)?);
152 Ok(())
153 }
154
155 /// Replaces the active `LogSpecification` and pushes the previous one to a stack.
156 #[allow(clippy::missing_panics_doc)]
157 pub fn push_temp_spec(&mut self, new_spec: LogSpecification) {
158 self.writers_handle
159 .spec_stack
160 .push(self.writers_handle.spec.read().unwrap(/* catch and expose error? */).clone());
161 self.set_new_spec(new_spec);
162 }
163
164 /// Tries to replace the active `LogSpecification` with the result from parsing the given String
165 /// and pushes the previous one to a Stack.
166 ///
167 /// # Errors
168 ///
169 /// [`FlexiLoggerError::Parse`] if the input is malformed.
170 pub fn parse_and_push_temp_spec<S: AsRef<str>>(
171 &mut self,
172 new_spec: S,
173 ) -> Result<(), FlexiLoggerError> {
174 self.writers_handle.spec_stack.push(
175 self.writers_handle
176 .spec
177 .read()
178 .map_err(|_| FlexiLoggerError::Poison)?
179 .clone(),
180 );
181 self.set_new_spec(LogSpecification::parse(new_spec)?);
182 Ok(())
183 }
184
185 /// Reverts to the previous `LogSpecification`, if any.
186 pub fn pop_temp_spec(&mut self) {
187 if let Some(previous_spec) = self.writers_handle.spec_stack.pop() {
188 self.set_new_spec(previous_spec);
189 }
190 }
191
192 /// Flush all writers.
193 pub fn flush(&self) {
194 self.writers_handle.primary_writer.flush().ok();
195 for writer in self.writers_handle.other_writers.values() {
196 writer.flush().ok();
197 }
198 }
199
200 /// Replaces parts of the configuration of the file log writer.
201 ///
202 /// Note that neither the write mode nor the format function can be reset and
203 /// that the provided `FileLogWriterBuilder` must have the same values for these as the
204 /// currently used `FileLogWriter`.
205 ///
206 /// # Example
207 ///
208 /// See [`code_examples`](code_examples/index.html#reconfigure-the-file-log-writer).
209 ///
210 /// # Errors
211 ///
212 /// `FlexiLoggerError::NoFileLogger` if no file log writer is configured.
213 ///
214 /// `FlexiLoggerError::Reset` if a reset was tried with a different write mode.
215 ///
216 /// `FlexiLoggerError::Io` if the specified path doesn't work.
217 ///
218 /// `FlexiLoggerError::Poison` if some mutex is poisoned.
219 pub fn reset_flw(&self, flwb: &FileLogWriterBuilder) -> Result<(), FlexiLoggerError> {
220 if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer {
221 mw.reset_file_log_writer(flwb)
222 } else {
223 Err(FlexiLoggerError::NoFileLogger)
224 }
225 }
226
227 /// Returns the current configuration of the file log writer.
228 ///
229 /// # Errors
230 ///
231 /// `FlexiLoggerError::NoFileLogger` if no file log writer is configured.
232 ///
233 /// `FlexiLoggerError::Poison` if some mutex is poisoned.
234 pub fn flw_config(&self) -> Result<FileLogWriterConfig, FlexiLoggerError> {
235 if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer {
236 mw.flw_config()
237 } else {
238 Err(FlexiLoggerError::NoFileLogger)
239 }
240 }
241
242 /// Get the current maximimum log level.
243 ///
244 /// # Errors
245 ///
246 /// `FlexiLoggerError::Poison` if some mutex is poisoned.
247 pub fn current_max_level(&self) -> Result<log::LevelFilter, FlexiLoggerError> {
248 let read_guard = self
249 .writers_handle
250 .spec
251 .read()
252 .map_err(|_| FlexiLoggerError::Poison)?;
253 Ok(read_guard.max_level())
254 }
255
256 /// Get a copy of the current log spec.
257 ///
258 /// # Errors
259 ///
260 /// `FlexiLoggerError::Poison` if some mutex is poisoned.
261 pub fn current_log_spec(&self) -> Result<LogSpecification, FlexiLoggerError> {
262 Ok(self
263 .writers_handle
264 .spec
265 .read()
266 .map_err(|_| FlexiLoggerError::Poison)?
267 .clone())
268 }
269
270 /// Makes the logger re-open the current log file.
271 ///
272 /// If the log is written to a file, `flexi_logger` expects that nobody else modifies the file,
273 /// and offers capabilities to rotate, compress, and clean up log files.
274 ///
275 /// However, if you use tools like linux' `logrotate`
276 /// to rename or delete the current output file, you need to inform `flexi_logger` about
277 /// such actions by calling this method. Otherwise `flexi_logger` will not stop
278 /// writing to the renamed or even deleted file!
279 ///
280 /// In more complex configurations, i.e. when more than one output stream is written to,
281 /// all of them will be attempted to be re-opened; only the first error will be reported.
282 ///
283 /// # Example
284 ///
285 /// `logrotate` e.g. can be configured to send a `SIGHUP` signal to your program. You need to
286 /// handle `SIGHUP` in your program explicitly,
287 /// e.g. using a crate like [`ctrlc`](https://docs.rs/ctrlc/latest/ctrlc/),
288 /// and call this function from the registered signal handler.
289 ///
290 /// # Errors
291 ///
292 /// `FlexiLoggerError::Poison` if some mutex is poisoned.
293 ///
294 /// Other variants of `FlexiLoggerError`, depending on the used writers.
295 pub fn reopen_output(&self) -> Result<(), FlexiLoggerError> {
296 let mut result = if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer
297 {
298 mw.reopen_output()
299 } else {
300 Ok(())
301 };
302
303 for blw in self.writers_handle.other_writers.values() {
304 let result2 = blw.reopen_output();
305 if result.is_ok() && result2.is_err() {
306 result = result2;
307 }
308 }
309
310 result
311 }
312
313 /// Trigger an extra log file rotation.
314 ///
315 /// Does nothing if rotation is not configured.
316 ///
317 /// # Errors
318 ///
319 /// `FlexiLoggerError::Poison` if some mutex is poisoned.
320 ///
321 /// IO errors.
322 pub fn trigger_rotation(&self) -> Result<(), FlexiLoggerError> {
323 let mut result = if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer
324 {
325 mw.trigger_rotation()
326 } else {
327 Ok(())
328 };
329
330 for blw in self.writers_handle.other_writers.values() {
331 let result2 = blw.rotate();
332 if result.is_ok() && result2.is_err() {
333 result = result2;
334 }
335 }
336 result
337 }
338
339 /// Updates the given `Snapshot` object with the current content of the buffer,
340 /// provided that logging to buffer is configured (see [`Logger::log_to_buffer`](crate::Logger::log_to_buffer)).
341 ///
342 /// Does nothing if logging to buffer is not configured
343 /// or if the given snapshot object is already up-to-date.
344 ///
345 /// # Errors
346 ///
347 /// `FlexiLoggerError::Poison` if some mutex is poisoned.
348 ///
349 /// # Example
350 ///
351 /// ```rust
352 /// use flexi_logger::{opt_format, Logger, LogSpecification, Snapshot};
353 /// let logger_handle = Logger::with(LogSpecification::info())
354 /// .log_to_buffer(1_000_000, Some(opt_format))
355 /// .start()
356 /// .unwrap();
357 /// let mut snapshot = Snapshot::new();
358 /// logger_handle.update_snapshot(&mut snapshot).unwrap();
359 /// // use_in_ui(&snapshot.text);
360 /// # fn use_in_ui(_: &str){}
361 ///
362 /// ```
363 #[cfg(feature = "buffer_writer")]
364 pub fn update_snapshot(&self, snapshot: &mut Snapshot) -> Result<bool, FlexiLoggerError> {
365 if let PrimaryWriter::Multi(ref mw) = *self.writers_handle.primary_writer {
366 if let Some(s) = mw.get_buffer_writer() {
367 return s.update_snapshot(snapshot);
368 }
369 }
370 Ok(false)
371 }
372
373 /// Shutdown all participating writers.
374 ///
375 /// This method is supposed to be called at the very end of your program, if
376 ///
377 /// - you use some [`Cleanup`](crate::Cleanup) strategy with compression:
378 /// then you want to ensure that a termination of your program
379 /// does not interrput the cleanup-thread when it is compressing a log file,
380 /// which could leave unexpected files in the filesystem
381 /// - you use your own writer(s), and they need to clean up resources
382 ///
383 /// See also [`writers::LogWriter::shutdown`](crate::writers::LogWriter::shutdown).
384 pub fn shutdown(&self) {
385 self.writers_handle.primary_writer.shutdown();
386 for writer in self.writers_handle.other_writers.values() {
387 writer.shutdown();
388 }
389 }
390
391 /// Returns the list of existing log files according to the current `FileSpec`.
392 ///
393 /// Depending on the given selector, the list may include the CURRENT log file
394 /// and the compressed files, if they exist.
395 /// The list is empty if the logger is not configured for writing to files.
396 ///
397 /// # Errors
398 ///
399 /// `FlexiLoggerError::Poison` if some mutex is poisoned.
400 pub fn existing_log_files(
401 &self,
402 selector: &LogfileSelector,
403 ) -> Result<Vec<PathBuf>, FlexiLoggerError> {
404 let mut log_files = self
405 .writers_handle
406 .primary_writer
407 .existing_log_files(selector)?;
408 log_files.sort();
409 Ok(log_files)
410 }
411
412 /// Allows re-configuring duplication to stderr.
413 ///
414 /// # Errors
415 ///
416 /// `FlexiLoggerError::NoDuplication`
417 /// if `FlexiLogger` was initialized without duplication support
418 pub fn adapt_duplication_to_stderr(&mut self, dup: Duplicate) -> Result<(), FlexiLoggerError> {
419 if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer {
420 mw.adapt_duplication_to_stderr(dup);
421 Ok(())
422 } else {
423 Err(FlexiLoggerError::NoFileLogger)
424 }
425 }
426
427 /// Allows re-configuring duplication to stdout.
428 ///
429 /// # Errors
430 ///
431 /// `FlexiLoggerError::NoDuplication`
432 /// if `FlexiLogger` was initialized without duplication support
433 pub fn adapt_duplication_to_stdout(&mut self, dup: Duplicate) -> Result<(), FlexiLoggerError> {
434 if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer {
435 mw.adapt_duplication_to_stdout(dup);
436 Ok(())
437 } else {
438 Err(FlexiLoggerError::NoFileLogger)
439 }
440 }
441
442 // Allows checking the logs written so far to the writer
443 #[doc(hidden)]
444 pub fn validate_logs(&self, expected: &[(&'static str, &'static str, &'static str)]) {
445 self.writers_handle.primary_writer.validate_logs(expected);
446 }
447
448 // Allows checking the logs written so far to the writer
449 #[doc(hidden)]
450 pub fn validate_additional_logs(
451 &self,
452 target: &str,
453 expected: &[(&'static str, &'static str, &'static str)],
454 ) {
455 self.writers_handle
456 .other_writers
457 .get(target)
458 .unwrap(/*fail fast*/)
459 .validate_logs(expected);
460 }
461}
462
463/// Used in [`LoggerHandle::existing_log_files`].
464///
465/// Example:
466///
467/// ```rust
468/// # use flexi_logger::{LogfileSelector,Logger};
469/// # let logger_handle = Logger::try_with_env().unwrap().start().unwrap();
470/// let all_log_files = logger_handle.existing_log_files(
471/// &LogfileSelector::default()
472/// .with_r_current()
473/// .with_compressed_files()
474/// );
475/// ```
476#[allow(clippy::struct_field_names)]
477pub struct LogfileSelector {
478 pub(crate) with_plain_files: bool,
479 pub(crate) with_r_current: bool,
480 pub(crate) with_compressed_files: bool,
481 pub(crate) with_configured_current: Option<String>,
482}
483impl Default for LogfileSelector {
484 /// Selects plain log files without the `rCURRENT` file.
485 fn default() -> Self {
486 Self {
487 with_plain_files: true,
488 with_r_current: false,
489 with_compressed_files: false,
490 with_configured_current: None,
491 }
492 }
493}
494impl LogfileSelector {
495 /// Selects no file at all.
496 #[must_use]
497 pub fn none() -> Self {
498 Self {
499 with_plain_files: false,
500 with_r_current: false,
501 with_compressed_files: false,
502 with_configured_current: None,
503 }
504 }
505 /// Selects additionally the `rCURRENT` file.
506 #[must_use]
507 pub fn with_r_current(mut self) -> Self {
508 self.with_r_current = true;
509 self
510 }
511
512 /// Selects additionally a custom "current" file.
513 #[must_use]
514 pub fn with_custom_current(mut self, s: &str) -> Self {
515 self.with_configured_current = Some(s.to_string());
516 self
517 }
518
519 /// Selects additionally the compressed log files.
520 #[must_use]
521 pub fn with_compressed_files(mut self) -> Self {
522 self.with_compressed_files = true;
523 self
524 }
525}
526
527#[derive(Clone)]
528pub(crate) struct WritersHandle {
529 spec: Arc<RwLock<LogSpecification>>,
530 spec_stack: Vec<LogSpecification>,
531 primary_writer: Arc<PrimaryWriter>,
532 other_writers: Arc<HashMap<String, Box<dyn LogWriter>>>,
533}
534impl WritersHandle {
535 fn set_new_spec(&self, new_spec: LogSpecification) -> Result<(), FlexiLoggerError> {
536 let max_level = new_spec.max_level();
537 self.spec
538 .write()
539 .map_err(|_| FlexiLoggerError::Poison)?
540 .update_from(new_spec);
541 self.reconfigure(max_level);
542 Ok(())
543 }
544
545 pub(crate) fn reconfigure(&self, mut max_level: log::LevelFilter) {
546 for w in self.other_writers.as_ref().values() {
547 max_level = std::cmp::max(max_level, w.max_log_level());
548 }
549 log::set_max_level(max_level);
550 }
551}
552impl Drop for WritersHandle {
553 fn drop(&mut self) {
554 self.primary_writer.shutdown();
555 for writer in self.other_writers.values() {
556 writer.shutdown();
557 }
558 }
559}
560
561/// Trait that allows to register for changes to the log specification.
562#[cfg(feature = "specfile_without_notification")]
563#[cfg_attr(docsrs, doc(cfg(feature = "specfile")))]
564pub trait LogSpecSubscriber: 'static + Send {
565 /// Apply a new `LogSpecification`.
566 ///
567 /// # Errors
568 fn set_new_spec(&mut self, new_spec: LogSpecification) -> Result<(), FlexiLoggerError>;
569
570 /// Provide the current log spec.
571 ///
572 /// # Errors
573 fn initial_spec(&self) -> Result<LogSpecification, FlexiLoggerError>;
574}
575#[cfg(feature = "specfile_without_notification")]
576impl LogSpecSubscriber for WritersHandle {
577 fn set_new_spec(&mut self, new_spec: LogSpecification) -> Result<(), FlexiLoggerError> {
578 WritersHandle::set_new_spec(self, new_spec)
579 }
580
581 fn initial_spec(&self) -> Result<LogSpecification, FlexiLoggerError> {
582 Ok((*self.spec.read().map_err(|_e| FlexiLoggerError::Poison)?).clone())
583 }
584}