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
use crate::log_specification::LogSpecification;
use crate::primary_writer::PrimaryWriter;

use std::borrow::Borrow;
use std::sync::Arc;
use std::sync::RwLock;

/// Allows reconfiguring the logger programmatically.
///
/// # Example
///
/// Obtain the `ReconfigurationHandle` (using `.start_reconfigurable()` instead of `.start()`):
/// ```rust
/// # use flexi_logger::{Logger, LogSpecBuilder};
/// let mut log_handle = Logger::with_str("info")
///     // ... your logger configuration goes here, as usual
///     .start_reconfigurable()
///     .unwrap_or_else(|e| panic!("Logger initialization failed with {}", e));
///
/// // ...
/// ```
///
/// You can permanently exchange the log specification programmatically:
///
/// ```rust
/// # use flexi_logger::{Logger, LogSpecBuilder};
/// # let mut log_handle = Logger::with_str("info")
/// #     .start_reconfigurable()
/// #     .unwrap_or_else(|e| panic!("Logger initialization failed with {}", e));
/// log_handle.parse_new_spec("warn");
/// // ...
/// ```
///
/// However, when debugging, you often want to modify the log spec only temporarily, for  
/// one or few method calls only; this is easier done with the following method, because
/// it allows switching back to the previous spec:
///
/// ```rust
/// # use flexi_logger::{Logger, LogSpecBuilder};
/// #    let mut log_handle = Logger::with_str("info")
/// #        .start_reconfigurable()
/// #        .unwrap_or_else(|e| panic!("Logger initialization failed with {}", e));
/// log_handle.parse_and_push_temp_spec("trace");
/// // ...
/// // critical calls
/// // ...
///
/// log_handle.pop_temp_spec();
/// // Continue with the log spec you had before.
/// // ...
/// ```

pub struct ReconfigurationHandle {
    spec: Arc<RwLock<LogSpecification>>,
    spec_stack: Vec<LogSpecification>,
    primary_writer: Arc<PrimaryWriter>,
}
impl ReconfigurationHandle {
    /// Allows specifying a new LogSpecification for the current logger.
    pub fn set_new_spec(&mut self, new_spec: LogSpecification) {
        let mut guard = self.spec.write().unwrap(/* not sure if we should expose this */);
        guard.reconfigure(new_spec);
    }

    /// Allows specifying a new LogSpecification for the current logger.
    pub fn parse_new_spec(&mut self, spec: &str) {
        let mut guard = self.spec.write().unwrap(/* not sure if we should expose this */);
        guard.reconfigure(LogSpecification::parse(spec).unwrap_or_else(|e| {
            eprintln!("ReconfigurationHandle::parse_new_spec(): failed with {}", e);
            LogSpecification::off()
        }));
    }

    /// Allows temporarily pushing a new LogSpecification for the current logger.
    pub fn push_temp_spec(&mut self, new_spec: LogSpecification) {
        let mut guard = self.spec.write().unwrap(/* not sure if we should expose this */);
        self.spec_stack.push(guard.clone());
        guard.reconfigure(new_spec);
    }

    /// Allows temporarily pushing a new LogSpecification for the current logger.
    pub fn parse_and_push_temp_spec(&mut self, new_spec: &str) {
        let mut guard = self.spec.write().unwrap(/* not sure if we should expose this */);
        let new_spec = LogSpecification::parse(new_spec).unwrap_or_else(|e| {
            eprintln!(
                "ReconfigurationHandle::parse_new_spec(): failed with {}, \
                 falling back to empty log spec",
                e
            );
            LogSpecification::off()
        });
        self.spec_stack.push(guard.clone());
        guard.reconfigure(new_spec);
    }

    /// Allows pushing a new LogSpecification for the current logger.
    /// It will automatically be popped once the returned guard is dropped.
    pub fn pop_temp_spec(&mut self) {
        let mut guard = self.spec.write().unwrap(/* not sure if we should expose this */);
        if let Some(new_spec) = self.spec_stack.pop() {
            guard.reconfigure(new_spec);
        }
    }

    // Allows checking the logs written so far to the writer
    #[doc(hidden)]
    pub fn validate_logs(&self, expected: &[(&'static str, &'static str, &'static str)]) -> bool {
        Borrow::<PrimaryWriter>::borrow(&self.primary_writer).validate_logs(expected)
    }
}

pub(crate) fn reconfiguration_handle(
    spec: Arc<RwLock<LogSpecification>>,
    primary_writer: Arc<PrimaryWriter>,
) -> ReconfigurationHandle {
    ReconfigurationHandle {
        spec,
        spec_stack: Default::default(),
        primary_writer,
    }
}