[go: up one dir, main page]

fern/
lib.rs

1#![deny(missing_docs)]
2#![doc(html_root_url = "https://docs.rs/fern/0.7.1")]
3//! Efficient, configurable logging in Rust.
4//!
5//! # fern 0.4.4, 0.5.\*, 0.6.\* security warning - `colored` feature + global allocator
6//!
7//! One of our downstream dependencies, [atty](https://docs.rs/atty/), through
8//! [colored](https://docs.rs/colored/), has an unsoundness issue:
9//! <https://rustsec.org/advisories/RUSTSEC-2021-0145.html>.
10//!
11//! This shows up in one situation: if you're using `colored` 0.1.0 (the crate, or our
12//! feature), and a custom global allocator.
13//!
14//! Upgrade to `fern` 0.7.0, and `colored` 0.2.0 if you depend on it directly, to fix this issue.
15//!
16//! # Depending on fern
17//!
18//! Ensure you require both fern and log in your project's `Cargo.toml`:
19//!
20//! ```toml
21//! [dependencies]
22//! log = "0.4"
23//! fern = "0.7"
24//! ```
25//!
26//! # Example setup
27//!
28//! With fern, all logger configuration is done via builder-like methods on
29//! instances of the [`Dispatch`] structure.
30//!
31//! Here's an example logger which formats messages, and sends everything Debug
32//! and above to both stdout and an output.log file:
33//!
34//! ```no_run
35//! use log::{debug, error, info, trace, warn};
36//! use std::time::SystemTime;
37//!
38//! fn setup_logger() -> Result<(), fern::InitError> {
39//!     fern::Dispatch::new()
40//!         .format(|out, message, record| {
41//!             out.finish(format_args!(
42//!                 "[{} {} {}] {}",
43//!                 humantime::format_rfc3339_seconds(SystemTime::now()),
44//!                 record.level(),
45//!                 record.target(),
46//!                 message
47//!             ))
48//!         })
49//!         .level(log::LevelFilter::Debug)
50//!         .chain(std::io::stdout())
51//!         .chain(fern::log_file("output.log")?)
52//!         .apply()?;
53//!     Ok(())
54//! }
55//!
56//! fn main() -> Result<(), Box<dyn std::error::Error>> {
57//!     setup_logger()?;
58//!
59//!     info!("Hello, world!");
60//!     warn!("Warning!");
61//!     debug!("Now exiting.");
62//!
63//!     Ok(())
64//! }
65//! ```
66//!
67//! Let's unwrap this:
68//!
69//!
70//! ```
71//! fern::Dispatch::new()
72//! # ;
73//! ```
74//!
75//! [`Dispatch::new`] creates an empty configuration.
76//!
77//! ```
78//! # fern::Dispatch::new()
79//! .format(|out, message, record| {
80//!     out.finish(format_args!(
81//!         "..."
82//!     ))
83//! })
84//! # ;
85//! ```
86//!
87//! This incantation sets the `Dispatch` format! The closure taking in
88//! `out, message, record` will be called once for each message going through
89//! the dispatch, and the formatted log message will be used for any downstream
90//! consumers.
91//!
92//! Do any work you want in this closure, and then call `out.finish` at the end.
93//! The callback-style result passing with `out.finish(format_args!())` lets us
94//! format without any intermediate string allocation.
95//!
96//! [`format_args!`] has the same format as [`println!`], just returning a
97//! not-yet-written result we can use internally.
98//!
99//! ```
100//! std::time::SystemTime::now()
101//! # ;
102//! ```
103//!
104//! [`std::time::SystemTime::now`] retrieves the current time.
105//!
106//! ```
107//! humantime::format_rfc3339_seconds(
108//!     // ...
109//!     # std::time::SystemTime::now()
110//! )
111//! # ;
112//! ```
113//!
114//! [`humantime::format_rfc3339_seconds`] formats the current time into an
115//! RFC3339 timestamp, with second-precision.
116//!
117//! RFC3339 looks like `2018-02-14T00:28:07Z`, always using UTC, ignoring system
118//! timezone.
119//!
120//! `humantime` is a nice light dependency, but only offers this one format.
121//! For more custom time formatting, I recommend
122//! [`jiff`](https://docs.rs/jiff/).
123//!
124//! Now, back to the [`Dispatch`] methods:
125//!
126//! ```
127//! # fern::Dispatch::new()
128//! .level(log::LevelFilter::Debug)
129//! # ;
130//! ```
131//!
132//! Sets the minimum logging level for all modules, if not overwritten with
133//! [`Dispatch::level_for`], to [`Level::Debug`][log::Level::Debug].
134//!
135//! ```
136//! # fern::Dispatch::new()
137//! .chain(std::io::stdout())
138//! # ;
139//! ```
140//!
141//! Adds a child to the logger. With this, all messages which pass the filters
142//! will be sent to stdout.
143//!
144//! [`Dispatch::chain`] accepts [`Stdout`], [`Stderr`], [`File`] and other
145//! [`Dispatch`] instances.
146//!
147//! ```
148//! # fern::Dispatch::new()
149//! .chain(fern::log_file("output.log")?)
150//! # ; <Result<(), Box<dyn std::error::Error>>>::Ok(())
151//! ```
152//!
153//! Adds a second child sending messages to the file "output.log".
154//!
155//! See [`log_file`].
156//!
157//! ```
158//! # fern::Dispatch::new()
159//! // ...
160//! .apply()
161//! # ;
162//! ```
163//!
164//! Consumes the configuration and instantiates it as the current runtime global
165//! logger.
166//!
167//! This will fail if and only if `.apply()` or equivalent form another crate
168//! has already been used this runtime.
169//!
170//! Since the binary crate is the only one ever setting up logging, and it's
171//! usually done near the start of `main`, the [`Dispatch::apply`] result can be
172//! reasonably unwrapped: it's a bug if any crate is calling this method more
173//! than once.
174//!
175//! ---
176//!
177//! The final output will look like:
178//!
179//! ```text
180//! [2023-03-18T20:12:50Z INFO cmd_program] Hello, world!
181//! [2023-03-18T20:12:50Z WARN cmd_program] Warning!
182//! [2023-03-18T20:12:50Z DEBUG cmd_program] Now exiting.
183//! ```
184//!
185//! # Logging
186//!
187//! Once the logger has been set, it will pick up all logging calls from your
188//! crate and all libraries you depend on.
189//!
190//! ```rust
191//! # use log::{debug, error, info, trace, warn};
192//!
193//! # fn setup_logger() -> Result<(), fern::InitError> {
194//! fern::Dispatch::new()
195//!     // ...
196//!     .apply()?;
197//! # Ok(())
198//! # }
199//!
200//! # fn main() {
201//! # setup_logger().ok(); // we're ok with this not succeeding.
202//! trace!("Trace message");
203//! debug!("Debug message");
204//! info!("Info message");
205//! warn!("Warning message");
206//! error!("Error message");
207//! # }
208//! ```
209//!
210//! # More
211//!
212//! The [`Dispatch`] documentation has example usages of each method, and the
213//! [full example program] might be useful for using fern in a larger
214//! application context.
215//!
216//! See the [colors] module for examples using ANSI terminal coloring.
217//!
218//! See the [syslog] module for examples outputting to the unix syslog, or the
219//! [syslog full example program] for a more realistic sample.
220//!
221//! See the [meta] module for information on getting logging-within-logging
222//! working correctly.
223//!
224//! [`Stdout`]: std::io::Stdout
225//! [`Stderr`]: std::io::Stderr
226//! [`File`]: std::fs::File
227//! [full example program]: https://github.com/daboross/fern/tree/fern-0.7.0/examples/cmd-program.rs
228//! [syslog full example program]: https://github.com/daboross/fern/tree/fern-0.7.0/examples/syslog.rs
229//! [`humantime::format_rfc3339_seconds`]: https://docs.rs/humantime/2/humantime/fn.format_rfc3339_seconds.html
230use std::{
231    convert::AsRef,
232    fmt,
233    fs::{File, OpenOptions},
234    io,
235    path::Path,
236};
237
238#[cfg(all(not(windows), any(feature = "syslog-4", feature = "syslog-6")))]
239use std::collections::HashMap;
240
241#[cfg(all(not(windows), feature = "syslog-7"))]
242use std::collections::BTreeMap;
243
244pub use crate::{
245    builders::{Dispatch, Output, Panic},
246    errors::InitError,
247    log_impl::FormatCallback,
248};
249
250mod builders;
251mod errors;
252mod log_impl;
253
254#[cfg(feature = "colored")]
255pub mod colors;
256#[cfg(all(
257    feature = "syslog-3",
258    feature = "syslog-4",
259    // disable on windows when running doctests, as the code itself only runs on
260    // linux. enable on windows otherwise because it's a documentation-only
261    // module.
262    any(not(windows), not(doctest))
263))]
264pub mod syslog;
265
266pub mod meta;
267
268/// A type alias for a log formatter.
269///
270/// As of fern `0.5`, the passed `fmt::Arguments` will always be the same as
271/// the given `log::Record`'s `.args()`.
272pub type Formatter = dyn Fn(FormatCallback, &fmt::Arguments, &log::Record) + Sync + Send + 'static;
273
274/// A type alias for a log filter. Returning true means the record should
275/// succeed - false means it should fail.
276pub type Filter = dyn Fn(&log::Metadata) -> bool + Send + Sync + 'static;
277
278#[cfg(feature = "date-based")]
279pub use crate::builders::DateBased;
280
281#[cfg(all(not(windows), feature = "syslog-4"))]
282type Syslog4Rfc3164Logger = syslog4::Logger<syslog4::LoggerBackend, String, syslog4::Formatter3164>;
283
284#[cfg(all(not(windows), feature = "syslog-4"))]
285type Syslog4Rfc5424Logger = syslog4::Logger<
286    syslog4::LoggerBackend,
287    (i32, HashMap<String, HashMap<String, String>>, String),
288    syslog4::Formatter5424,
289>;
290
291#[cfg(all(not(windows), feature = "syslog-6"))]
292type Syslog6Rfc3164Logger = syslog6::Logger<syslog6::LoggerBackend, syslog6::Formatter3164>;
293
294#[cfg(all(not(windows), feature = "syslog-6"))]
295type Syslog6Rfc5424Logger = syslog6::Logger<syslog6::LoggerBackend, syslog6::Formatter5424>;
296
297#[cfg(all(not(windows), feature = "syslog-7"))]
298type Syslog7Rfc3164Logger = syslog7::Logger<syslog7::LoggerBackend, syslog7::Formatter3164>;
299
300#[cfg(all(not(windows), feature = "syslog-7"))]
301type Syslog7Rfc5424Logger = syslog7::Logger<syslog7::LoggerBackend, syslog7::Formatter5424>;
302
303#[cfg(all(not(windows), feature = "syslog-4"))]
304type Syslog4TransformFn =
305    dyn Fn(&log::Record) -> (i32, HashMap<String, HashMap<String, String>>, String) + Send + Sync;
306
307#[cfg(all(not(windows), feature = "syslog-6"))]
308type Syslog6TransformFn =
309    dyn Fn(&log::Record) -> (u32, HashMap<String, HashMap<String, String>>, String) + Send + Sync;
310
311#[cfg(all(not(windows), feature = "syslog-7"))]
312type Syslog7TransformFn =
313    dyn Fn(&log::Record) -> (u32, BTreeMap<String, BTreeMap<String, String>>, String) + Send + Sync;
314
315/// Convenience method for opening a log file with common options.
316///
317/// Equivalent to:
318///
319/// ```no_run
320/// std::fs::OpenOptions::new()
321///     .write(true)
322///     .create(true)
323///     .append(true)
324///     .open("filename")
325/// # ;
326/// ```
327///
328/// See [`OpenOptions`] for more information.
329///
330/// [`OpenOptions`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html
331#[inline]
332pub fn log_file<P: AsRef<Path>>(path: P) -> io::Result<File> {
333    OpenOptions::new().create(true).append(true).open(path)
334}
335
336/// Convenience method for opening a re-openable log file with common options.
337///
338/// The file opening is equivalent to:
339///
340/// ```no_run
341/// std::fs::OpenOptions::new()
342///     .write(true)
343///     .create(true)
344///     .append(true)
345///     .open("filename")
346/// # ;
347/// ```
348///
349/// See [`OpenOptions`] for more information.
350///
351/// [`OpenOptions`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html
352///
353/// This function is not available on Windows, and it requires the `reopen-03`
354/// feature to be enabled.
355#[cfg(all(not(windows), feature = "reopen-03"))]
356#[inline]
357pub fn log_reopen(path: &Path, signal: Option<libc::c_int>) -> io::Result<reopen03::Reopen<File>> {
358    let p = path.to_owned();
359    let r = reopen03::Reopen::new(Box::new(move || log_file(&p)))?;
360
361    if let Some(s) = signal {
362        r.handle().register_signal(s)?;
363    }
364    Ok(r)
365}
366
367/// Convenience method for opening a re-openable log file with common options.
368///
369/// The file opening is equivalent to:
370///
371/// ```no_run
372/// std::fs::OpenOptions::new()
373///     .write(true)
374///     .create(true)
375///     .append(true)
376///     .open("filename")
377/// # ;
378/// ```
379///
380/// See [`OpenOptions`] for more information.
381///
382/// [`OpenOptions`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html
383///
384/// This function requires the `reopen-1` feature to be enabled.
385#[cfg(all(not(windows), feature = "reopen-1"))]
386#[inline]
387pub fn log_reopen1<S: IntoIterator<Item = libc::c_int>>(
388    path: &Path,
389    signals: S,
390) -> io::Result<reopen1::Reopen<File>> {
391    let p = path.to_owned();
392    let r = reopen1::Reopen::new(Box::new(move || log_file(&p)))?;
393
394    for s in signals {
395        r.handle().register_signal(s)?;
396    }
397    Ok(r)
398}