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}