#[cfg(feature = "buffer_writer")]
use crate::writers::Snapshot;
use crate::{
primary_writer::PrimaryWriter,
util::{eprint_err, ErrorCode},
writers::{FileLogWriterBuilder, FileLogWriterConfig, LogWriter},
Duplicate, FlexiLoggerError, LogSpecification,
};
#[cfg(feature = "specfile")]
use notify_debouncer_mini::{notify::RecommendedWatcher, Debouncer};
#[cfg(feature = "specfile")]
use std::sync::Mutex;
use std::{
collections::HashMap,
path::PathBuf,
sync::{Arc, RwLock},
};
#[derive(Clone)]
pub struct LoggerHandle
where
Self: Send + Sync,
{
pub(crate) writers_handle: WritersHandle,
#[cfg(feature = "specfile")]
pub(crate) oam_specfile_watcher: Option<Arc<Mutex<Debouncer<RecommendedWatcher>>>>,
}
impl LoggerHandle {
pub(crate) fn new(
spec: Arc<RwLock<LogSpecification>>,
primary_writer: Arc<PrimaryWriter>,
other_writers: Arc<HashMap<String, Box<dyn LogWriter>>>,
) -> Self {
Self {
writers_handle: WritersHandle {
spec,
spec_stack: Vec::default(),
primary_writer,
other_writers,
},
#[cfg(feature = "specfile")]
oam_specfile_watcher: None,
}
}
pub(crate) fn reconfigure(&self, max_level: log::LevelFilter) {
self.writers_handle.reconfigure(max_level);
}
pub fn set_new_spec(&self, new_spec: LogSpecification) {
self.writers_handle
.set_new_spec(new_spec)
.map_err(|e| eprint_err(ErrorCode::Poison, "rwlock on log spec is poisoned", &e))
.ok();
}
pub fn parse_new_spec(&self, spec: &str) -> Result<(), FlexiLoggerError> {
self.set_new_spec(LogSpecification::parse(spec)?);
Ok(())
}
#[allow(clippy::missing_panics_doc)]
pub fn push_temp_spec(&mut self, new_spec: LogSpecification) {
self.writers_handle
.spec_stack
.push(self.writers_handle.spec.read().unwrap().clone());
self.set_new_spec(new_spec);
}
pub fn parse_and_push_temp_spec<S: AsRef<str>>(
&mut self,
new_spec: S,
) -> Result<(), FlexiLoggerError> {
self.writers_handle.spec_stack.push(
self.writers_handle
.spec
.read()
.map_err(|_| FlexiLoggerError::Poison)?
.clone(),
);
self.set_new_spec(LogSpecification::parse(new_spec)?);
Ok(())
}
pub fn pop_temp_spec(&mut self) {
if let Some(previous_spec) = self.writers_handle.spec_stack.pop() {
self.set_new_spec(previous_spec);
}
}
pub fn flush(&self) {
self.writers_handle.primary_writer.flush().ok();
for writer in self.writers_handle.other_writers.values() {
writer.flush().ok();
}
}
pub fn reset_flw(&self, flwb: &FileLogWriterBuilder) -> Result<(), FlexiLoggerError> {
if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer {
mw.reset_file_log_writer(flwb)
} else {
Err(FlexiLoggerError::NoFileLogger)
}
}
pub fn flw_config(&self) -> Result<FileLogWriterConfig, FlexiLoggerError> {
if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer {
mw.flw_config()
} else {
Err(FlexiLoggerError::NoFileLogger)
}
}
pub fn current_max_level(&self) -> Result<log::LevelFilter, FlexiLoggerError> {
let read_guard = self
.writers_handle
.spec
.read()
.map_err(|_| FlexiLoggerError::Poison)?;
Ok(read_guard.max_level())
}
pub fn current_log_spec(&self) -> Result<LogSpecification, FlexiLoggerError> {
Ok(self
.writers_handle
.spec
.read()
.map_err(|_| FlexiLoggerError::Poison)?
.clone())
}
pub fn reopen_output(&self) -> Result<(), FlexiLoggerError> {
let mut result = if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer
{
mw.reopen_output()
} else {
Ok(())
};
for blw in self.writers_handle.other_writers.values() {
let result2 = blw.reopen_output();
if result.is_ok() && result2.is_err() {
result = result2;
}
}
result
}
pub fn trigger_rotation(&self) -> Result<(), FlexiLoggerError> {
let mut result = if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer
{
mw.trigger_rotation()
} else {
Ok(())
};
for blw in self.writers_handle.other_writers.values() {
let result2 = blw.rotate();
if result.is_ok() && result2.is_err() {
result = result2;
}
}
result
}
#[cfg(feature = "buffer_writer")]
pub fn update_snapshot(&self, snapshot: &mut Snapshot) -> Result<bool, FlexiLoggerError> {
if let PrimaryWriter::Multi(ref mw) = *self.writers_handle.primary_writer {
if let Some(s) = mw.get_buffer_writer() {
return s.update_snapshot(snapshot);
}
}
Ok(false)
}
pub fn shutdown(&self) {
self.writers_handle.primary_writer.shutdown();
for writer in self.writers_handle.other_writers.values() {
writer.shutdown();
}
}
pub fn existing_log_files(
&self,
selector: &LogfileSelector,
) -> Result<Vec<PathBuf>, FlexiLoggerError> {
let mut log_files = self
.writers_handle
.primary_writer
.existing_log_files(selector)?;
log_files.sort();
Ok(log_files)
}
pub fn adapt_duplication_to_stderr(&mut self, dup: Duplicate) -> Result<(), FlexiLoggerError> {
if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer {
mw.adapt_duplication_to_stderr(dup);
Ok(())
} else {
Err(FlexiLoggerError::NoFileLogger)
}
}
pub fn adapt_duplication_to_stdout(&mut self, dup: Duplicate) -> Result<(), FlexiLoggerError> {
if let PrimaryWriter::Multi(ref mw) = &*self.writers_handle.primary_writer {
mw.adapt_duplication_to_stdout(dup);
Ok(())
} else {
Err(FlexiLoggerError::NoFileLogger)
}
}
#[doc(hidden)]
pub fn validate_logs(&self, expected: &[(&'static str, &'static str, &'static str)]) {
self.writers_handle.primary_writer.validate_logs(expected);
}
#[doc(hidden)]
pub fn validate_additional_logs(
&self,
target: &str,
expected: &[(&'static str, &'static str, &'static str)],
) {
self.writers_handle
.other_writers
.get(target)
.unwrap()
.validate_logs(expected);
}
}
#[allow(clippy::struct_field_names)]
pub struct LogfileSelector {
pub(crate) with_plain_files: bool,
pub(crate) with_r_current: bool,
pub(crate) with_compressed_files: bool,
pub(crate) with_configured_current: Option<String>,
}
impl Default for LogfileSelector {
fn default() -> Self {
Self {
with_plain_files: true,
with_r_current: false,
with_compressed_files: false,
with_configured_current: None,
}
}
}
impl LogfileSelector {
#[must_use]
pub fn none() -> Self {
Self {
with_plain_files: false,
with_r_current: false,
with_compressed_files: false,
with_configured_current: None,
}
}
#[must_use]
pub fn with_r_current(mut self) -> Self {
self.with_r_current = true;
self
}
#[must_use]
pub fn with_custom_current(mut self, s: &str) -> Self {
self.with_configured_current = Some(s.to_string());
self
}
#[must_use]
pub fn with_compressed_files(mut self) -> Self {
self.with_compressed_files = true;
self
}
}
#[derive(Clone)]
pub(crate) struct WritersHandle {
spec: Arc<RwLock<LogSpecification>>,
spec_stack: Vec<LogSpecification>,
primary_writer: Arc<PrimaryWriter>,
other_writers: Arc<HashMap<String, Box<dyn LogWriter>>>,
}
impl WritersHandle {
fn set_new_spec(&self, new_spec: LogSpecification) -> Result<(), FlexiLoggerError> {
let max_level = new_spec.max_level();
self.spec
.write()
.map_err(|_| FlexiLoggerError::Poison)?
.update_from(new_spec);
self.reconfigure(max_level);
Ok(())
}
pub(crate) fn reconfigure(&self, mut max_level: log::LevelFilter) {
for w in self.other_writers.as_ref().values() {
max_level = std::cmp::max(max_level, w.max_log_level());
}
log::set_max_level(max_level);
}
}
impl Drop for WritersHandle {
fn drop(&mut self) {
self.primary_writer.shutdown();
for writer in self.other_writers.values() {
writer.shutdown();
}
}
}
#[cfg(feature = "specfile_without_notification")]
#[cfg_attr(docsrs, doc(cfg(feature = "specfile")))]
pub trait LogSpecSubscriber: 'static + Send {
fn set_new_spec(&mut self, new_spec: LogSpecification) -> Result<(), FlexiLoggerError>;
fn initial_spec(&self) -> Result<LogSpecification, FlexiLoggerError>;
}
#[cfg(feature = "specfile_without_notification")]
impl LogSpecSubscriber for WritersHandle {
fn set_new_spec(&mut self, new_spec: LogSpecification) -> Result<(), FlexiLoggerError> {
WritersHandle::set_new_spec(self, new_spec)
}
fn initial_spec(&self) -> Result<LogSpecification, FlexiLoggerError> {
Ok((*self.spec.read().map_err(|_e| FlexiLoggerError::Poison)?).clone())
}
}