Coverage Report

Created: 2025-10-10 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/flexi_logger-0.27.4/src/logger.rs
Line
Count
Source
1
use crate::{
2
    filter::LogLineFilter,
3
    flexi_logger::FlexiLogger,
4
    formats::default_format,
5
    primary_writer::PrimaryWriter,
6
    threads::start_flusher_thread,
7
    util::set_error_channel,
8
    writers::{FileLogWriter, FileLogWriterBuilder, LogWriter},
9
    Cleanup, Criterion, DeferredNow, FileSpec, FlexiLoggerError, FormatFunction, LogSpecification,
10
    LoggerHandle, Naming, WriteMode,
11
};
12
use log::LevelFilter;
13
use std::{
14
    collections::HashMap,
15
    path::PathBuf,
16
    sync::{Arc, RwLock},
17
    time::Duration,
18
};
19
20
#[cfg(feature = "is-terminal")]
21
use crate::formats::AdaptiveFormat;
22
#[cfg(feature = "is-terminal")]
23
use is_terminal::IsTerminal;
24
25
#[cfg(feature = "specfile_without_notification")]
26
use {crate::logger_handle::LogSpecSubscriber, std::io::Read, std::path::Path};
27
28
#[cfg(feature = "specfile")]
29
use {
30
    crate::util::{eprint_err, ErrorCode},
31
    notify_debouncer_mini::{
32
        new_debouncer,
33
        notify::{RecommendedWatcher, RecursiveMode},
34
        DebounceEventResult, Debouncer,
35
    },
36
};
37
38
/// The entry-point for using `flexi_logger`.
39
///
40
/// `Logger` is a builder class that allows you to
41
/// * specify your desired (initial) loglevel-specification
42
///   * either as a String ([`Logger::try_with_str`])
43
///   * or by providing it in the environment ([`Logger::try_with_env`]),
44
///   * or by combining both options ([`Logger::try_with_env_or_str`]),
45
///   * or by building a [`LogSpecification`] programmatically ([`Logger::with`]),
46
/// * use the desired configuration methods,
47
/// * and finally start the logger with
48
///
49
///   * [`Logger::start`], or
50
///   * [`Logger::start_with_specfile`].
51
///
52
/// # Usage
53
///
54
/// See [`code_examples`](code_examples/index.html) for a comprehensive list of usage possibilities.
55
pub struct Logger {
56
    spec: LogSpecification,
57
    log_target: LogTarget,
58
    duplicate_err: Duplicate,
59
    duplicate_out: Duplicate,
60
    format_for_file: FormatFunction,
61
    format_for_stderr: FormatFunction,
62
    format_for_stdout: FormatFunction,
63
    format_for_writer: FormatFunction,
64
    #[cfg(feature = "colors")]
65
    o_palette: Option<String>,
66
    flush_interval: std::time::Duration,
67
    flwb: FileLogWriterBuilder,
68
    other_writers: HashMap<String, Box<dyn LogWriter>>,
69
    filter: Option<Box<dyn LogLineFilter + Send + Sync>>,
70
    error_channel: ErrorChannel,
71
    use_utc: bool,
72
}
73
74
enum LogTarget {
75
    StdErr,
76
    StdOut,
77
    Multi(bool, Option<Box<dyn LogWriter>>),
78
}
79
80
/// Create a Logger instance and define how to access the (initial)
81
/// loglevel-specification.
82
impl Logger {
83
    /// Creates a Logger that you provide with an explicit [`LogSpecification`].
84
    #[must_use]
85
0
    pub fn with(logspec: LogSpecification) -> Self {
86
0
        Self::from_spec_and_errs(logspec)
87
0
    }
88
89
    /// Creates a Logger that reads the [`LogSpecification`] from a `String` or `&str`.
90
    /// See [`LogSpecification`] for the syntax.
91
    ///
92
    /// # Errors
93
    ///
94
    /// `FlexiLoggerError::Parse` if the String uses an erroneous syntax.
95
0
    pub fn try_with_str<S: AsRef<str>>(s: S) -> Result<Self, FlexiLoggerError> {
96
0
        Ok(Self::from_spec_and_errs(LogSpecification::parse(
97
0
            s.as_ref(),
98
0
        )?))
99
0
    }
100
101
    /// Creates a Logger that reads the [`LogSpecification`] from the environment variable
102
    /// `RUST_LOG`.
103
    ///
104
    /// Note that if `RUST_LOG` is not set, nothing is logged.
105
    ///
106
    /// # Errors
107
    ///
108
    /// `FlexiLoggerError::Parse` if the value of `RUST_LOG` is malformed.
109
0
    pub fn try_with_env() -> Result<Self, FlexiLoggerError> {
110
0
        Ok(Self::from_spec_and_errs(LogSpecification::env()?))
111
0
    }
112
113
    /// Creates a Logger that reads the [`LogSpecification`] from the environment variable
114
    /// `RUST_LOG`, or derives it from the given `String`, if `RUST_LOG` is not set.
115
    ///
116
    /// # Errors
117
    ///
118
    /// `FlexiLoggerError::Parse` if the chosen value is malformed.
119
0
    pub fn try_with_env_or_str<S: AsRef<str>>(s: S) -> Result<Self, FlexiLoggerError> {
120
0
        Ok(Self::from_spec_and_errs(LogSpecification::env_or_parse(s)?))
121
0
    }
122
123
0
    fn from_spec_and_errs(spec: LogSpecification) -> Self {
124
        #[cfg(feature = "colors")]
125
        #[cfg(windows)]
126
        {
127
            nu_ansi_term::enable_ansi_support().ok();
128
        }
129
130
        Self {
131
0
            spec,
132
0
            log_target: LogTarget::StdErr,
133
0
            duplicate_err: Duplicate::None,
134
0
            duplicate_out: Duplicate::None,
135
0
            format_for_file: default_format,
136
137
            #[cfg(feature = "colors")]
138
0
            format_for_stdout: AdaptiveFormat::Default.format_function(
139
0
                if cfg!(feature = "is-terminal") {
140
0
                    std::io::stdout().is_terminal()
141
                } else {
142
0
                    false
143
                },
144
            ),
145
            #[cfg(feature = "colors")]
146
0
            format_for_stderr: AdaptiveFormat::Default.format_function(
147
0
                if cfg!(feature = "is-terminal") {
148
0
                    std::io::stderr().is_terminal()
149
                } else {
150
0
                    false
151
                },
152
            ),
153
154
            #[cfg(not(feature = "colors"))]
155
            format_for_stdout: default_format,
156
            #[cfg(not(feature = "colors"))]
157
            format_for_stderr: default_format,
158
159
0
            format_for_writer: default_format,
160
            #[cfg(feature = "colors")]
161
0
            o_palette: None,
162
0
            flush_interval: Duration::from_secs(0),
163
0
            flwb: FileLogWriter::builder(FileSpec::default()),
164
0
            other_writers: HashMap::<String, Box<dyn LogWriter>>::new(),
165
0
            filter: None,
166
0
            error_channel: ErrorChannel::default(),
167
            use_utc: false,
168
        }
169
0
    }
170
}
171
172
/// Simple methods for influencing the behavior of the Logger.
173
impl Logger {
174
    /// Log is written to stderr (which is the default).
175
    #[must_use]
176
0
    pub fn log_to_stderr(mut self) -> Self {
177
0
        self.log_target = LogTarget::StdErr;
178
0
        self
179
0
    }
180
181
    /// Log is written to stdout.
182
    #[must_use]
183
0
    pub fn log_to_stdout(mut self) -> Self {
184
0
        self.log_target = LogTarget::StdOut;
185
0
        self
186
0
    }
187
188
    /// Log is written to a file.
189
    ///
190
    /// See [`FileSpec`] for details about the filename pattern.
191
    ///
192
    /// You can duplicate to stdout and stderr, and you can add additional writers.
193
    #[must_use]
194
0
    pub fn log_to_file(mut self, file_spec: FileSpec) -> Self {
195
0
        self.log_target = LogTarget::Multi(true, None);
196
0
        self.flwb = self.flwb.file_spec(file_spec);
197
0
        self
198
0
    }
199
200
    /// Log is written to the provided writer.
201
    ///
202
    /// You can duplicate to stdout and stderr, and you can add additional writers.
203
    #[must_use]
204
0
    pub fn log_to_writer(mut self, w: Box<dyn LogWriter>) -> Self {
205
0
        self.log_target = LogTarget::Multi(false, Some(w));
206
0
        self
207
0
    }
208
209
    /// Log is written to a file, as with [`Logger::log_to_file`], _and_ to an alternative
210
    /// [`LogWriter`] implementation.
211
    ///
212
    /// And you can duplicate to stdout and stderr, and you can add additional writers.
213
    #[must_use]
214
0
    pub fn log_to_file_and_writer(mut self, file_spec: FileSpec, w: Box<dyn LogWriter>) -> Self {
215
0
        self.log_target = LogTarget::Multi(true, Some(w));
216
0
        self.flwb = self.flwb.file_spec(file_spec);
217
0
        self
218
0
    }
219
220
    /// Log is processed, including duplication, but not written to any destination.
221
    ///
222
    /// This can be useful e.g. for running application tests with all log-levels active and still
223
    /// avoiding tons of log files etc.
224
    /// Such tests ensure that the log calls which are normally not active
225
    /// will not cause undesired side-effects when activated
226
    /// (note that the log macros may prevent arguments of inactive log-calls from being evaluated).
227
    ///
228
    /// Or, if you want to get logs both to stdout and stderr, but nowhere else,
229
    /// then use this option and combine it with
230
    /// [`Logger::duplicate_to_stdout`] and [`Logger::duplicate_to_stderr`].
231
    #[must_use]
232
0
    pub fn do_not_log(mut self) -> Self {
233
0
        self.log_target = LogTarget::Multi(false, None);
234
0
        self
235
0
    }
236
237
    /// Makes the logger print an info message to stdout with the name of the logfile
238
    /// when a logfile is opened for writing.
239
    #[must_use]
240
0
    pub fn print_message(mut self) -> Self {
241
0
        self.flwb = self.flwb.print_message();
242
0
        self
243
0
    }
244
245
    /// Makes the logger write messages with the specified minimum severity additionally to stderr.
246
    ///
247
    /// Does not work with [`Logger::log_to_stdout`] or [`Logger::log_to_stderr`].
248
    #[must_use]
249
0
    pub fn duplicate_to_stderr(mut self, dup: Duplicate) -> Self {
250
0
        self.duplicate_err = dup;
251
0
        self
252
0
    }
253
254
    /// Makes the logger write messages with the specified minimum severity additionally to stdout.
255
    ///
256
    /// Does not work with [`Logger::log_to_stdout`] or [`Logger::log_to_stderr`].
257
    #[must_use]
258
0
    pub fn duplicate_to_stdout(mut self, dup: Duplicate) -> Self {
259
0
        self.duplicate_out = dup;
260
0
        self
261
0
    }
262
263
    /// Makes the logger use the provided format function for all messages
264
    /// that are written to files, stderr, stdout, or to an additional writer.
265
    ///
266
    /// You can either choose one of the provided log-line formatters,
267
    /// or you create and use your own format function with the signature <br>
268
    /// ```rust
269
    /// fn my_format(
270
    ///    write: &mut dyn std::io::Write,
271
    ///    now: &mut flexi_logger::DeferredNow,
272
    ///    record: &log::Record,
273
    /// ) -> std::io::Result<()>
274
    /// # {unimplemented!("")}
275
    /// ```
276
    ///
277
    /// By default, [`default_format`] is used for output to files and to custom writers,
278
    /// and [`AdaptiveFormat::Default`] is used for output to `stderr` and `stdout`.
279
    /// If the feature `colors` is switched off, [`default_format`] is used for all outputs.
280
    #[must_use]
281
0
    pub fn format(mut self, format: FormatFunction) -> Self {
282
0
        self.format_for_file = format;
283
0
        self.format_for_stderr = format;
284
0
        self.format_for_stdout = format;
285
0
        self.format_for_writer = format;
286
0
        self
287
0
    }
288
289
    /// Makes the logger use the provided format function for messages
290
    /// that are written to files.
291
    ///
292
    /// Regarding the default, see [`Logger::format`].
293
    #[must_use]
294
0
    pub fn format_for_files(mut self, format: FormatFunction) -> Self {
295
0
        self.format_for_file = format;
296
0
        self
297
0
    }
298
299
    /// Makes the logger use the provided format function for messages
300
    /// that are written to stderr.
301
    ///
302
    /// Regarding the default, see [`Logger::format`].
303
    #[must_use]
304
0
    pub fn format_for_stderr(mut self, format_function: FormatFunction) -> Self {
305
0
        self.format_for_stderr = format_function;
306
0
        self
307
0
    }
308
309
    /// Makes the logger use the specified format for messages that are written to `stderr`.
310
    /// Coloring is used if `stderr` is a tty.
311
    ///
312
    /// Regarding the default, see [`Logger::format`].
313
    #[cfg_attr(docsrs, doc(cfg(feature = "is-terminal")))]
314
    #[cfg(feature = "is-terminal")]
315
    #[must_use]
316
0
    pub fn adaptive_format_for_stderr(mut self, adaptive_format: AdaptiveFormat) -> Self {
317
0
        self.format_for_stderr = adaptive_format.format_function(std::io::stderr().is_terminal());
318
0
        self
319
0
    }
320
321
    /// Makes the logger use the provided format function to format messages
322
    /// that are written to stdout.
323
    ///
324
    /// Regarding the default, see [`Logger::format`].
325
    #[must_use]
326
0
    pub fn format_for_stdout(mut self, format_function: FormatFunction) -> Self {
327
0
        self.format_for_stdout = format_function;
328
0
        self
329
0
    }
330
331
    /// Makes the logger use the specified format for messages that are written to `stdout`.
332
    /// Coloring is used if `stdout` is a tty.
333
    ///
334
    /// Regarding the default, see [`Logger::format`].
335
    #[cfg_attr(docsrs, doc(cfg(feature = "is-terminal")))]
336
    #[cfg(feature = "is-terminal")]
337
    #[must_use]
338
0
    pub fn adaptive_format_for_stdout(mut self, adaptive_format: AdaptiveFormat) -> Self {
339
0
        self.format_for_stdout = adaptive_format.format_function(std::io::stdout().is_terminal());
340
0
        self
341
0
    }
342
343
    /// Allows specifying a format function for an additional writer.
344
    /// Note that it is up to the implementation of the additional writer
345
    /// whether it evaluates this setting or not.
346
    ///
347
    /// Regarding the default, see [`Logger::format`].
348
    #[must_use]
349
0
    pub fn format_for_writer(mut self, format: FormatFunction) -> Self {
350
0
        self.format_for_writer = format;
351
0
        self
352
0
    }
353
354
    /// Sets the color palette for function [`style`](crate::style), which is used in the
355
    /// provided coloring format functions.
356
    ///
357
    /// The palette given here overrides the default palette.
358
    ///
359
    /// The palette is specified in form of a String that contains a semicolon-separated list
360
    /// of numbers (0..=255) and/or dashes (´-´).
361
    /// The first five values denote the fixed color that is
362
    /// used for coloring `error`, `warn`, `info`, `debug`, and `trace` messages.
363
    ///
364
    /// The String `"196;208;-;7;8"` describes the default palette, where color 196 is
365
    /// used for error messages, and so on. The `-` means that no coloring is done,
366
    /// i.e., with `"-;-;-;-;-"` all coloring is switched off.
367
    ///
368
    /// Prefixing a number with 'b' makes the output being written in bold.
369
    /// The String `"b1;3;2;4;6"` e.g. describes the palette used by `env_logger`.
370
    ///
371
    /// The palette can further be overridden at runtime by setting the environment variable
372
    /// `FLEXI_LOGGER_PALETTE` to a palette String. This allows adapting the used text colors to
373
    /// differently colored terminal backgrounds.
374
    ///
375
    /// For your convenience, if you want to specify your own palette,
376
    /// you can produce a colored list with all 255 colors with `cargo run --example colors`.
377
    #[cfg_attr(docsrs, doc(cfg(feature = "colors")))]
378
    #[cfg(feature = "colors")]
379
    #[must_use]
380
0
    pub fn set_palette(mut self, palette: String) -> Self {
381
0
        self.o_palette = Some(palette);
382
0
        self
383
0
    }
384
385
    /// Prevent indefinite growth of the log file by applying file rotation
386
    /// and a clean-up strategy for older log files.
387
    ///
388
    /// By default, the log file is fixed while your program is running and will grow indefinitely.
389
    /// With this option being used, when the log file reaches the specified criterion,
390
    /// the file will be closed and a new file will be opened.
391
    ///
392
    /// Note that also the filename pattern changes:
393
    ///
394
    /// - by default, no timestamp is added to the filename if rotation is used
395
    /// - the logs are always written to a file with infix `_rCURRENT`
396
    /// - when the rotation criterion is fulfilled, it is closed and renamed to a file
397
    ///   with another infix (see `Naming`),
398
    ///   and then the logging continues again to the (fresh) file with infix `_rCURRENT`.
399
    ///
400
    /// Example:
401
    ///
402
    /// After some logging with your program `my_prog` and rotation with `Naming::Numbers`,
403
    /// you will find files like
404
    ///
405
    /// ```text
406
    /// my_prog_r00000.log
407
    /// my_prog_r00001.log
408
    /// my_prog_r00002.log
409
    /// my_prog_rCURRENT.log
410
    /// ```
411
    ///
412
    /// ## Parameters
413
    ///
414
    /// `criterion` defines *when* the log file should be rotated, based on its size or age.
415
    /// See [`Criterion`] for details.
416
    ///
417
    /// `naming` defines the naming convention for the rotated log files.
418
    /// See [`Naming`] for details.
419
    ///
420
    /// `cleanup` defines the strategy for dealing with older files.
421
    /// See [`Cleanup`] for details.
422
    #[must_use]
423
0
    pub fn rotate(mut self, criterion: Criterion, naming: Naming, cleanup: Cleanup) -> Self {
424
0
        self.flwb = self.flwb.rotate(criterion, naming, cleanup);
425
0
        self
426
0
    }
427
428
    /// When [`Logger::rotate`] is used with some [`Cleanup`] variant other than [`Cleanup::Never`],
429
    /// then this method can be used to define
430
    /// if the cleanup activities (finding files, deleting files, evtl compressing files) are
431
    /// delegated to a background thread (which is the default,
432
    /// to minimize the blocking impact to your application caused by IO operations),
433
    /// or whether they are done synchronously in the current log-call.
434
    ///
435
    /// If you call this method with `use_background_thread = false`,
436
    /// the cleanup is done synchronously.
437
    #[must_use]
438
0
    pub fn cleanup_in_background_thread(mut self, use_background_thread: bool) -> Self {
439
0
        self.flwb = self
440
0
            .flwb
441
0
            .cleanup_in_background_thread(use_background_thread);
442
0
        self
443
0
    }
444
445
    /// Apply the provided filter before really writing log lines.
446
    ///
447
    /// See the documentation of module [`filter`](crate::filter) for a usage example.
448
    #[must_use]
449
0
    pub fn filter(mut self, filter: Box<dyn LogLineFilter + Send + Sync>) -> Self {
450
0
        self.filter = Some(filter);
451
0
        self
452
0
    }
453
454
    /// Makes the logger append to the specified output file, if it exists already;
455
    /// by default, the file would be truncated.
456
    ///
457
    /// This option only has an effect if logs are written to files, but
458
    /// it will hardly make an effect if [`FileSpec::suppress_timestamp`] is not used.
459
    #[must_use]
460
0
    pub fn append(mut self) -> Self {
461
0
        self.flwb = self.flwb.append();
462
0
        self
463
0
    }
464
465
    /// Makes the logger use UTC timestamps rather than local timestamps.
466
    #[must_use]
467
0
    pub fn use_utc(mut self) -> Self {
468
0
        self.use_utc = true;
469
0
        self
470
0
    }
471
472
    /// The specified path will be used on unix systems to create a symbolic link
473
    /// to the current log file.
474
    ///
475
    /// This option has no effect on filesystems where symlinks are not supported,
476
    /// and it only has an effect if logs are written to files.
477
    ///
478
    /// ### Example
479
    ///
480
    /// You can use the symbolic link to follow the log output with `tail`,
481
    /// even if the log files are rotated.
482
    ///
483
    /// Assuming you use `create_symlink("link_to_log_file")`, then use:
484
    ///
485
    /// ```text
486
    /// tail --follow=name --max-unchanged-stats=1 --retry link_to_log_file
487
    /// ```
488
    ///
489
    #[must_use]
490
0
    pub fn create_symlink<P: Into<PathBuf>>(mut self, symlink: P) -> Self {
491
0
        self.flwb = self.flwb.create_symlink(symlink);
492
0
        self
493
0
    }
494
495
    /// Registers a [`LogWriter`] implementation under the given target name.
496
    ///
497
    /// The target name must not start with an underscore.
498
    /// See module [`writers`](crate::writers) for more details.
499
    #[must_use]
500
0
    pub fn add_writer<S: Into<String>>(
501
0
        mut self,
502
0
        target_name: S,
503
0
        writer: Box<dyn LogWriter>,
504
0
    ) -> Self {
505
0
        self.other_writers.insert(target_name.into(), writer);
506
0
        self
507
0
    }
508
509
    /// Sets the write mode for the logger.
510
    ///
511
    /// See [`WriteMode`] for more (important!) details.
512
    #[must_use]
513
0
    pub fn write_mode(mut self, write_mode: WriteMode) -> Self {
514
0
        self.flwb = self.flwb.write_mode(write_mode.without_flushing());
515
0
        self.flush_interval = write_mode.get_flush_interval();
516
0
        self
517
0
    }
518
519
    /// Use Windows line endings, rather than just `\n`.
520
    #[must_use]
521
0
    pub fn use_windows_line_ending(mut self) -> Self {
522
0
        self.flwb = self.flwb.use_windows_line_ending();
523
0
        self
524
0
    }
525
526
    /// Define the output channel for `flexi_logger`'s own error messages.
527
    ///
528
    /// These are only written if `flexi_logger` cannot do what it is supposed to do,
529
    /// so under normal circumstances no single message shuld appear.
530
    ///
531
    /// By default these error messages are printed to `stderr`.
532
    #[must_use]
533
0
    pub fn error_channel(mut self, error_channel: ErrorChannel) -> Self {
534
0
        self.error_channel = error_channel;
535
0
        self
536
0
    }
537
}
538
539
/// Enum for defining the output channel for `flexi_logger`'s own error messages.
540
///
541
/// These are only written if `flexi_logger` cannot do what it is supposed to do,
542
/// so under normal circumstances no single message shuld appear.
543
///
544
/// By default these error messages are printed to `stderr`.
545
#[derive(Debug, Default)]
546
pub enum ErrorChannel {
547
    /// Write `flexi_logger`'s own error messages to `stderr`.
548
    #[default]
549
    StdErr,
550
    /// Write `flexi_logger`'s own error messages to `stdout`.
551
    StdOut,
552
    /// Write `flexi_logger`'s own error messages to the specified file.
553
    File(PathBuf),
554
    /// Don't write `flexi_logger`'s own error messages.
555
    DevNull,
556
}
557
558
/// Alternative set of methods to control the behavior of the Logger.
559
/// Use these methods when you want to control the settings flexibly,
560
/// e.g. with commandline arguments via `docopts` or `clap`.
561
impl Logger {
562
    /// With true, makes the logger print an info message to stdout, each time
563
    /// when a new file is used for log-output.
564
    #[must_use]
565
0
    pub fn o_print_message(mut self, print_message: bool) -> Self {
566
0
        self.flwb = self.flwb.o_print_message(print_message);
567
0
        self
568
0
    }
569
570
    /// By default, and with None, the log file will grow indefinitely.
571
    /// If a `rotate_config` is set, when the log file reaches or exceeds the specified size,
572
    /// the file will be closed and a new file will be opened.
573
    /// Also the filename pattern changes: instead of the timestamp, a serial number
574
    /// is included into the filename.
575
    ///
576
    /// The size is given in bytes, e.g. `o_rotate_over_size(Some(1_000))` will rotate
577
    /// files once they reach a size of 1 kB.
578
    ///
579
    /// The cleanup strategy allows delimiting the used space on disk.
580
    #[must_use]
581
0
    pub fn o_rotate(mut self, rotate_config: Option<(Criterion, Naming, Cleanup)>) -> Self {
582
0
        self.flwb = self.flwb.o_rotate(rotate_config);
583
0
        self
584
0
    }
585
586
    /// If append is set to true, makes the logger append to the specified output file, if it exists.
587
    /// By default, or with false, the file would be truncated.
588
    ///
589
    /// This option only has an effect if logs are written to files,
590
    /// and it will hardly make an effect if `suppress_timestamp()` is not used.
591
    #[must_use]
592
0
    pub fn o_append(mut self, append: bool) -> Self {
593
0
        self.flwb = self.flwb.o_append(append);
594
0
        self
595
0
    }
596
597
    /// If a String is specified, it will be used on unix systems to create in the current folder
598
    /// a symbolic link with this name to the current log file.
599
    ///
600
    /// This option only has an effect on unix systems and if logs are written to files.
601
    #[must_use]
602
0
    pub fn o_create_symlink<P: Into<PathBuf>>(mut self, symlink: Option<P>) -> Self {
603
0
        self.flwb = self.flwb.o_create_symlink(symlink);
604
0
        self
605
0
    }
606
}
607
608
/// Finally, start logging, optionally with a spec-file.
609
impl Logger {
610
    /// Consumes the Logger object and initializes `flexi_logger`.
611
    ///
612
    /// **Keep the [`LoggerHandle`] alive up to the very end of your program!**
613
    /// Dropping the [`LoggerHandle`] flushes and shuts down [`FileLogWriter`]s
614
    /// and other [`LogWriter`]s, and then may prevent further logging!
615
    /// This should happen immediately before the program terminates, but not earlier.
616
    ///
617
    /// Dropping the [`LoggerHandle`] is uncritical
618
    /// only with [`Logger::log_to_stdout`] or [`Logger::log_to_stderr`].
619
    ///
620
    /// The [`LoggerHandle`] also allows updating the log specification programmatically,
621
    /// e.g. to intensify logging for (buggy) parts of a (test) program, etc.
622
    ///
623
    /// # Example
624
    ///
625
    /// ```rust
626
    /// use flexi_logger::{Logger,WriteMode, FileSpec};
627
    /// fn main() -> Result<(), Box<dyn std::error::Error>> {
628
    ///     let _logger = Logger::try_with_str("info")?
629
    ///         .log_to_file(FileSpec::default())
630
    ///         .write_mode(WriteMode::BufferAndFlush)
631
    ///         .start()?;
632
    ///
633
    ///     // ... do all your work and join back all threads whose logs you want to see ...
634
    ///
635
    ///     Ok(())
636
    /// }
637
    /// ```
638
    ///
639
    /// # Errors
640
    ///
641
    /// Several variants of [`FlexiLoggerError`] can occur.
642
0
    pub fn start(self) -> Result<LoggerHandle, FlexiLoggerError> {
643
0
        let (boxed_logger, handle) = self.build()?;
644
0
        log::set_boxed_logger(boxed_logger)?;
645
0
        Ok(handle)
646
0
    }
647
648
    /// Builds a boxed logger and a `LoggerHandle` for it,
649
    /// but does not initialize the global logger.
650
    ///
651
    /// The returned boxed logger implements the [`Log`](log::Log) trait
652
    /// and can be installed manually or nested within another logger.
653
    ///
654
    /// **Keep the [`LoggerHandle`] alive up to the very end of your program!**
655
    /// See [`Logger::start`] for more details.
656
    ///
657
    /// # Errors
658
    ///
659
    /// Several variants of [`FlexiLoggerError`] can occur.
660
0
    pub fn build(mut self) -> Result<(Box<dyn log::Log>, LoggerHandle), FlexiLoggerError> {
661
        #[cfg(feature = "colors")]
662
0
        crate::formats::set_palette(&self.o_palette)?;
663
664
0
        if self.use_utc {
665
0
            self.flwb = self.flwb.use_utc();
666
0
        }
667
668
0
        let a_primary_writer = Arc::new(match self.log_target {
669
            LogTarget::StdOut => {
670
0
                if let WriteMode::SupportCapture = self.flwb.get_write_mode() {
671
0
                    PrimaryWriter::test(true, self.format_for_stdout)
672
                } else {
673
0
                    PrimaryWriter::stdout(self.format_for_stdout, self.flwb.get_write_mode())
674
                }
675
            }
676
            LogTarget::StdErr => {
677
0
                if let WriteMode::SupportCapture = self.flwb.get_write_mode() {
678
0
                    PrimaryWriter::test(false, self.format_for_stderr)
679
                } else {
680
0
                    PrimaryWriter::stderr(self.format_for_stderr, self.flwb.get_write_mode())
681
                }
682
            }
683
0
            LogTarget::Multi(use_file, mut o_writer) => PrimaryWriter::multi(
684
0
                self.duplicate_err,
685
0
                self.duplicate_out,
686
0
                WriteMode::SupportCapture == *self.flwb.get_write_mode(),
687
0
                self.format_for_stderr,
688
0
                self.format_for_stdout,
689
0
                if use_file {
690
0
                    Some(Box::new(
691
0
                        self.flwb.format(self.format_for_file).try_build()?,
692
                    ))
693
                } else {
694
0
                    None
695
                },
696
                {
697
0
                    if let Some(ref mut writer) = o_writer {
698
0
                        writer.format(self.format_for_writer);
699
0
                    }
700
0
                    o_writer
701
                },
702
            ),
703
        });
704
705
0
        let a_other_writers = Arc::new(self.other_writers);
706
707
0
        if self.flush_interval.as_secs() != 0 || self.flush_interval.subsec_nanos() != 0 {
708
0
            start_flusher_thread(
709
0
                Arc::clone(&a_primary_writer),
710
0
                Arc::clone(&a_other_writers),
711
0
                self.flush_interval,
712
0
            )?;
713
0
        }
714
715
0
        let max_level = self.spec.max_level();
716
0
        let a_l_spec = Arc::new(RwLock::new(self.spec));
717
0
        set_error_channel(self.error_channel);
718
719
        // initialize the lazy_statics in DeferredNow before threads are spawned
720
0
        if self.use_utc {
721
0
            DeferredNow::force_utc();
722
0
        }
723
0
        let mut now = DeferredNow::new();
724
0
        now.now();
725
726
0
        let flexi_logger = FlexiLogger::new(
727
0
            Arc::clone(&a_l_spec),
728
0
            Arc::clone(&a_primary_writer),
729
0
            Arc::clone(&a_other_writers),
730
0
            self.filter,
731
        );
732
733
0
        let handle = LoggerHandle::new(a_l_spec, a_primary_writer, a_other_writers);
734
0
        handle.reconfigure(max_level);
735
0
        Ok((Box::new(flexi_logger), handle))
736
0
    }
737
738
    /// Consumes the Logger object and initializes `flexi_logger` in a way that
739
    /// subsequently the log specification can be updated,
740
    /// while the program is running, by editing a file.
741
    ///
742
    /// Uses the spec that was given to the factory method ([`Logger::with`] etc)
743
    /// as initial spec and then tries to read the logspec from a file.
744
    ///
745
    /// If the file does not exist, `flexi_logger` creates the file and fills it
746
    /// with the initial spec (and in the respective file format, of course).
747
    ///
748
    /// **Keep the returned [`LoggerHandle`] alive up to the very end of your program!**
749
    /// See [`Logger::start`] for more details.
750
    ///
751
    /// # Feature dependency
752
    ///
753
    /// The implementation of this configuration method uses some additional crates
754
    /// that you might not want to depend on with your program if you don't use this functionality.
755
    /// For that reason the method is only available if you activate the
756
    /// `specfile` feature. See the usage section on
757
    /// [crates.io](https://crates.io/crates/flexi_logger) for details.
758
    ///
759
    /// # Usage
760
    ///
761
    /// A logger initialization like
762
    ///
763
    /// ```rust,no_run
764
    /// use flexi_logger::Logger;
765
    /// Logger::try_with_str("info")
766
    ///     .unwrap()
767
    ///     // more logger configuration
768
    ///     .start_with_specfile("logspecification.toml");
769
    /// ```
770
    ///
771
    /// will create the file `logspecification.toml` (if it does not yet exist) with this content:
772
    ///
773
    /// ```toml
774
    /// ### Optional: Default log level
775
    /// global_level = 'info'
776
    /// ### Optional: specify a regular expression to suppress all messages that don't match
777
    /// #global_pattern = 'foo'
778
    ///
779
    /// ### Specific log levels per module are optionally defined in this section
780
    /// [modules]
781
    /// #'mod1' = 'warn'
782
    /// #'mod2' = 'debug'
783
    /// #'mod2::mod3' = 'trace'
784
    /// ```
785
    ///
786
    /// You can subsequently edit and modify the file according to your needs,
787
    /// while the program is running, and it will immediately take your changes into account.
788
    ///
789
    /// Currently only toml-files are supported, the file suffix thus must be `.toml`.
790
    ///
791
    /// The initial spec remains valid if the file cannot be read.
792
    ///
793
    /// If you update the specfile subsequently while the program is running, `flexi_logger`
794
    /// re-reads it automatically and adapts its behavior according to the new content.
795
    /// If the file cannot be read anymore, e.g. because the format is not correct, the
796
    /// previous logspec remains active.
797
    /// If the file is corrected subsequently, the log spec update will work again.
798
    ///
799
    /// # Errors
800
    ///
801
    /// Several variants of [`FlexiLoggerError`] can occur.
802
    #[cfg_attr(docsrs, doc(cfg(feature = "specfile")))]
803
    #[cfg(feature = "specfile_without_notification")]
804
    pub fn start_with_specfile<P: AsRef<Path>>(
805
        self,
806
        specfile: P,
807
    ) -> Result<LoggerHandle, FlexiLoggerError> {
808
        let (boxed_logger, handle) = self.build_with_specfile(specfile)?;
809
        log::set_boxed_logger(boxed_logger)?;
810
        Ok(handle)
811
    }
812
813
    /// Builds a boxed logger and a `LoggerHandle` for it,
814
    /// but does not initialize the global logger.
815
    ///
816
    /// See also [`Logger::start`] and [`Logger::start_with_specfile`].
817
    /// for the properties of the returned logger.
818
    ///
819
    /// # Errors
820
    ///
821
    /// Several variants of [`FlexiLoggerError`] can occur.
822
    #[cfg_attr(docsrs, doc(cfg(feature = "specfile")))]
823
    #[cfg(feature = "specfile_without_notification")]
824
    pub fn build_with_specfile<P: AsRef<Path>>(
825
        self,
826
        specfile: P,
827
    ) -> Result<(Box<dyn log::Log>, LoggerHandle), FlexiLoggerError> {
828
        let (boxed_log, mut handle) = self.build()?;
829
830
        let specfile = specfile.as_ref();
831
        synchronize_subscriber_with_specfile(&mut handle.writers_handle, specfile)?;
832
833
        #[cfg(feature = "specfile")]
834
        {
835
            handle.ao_specfile_watcher = Some(Arc::new(create_specfile_watcher(
836
                specfile,
837
                handle.writers_handle.clone(),
838
            )?));
839
        }
840
841
        Ok((boxed_log, handle))
842
    }
843
}
844
845
// Reread the specfile when it was updated
846
#[cfg(feature = "specfile")]
847
pub(crate) fn create_specfile_watcher<S: LogSpecSubscriber>(
848
    specfile: &Path,
849
    mut subscriber: S,
850
) -> Result<Debouncer<RecommendedWatcher>, FlexiLoggerError> {
851
    let specfile = specfile
852
        .canonicalize()
853
        .map_err(FlexiLoggerError::SpecfileIo)?;
854
    let clone = specfile.clone();
855
    let parent = clone.parent().unwrap(/*cannot fail*/);
856
857
    let mut debouncer = new_debouncer(
858
        std::time::Duration::from_millis(1000),
859
        None, // <--- goes away with notify-debouncer-mini version 0.4
860
        move |res: DebounceEventResult| match res {
861
            Ok(events) => events.iter().for_each(|e| {
862
                if e.path
863
                    .canonicalize()
864
                    .map(|x| x == specfile)
865
                    .unwrap_or(false)
866
                {
867
                    log_spec_string_from_file(&specfile)
868
                        .map_err(FlexiLoggerError::SpecfileIo)
869
                        .and_then(LogSpecification::from_toml)
870
                        .and_then(|spec| subscriber.set_new_spec(spec))
871
                        .map_err(|e| {
872
                            eprint_err(
873
                                ErrorCode::LogSpecFile,
874
                                "continuing with previous log specification, because \
875
                                            rereading the log specification file failed",
876
                                &e,
877
                            );
878
                        })
879
                        .ok();
880
                }
881
            }),
882
            Err(errors) => errors.iter().for_each(|e| {
883
                eprint_err(
884
                    ErrorCode::LogSpecFile,
885
                    "error while watching the specfile",
886
                    &e,
887
                );
888
            }),
889
            // Err(e) => eprint_err(
890
            //     ErrorCode::LogSpecFile,
891
            //     "error while watching the specfile",
892
            //     &e,
893
            // ),
894
        },
895
    )
896
    .unwrap();
897
898
    debouncer
899
        .watcher()
900
        .watch(parent, RecursiveMode::NonRecursive)
901
        .unwrap();
902
903
    Ok(debouncer)
904
}
905
906
// If the specfile exists, read the file and update the subscriber's logspec from it;
907
// otherwise try to create the file, with the current spec as content, under the specified name.
908
#[cfg(feature = "specfile_without_notification")]
909
pub(crate) fn synchronize_subscriber_with_specfile<S: LogSpecSubscriber>(
910
    subscriber: &mut S,
911
    specfile: &Path,
912
) -> Result<(), FlexiLoggerError> {
913
    if specfile
914
        .extension()
915
        .unwrap_or_else(|| std::ffi::OsStr::new(""))
916
        .to_str()
917
        .unwrap_or("")
918
        != "toml"
919
    {
920
        return Err(FlexiLoggerError::SpecfileExtension(
921
            "only spec files with extension toml are supported",
922
        ));
923
    }
924
925
    if Path::is_file(specfile) {
926
        let s = log_spec_string_from_file(specfile).map_err(FlexiLoggerError::SpecfileIo)?;
927
        subscriber.set_new_spec(LogSpecification::from_toml(s)?)?;
928
    } else {
929
        if let Some(specfolder) = specfile.parent() {
930
            std::fs::DirBuilder::new()
931
                .recursive(true)
932
                .create(specfolder)
933
                .map_err(FlexiLoggerError::SpecfileIo)?;
934
        }
935
        let mut file = std::fs::OpenOptions::new()
936
            .write(true)
937
            .create_new(true)
938
            .open(specfile)
939
            .map_err(FlexiLoggerError::SpecfileIo)?;
940
941
        subscriber.initial_spec()?.to_toml(&mut file)?;
942
    }
943
    Ok(())
944
}
945
946
#[cfg(feature = "specfile_without_notification")]
947
pub(crate) fn log_spec_string_from_file<P: AsRef<Path>>(
948
    specfile: P,
949
) -> Result<String, std::io::Error> {
950
    let mut buf = String::new();
951
    let mut file = std::fs::File::open(specfile)?;
952
    file.read_to_string(&mut buf)?;
953
    Ok(buf)
954
}
955
956
/// Used to control which messages are to be duplicated to stderr, when `log_to_file()` is used.
957
#[derive(Debug, Clone, Copy)]
958
pub enum Duplicate {
959
    /// No messages are duplicated.
960
    None = 0,
961
    /// Only error messages are duplicated.
962
    Error = 1,
963
    /// Error and warn messages are duplicated.
964
    Warn = 2,
965
    /// Error, warn, and info messages are duplicated.
966
    Info = 3,
967
    /// Error, warn, info, and debug messages are duplicated.
968
    Debug = 4,
969
    /// All messages are duplicated.
970
    Trace = 5,
971
    /// All messages are duplicated.
972
    All = 6,
973
}
974
impl From<u8> for Duplicate {
975
0
    fn from(val: u8) -> Self {
976
0
        match val {
977
0
            0 => Duplicate::None,
978
0
            1 => Duplicate::Error,
979
0
            2 => Duplicate::Warn,
980
0
            3 => Duplicate::Info,
981
0
            4 => Duplicate::Debug,
982
0
            5 => Duplicate::Trace,
983
0
            6 => Duplicate::All,
984
0
            _ => unreachable!(),
985
        }
986
0
    }
987
}
988
989
impl From<LevelFilter> for Duplicate {
990
0
    fn from(level: LevelFilter) -> Self {
991
0
        match level {
992
0
            LevelFilter::Off => Duplicate::None,
993
0
            LevelFilter::Error => Duplicate::Error,
994
0
            LevelFilter::Warn => Duplicate::Warn,
995
0
            LevelFilter::Info => Duplicate::Info,
996
0
            LevelFilter::Debug => Duplicate::Debug,
997
0
            LevelFilter::Trace => Duplicate::Trace,
998
        }
999
0
    }
1000
}
1001
impl From<Duplicate> for LevelFilter {
1002
0
    fn from(level: Duplicate) -> Self {
1003
0
        match level {
1004
0
            Duplicate::None => LevelFilter::Off,
1005
0
            Duplicate::Error => LevelFilter::Error,
1006
0
            Duplicate::Warn => LevelFilter::Warn,
1007
0
            Duplicate::Info => LevelFilter::Info,
1008
0
            Duplicate::Debug => LevelFilter::Debug,
1009
0
            Duplicate::Trace | Duplicate::All => LevelFilter::Trace,
1010
        }
1011
0
    }
1012
}