1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! ## Use `flexi_logger` functionality with [`tracing`](https://docs.rs/tracing/latest/tracing/).
//!
//! [`tracing`](https://docs.rs/tracing/latest/tracing/) is an alternative to
//! [`log`](https://docs.rs/log/latest/log/).
//! It has a similar base architecture, but is optimized for supporting async apps,
//! which adds complexity due to the need to manage contexts.
//! [`tracing-subscriber`](https://docs.rs/tracing/latest/tracing-subscriber/)
//! facilitates contributing "backends", and is used in the example below to plug
//! `flexi_logger`-functionality into `tracing`.
//!
//! **The content of this module is a first attempt to support such an integration.
//! Every feedback is highly appreciated.**
//!
//! ### Example
//!
//! The following example uses a `FileLogWriter` as trace writer,
//! and `flexi_logger`'s specfile handling to adapt `tracing` dynamically,
//! while your program is running.
//! The code is a bit cumbersome, maybe there are (oor will be) easier ways to achieve the same.
//!
//! Precondition: add these entries to your `Cargo.toml`:
//! ```toml
//! flexi_logger = {version = "0.19", features = ["trc"]}
//! tracing = "0.1"
//! tracing-subscriber = {version = "0.2.20", features = ["env-filter"]}
//! ```
//!
//! ```rust,ignore
//! # #[cfg(feature = "specfile_without_notification")]
//! # {
//! # use std::error::Error;
//! use flexi_logger::{
//!     trc::{subscribe_to_specfile, BasicLogSpecSubscriber, LogSpecAsFilter},
//!     writers::FileLogWriter,
//!     Age, Cleanup, Criterion, FileSpec, LogSpecification, Naming, WriteMode,
//! };
//!
//! use tracing::{debug, info, trace, warn};
//! use tracing_subscriber::FmtSubscriber;
//!
//! # fn main() -> Result<(), Box<dyn Error>> {
//! // Prepare a `FileLogWriter` and a handle to it, and keep the handle alive
//! // until the program ends (it will flush and shutdown the `FileLogWriter` when dropped).
//! // For the `FileLogWriter`, use the settings that fit your needs
//! let (file_writer, _fw_handle) = FileLogWriter::builder(FileSpec::default())
//!     .rotate(
//!         // If the program runs long enough,
//!         Criterion::Age(Age::Day), // - create a new file every day
//!         Naming::Timestamps,       // - let the rotated files have a timestamp in their name
//!         Cleanup::KeepLogFiles(7), // - keep at most seven log files
//!     )
//!     .write_mode(WriteMode::Async)
//!     .try_build_with_handle()
//!     .unwrap();
//!
//! // Set up subscriber that makes use of the file writer, with some hardcoded initial log spec
//! let initial_logspec = LogSpecification::info();
//! let subscriber_builder = FmtSubscriber::builder()
//!     .with_writer(move || file_writer.clone())
//!     .with_env_filter(LogSpecAsFilter(initial_logspec.clone()))
//!     .with_filter_reloading();
//!
//! // Set up specfile tracking and subscribe
//! let reload_handle = Box::new(subscriber_builder.reload_handle());
//! subscribe_to_specfile(
//!     "trcspecfile.toml",
//!     BasicLogSpecSubscriber::new(
//!         Box::new(move |logspec| reload_handle.reload(LogSpecAsFilter(logspec)).unwrap()),
//!         initial_logspec,
//!     ),
//! )
//! .unwrap();
//!
//! // Get ready to trace
//! tracing::subscriber::set_global_default(subscriber_builder.finish())
//!     .expect("setting default subscriber failed");
//!
//! // now do what you really want to do...
//! # Ok(())}}
//! ```
//!

pub use crate::logger_handle::LogSpecSubscriber;
use crate::{FlexiLoggerError, LogSpecification};
use std::path::Path;
use tracing_subscriber::EnvFilter;

/// Helper struct for using [`LogSpecification`] as filter in `tracing`.
pub struct LogSpecAsFilter(pub LogSpecification);

impl From<LogSpecAsFilter> for EnvFilter {
    fn from(my_filter: LogSpecAsFilter) -> Self {
        Self::new(my_filter.0.to_string())
    }
}

/// Allows registering a `LogSpecSubscriber` to a specfile.
///
/// Every update to the specfile will be noticed (via crate `notify`),
/// the file will be re-read, and the `LogSpecSubscriber` will be updated.
///
/// # Errors
///
/// Several variants of [`FlexiLoggerError`] can occur.
#[cfg(feature = "specfile_without_notification")]
pub fn subscribe_to_specfile<P: AsRef<Path>, H: LogSpecSubscriber>(
    specfile: P,
    subscriber: H,
) -> Result<(), FlexiLoggerError> {
    crate::logger::subscribe_to_specfile(specfile, subscriber)
}

/// Helper struct that can be registered in
/// [`subscribe_to_specfile`](fn.subscribe_to_specfile.html) to get
/// informed about updates to the specfile,
/// and can be registered in `tracing` to forward such updates.
#[cfg(feature = "specfile_without_notification")]
pub struct BasicLogSpecSubscriber {
    initial_logspec: LogSpecification,
    update: Box<(dyn Fn(LogSpecification) + Send + Sync)>,
}
impl BasicLogSpecSubscriber {
    /// Factory method.
    ///
    /// # Parameters
    /// `initial_logspec`: used to initialize the logspec file if it does not yet exist
    ///
    /// update: Closure that implements the update of the log specification to some consumer
    #[must_use]
    pub fn new(
        update: Box<(dyn Fn(LogSpecification) + Send + Sync)>,
        initial_logspec: LogSpecification,
    ) -> Self {
        Self {
            initial_logspec,
            update,
        }
    }
}
#[cfg(feature = "specfile_without_notification")]
impl LogSpecSubscriber for BasicLogSpecSubscriber {
    fn set_new_spec(&mut self, logspec: LogSpecification) -> Result<(), FlexiLoggerError> {
        (self.update)(logspec);
        Ok(())
    }

    fn initial_spec(&self) -> Result<LogSpecification, FlexiLoggerError> {
        Ok(self.initial_logspec.clone())
    }
}