Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

subscriber: add example of Option<Layer> #1596

Merged
merged 17 commits into from
Sep 29, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 68 additions & 27 deletions tracing-subscriber/src/layer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,40 +395,80 @@
//!
//! ## Runtime Configuration With Layers
//!
//! Conditionally enabling/disabling layers at runtime can be challenging due to the generics
//! used Layers implementations. However, A Layer wrapped in an `Option` is also Layers, which
//! allows some runtime configuration:
//! In some cases, a particular [`Layer`] may be enabled or disabled based on
//! runtime configuration. This can introduce challenges, because the type of a
//! layered [`Subscriber`] depends on which layers are added to it: if an `if`
//! or `match` expression adds some [`Layer`]s in one branch and other layers
//! in another, the [`Subscriber`] values returned by those branches will have
//! different types. For example, the following _will not_ work:
//!
//! ```compile_fail
//! # fn docs() -> Result<(), Box<dyn std::error::Error + 'static>> {
//! # use std::path::PathBuf;
//! # struct Config {
//! # is_prod: bool,
//! # path: PathBuf,
//! # }
//! # let cfg = Config { is_prod: false, path: PathBuf::from("debug.log") };
davidbarsky marked this conversation as resolved.
Show resolved Hide resolved
//! use std::{fs::File, sync::Arc};
//! use tracing_subscriber::{Registry, prelude::*};
//!
//! let stdout_log = tracing_subscriber::fmt::layer().pretty();
//! let subscriber = Registry::default().with(stdout_log);
//!
//! // The compile error will occur here because the if and else
//! // branches have different, and therefore incompatible, types.
davidbarsky marked this conversation as resolved.
Show resolved Hide resolved
//! let subscriber = if cfg.is_prod {
//! let file = File::create(cfg.path)?;
//! let layer = tracing_subscriber::fmt::layer()
//! .json()
//! .with_writer(Arc::new(file));
//! subscriber.with(layer)
//! } else {
//! subscriber
//! };
//!
//! tracing::subscriber::set_global_default(subscriber)
//! .expect("Unable to set global subscriber");
//! # Ok(()) }
//! ```
//! # // wrap this in a function so we don't actually create `debug.log` when
//! # // running the doctests..
//!
//! However, a [`Layer`] wrapped in an [`Option`] [also implements the `Layer`
//! trait][option-impl]. This allows individual layers to be enabled or disabled at
//! runtime while always producing a [`Subscriber`] of the same type. For
//! example:
//!
//! ```
//! # use std::path::PathBuf;
//! # fn docs() -> Result<(), Box<dyn std::error::Error + 'static>> {
//! # struct Config {
//! # is_prod: bool,
//! # path: PathBuf,
//! # }
//! # let cfg = Config { is_prod: false, path: PathBuf::from("debug.log") };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit/tioli: this could maybe be simplified to avoid having to import PathBuf

Suggested change
//! # use std::path::PathBuf;
//! # fn docs() -> Result<(), Box<dyn std::error::Error + 'static>> {
//! # struct Config {
//! # is_prod: bool,
//! # path: PathBuf,
//! # }
//! # let cfg = Config { is_prod: false, path: PathBuf::from("debug.log") };
//! # fn docs() -> Result<(), Box<dyn std::error::Error + 'static>> {
//! # struct Config {
//! # is_prod: bool,
//! # path: &'static str,
//! # }
//! # let cfg = Config { is_prod: false, path: "debug.log" };

//! use std::{fs::File, sync::Arc};
hawkw marked this conversation as resolved.
Show resolved Hide resolved
//! use tracing_subscriber::{Registry, prelude::*};
//! use std::{fs::File, sync::Arc, path::PathBuf};
//! let stdout_log = tracing_subscriber::fmt::layer()
//! .pretty();
//!
//! // If the LOG_PATH environment variable is set, also log events
//! // to that file. If the environment variable is not set, then `debug_log`
//! // will be `None`.
//! let debug_log = match std::env::var("LOG_PATH") {
//! Ok(path) => {
//! let mut path = PathBuf::from(path);
//! path.push("debug.log");
//! let file = File::create(path)?;
//! let layer = tracing_subscriber::fmt::layer()
//! .with_writer(Arc::new(file));
//! Some(layer)
//! },
//! Err(_) => None,
//! let stdout_log = tracing_subscriber::fmt::layer().pretty();
//! let subscriber = Registry::default().with(stdout_log);
//!
//! // if cfg.is_prod is true, also log JSON-based logs to a file.
//! // note: in reality, https://crates.io/crates/tracing-appender should
//! // be used instead of the example below.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure if it's really important to add the note about tracing-appender...i wouldn't say it's a hard and fast rule that that crate should always be used when logging to a file, if the user doesn't intend to do things like rotating the log file. not a blocker, though....

//! let json_log = if cfg.is_prod {
//! let file = File::create(cfg.path)?;
//! let json_log = tracing_subscriber::fmt::layer()
//! .json()
//! .with_writer(Arc::new(file));
//! Some(json_log)
//! } else {
//! None
//! };
//!
//! let subscriber = Registry::default()
//! .with(stdout_log)
//! // If the log file is not enabled, `debug_log` will be `None`, and this layer
//! // will do nothing. However, the subscriber will still have the same _type_
//! // regardless of whether the `Option`'s value is `None` or `Some`.
//! .with(debug_log);
//! // If cfg.is_prod file is false, then `json` will be `None`, and this layer
davidbarsky marked this conversation as resolved.
Show resolved Hide resolved
//! // will do nothing. However, the subscriber will still have the same _type_
//! // regardless of whether the `Option`'s value is `None` or `Some`.
//! let subscriber = subscriber.with(json_log);
//!
//! tracing::subscriber::set_global_default(subscriber)
//! .expect("Unable to set global subscriber");
Expand All @@ -444,6 +484,7 @@
//! [`Layer::register_callsite`]: Layer::register_callsite
//! [`Layer::enabled`]: Layer::enabled
//! [`Interest::never()`]: https://docs.rs/tracing-core/latest/tracing_core/subscriber/struct.Interest.html#method.never
//! [option-impl]: crate::layer::Layer#impl-Layer<S>-for-Option<L>
//! [`Filtered`]: crate::filter::Filtered
//! [`filter`]: crate::filter
//! [`Targets`]: crate::filter::Targets
Expand Down