From 721f8e28e4c536ebdc5d4449a6268af0e0ad67d6 Mon Sep 17 00:00:00 2001 From: emabee Date: Fri, 28 Jul 2023 19:21:54 +0200 Subject: [PATCH] Add methods `LoggerHandle::adapt_duplication_to_stderr` and `LoggerHandle::adapt_duplication_to_stdout` - (realizes issue #142). And: - Extend docu on providing custom format. - Use rust-script instead of cargo-script for qualification scripts. - Update dependencies. --- CHANGELOG.md | 12 +++++++ Cargo.toml | 4 +-- examples/entry_numbers.rs | 32 ++++++++++++++++++ scripts/check.rs | 1 + scripts/cleanup.rs | 1 + scripts/qualify.rs | 1 + scripts/qualify_fast.rs | 5 ++- src/code_examples.md | 23 ++++++++++++- src/flexi_error.rs | 4 +++ src/logger.rs | 28 ++++++++++++---- src/logger_handle.rs | 32 +++++++++++++++++- src/primary_writer/multi_writer.rs | 37 ++++++++++++++++----- src/writers/file_log_writer/state_handle.rs | 4 +-- tests/test_reconfigure_methods.rs | 8 ++--- 14 files changed, 165 insertions(+), 27 deletions(-) create mode 100644 examples/entry_numbers.rs mode change 100644 => 100755 scripts/check.rs mode change 100644 => 100755 scripts/cleanup.rs mode change 100644 => 100755 scripts/qualify.rs mode change 100644 => 100755 scripts/qualify_fast.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 669907e..18c1a73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.25.6] - 2023-07-28 + +Add methods +`LoggerHandle::adapt_duplication_to_stderr` and `LoggerHandle::adapt_duplication_to_stdout` +(realizes issue #142). + +Extend docu on providing custom format. + +Use rust-script instead of cargo-script for qualification scripts. + +Update dependencies. + ## [0.25.5] - 2023-05-25 Use display (rather than debug) formatting for thread names diff --git a/Cargo.toml b/Cargo.toml index 1516973..b06bb5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flexi_logger" -version = "0.25.5" +version = "0.25.6" authors = ["emabee "] categories = ["development-tools::debugging"] description = """ @@ -41,7 +41,7 @@ trc = ["async", "specfile", "tracing", "tracing-subscriber"] [dependencies] is-terminal = { version = "0.4", optional = true } -nu-ansi-term = { version = "0.47", optional = true } +nu-ansi-term = { version = "0.49", optional = true } chrono = { version = "0.4.22", default-features = false, features = ["clock"] } crossbeam-channel = { version = "0.5", optional = true } crossbeam-queue = { version = "0.3", optional = true } diff --git a/examples/entry_numbers.rs b/examples/entry_numbers.rs new file mode 100644 index 0000000..64fbf03 --- /dev/null +++ b/examples/entry_numbers.rs @@ -0,0 +1,32 @@ +use flexi_logger::{DeferredNow, FlexiLoggerError, Logger}; +use log::*; +use std::sync::atomic::{AtomicU32, Ordering}; + +// Produces +// 1 INFO [entry_numbers] first +// 2 WARN [entry_numbers] second +// 3 ERROR [entry_numbers] third +fn main() -> Result<(), FlexiLoggerError> { + Logger::try_with_str("info")?.format(my_format).start()?; + + info!("first"); + warn!("second"); + error!("third"); + Ok(()) +} + +pub fn my_format( + w: &mut dyn std::io::Write, + _now: &mut DeferredNow, + record: &Record, +) -> Result<(), std::io::Error> { + static LINE: AtomicU32 = AtomicU32::new(1); + write!( + w, + "{:>6} {} [{}] {}", + LINE.fetch_add(1, Ordering::Relaxed), + record.level(), + record.module_path().unwrap_or(""), + record.args() + ) +} diff --git a/scripts/check.rs b/scripts/check.rs old mode 100644 new mode 100755 index c0ee739..0b85ce9 --- a/scripts/check.rs +++ b/scripts/check.rs @@ -1,3 +1,4 @@ +#!/usr/bin/env rust-script //! ```cargo //! [dependencies] //! yansi = "0.5" diff --git a/scripts/cleanup.rs b/scripts/cleanup.rs old mode 100644 new mode 100755 index a8202e8..ad8a7b5 --- a/scripts/cleanup.rs +++ b/scripts/cleanup.rs @@ -1,3 +1,4 @@ +#!/usr/bin/env rust-script //! Cleans up all files and folders that were produced by test runs. //! //! ```cargo diff --git a/scripts/qualify.rs b/scripts/qualify.rs old mode 100644 new mode 100755 index 2805659..c7b93e2 --- a/scripts/qualify.rs +++ b/scripts/qualify.rs @@ -1,3 +1,4 @@ +#!/usr/bin/env rust-script //! ```cargo //! [dependencies] //! yansi = "0.5" diff --git a/scripts/qualify_fast.rs b/scripts/qualify_fast.rs old mode 100644 new mode 100755 index ec34153..b83f90d --- a/scripts/qualify_fast.rs +++ b/scripts/qualify_fast.rs @@ -1,3 +1,4 @@ +#!/usr/bin/env rust-script //! ```cargo //! [dependencies] //! yansi = "0.5" @@ -54,5 +55,7 @@ fn main() { run_command!("cargo", "doc", "--all-features", "--no-deps", "--open"); // say goodbye - println!("\n> fast qualification is done :-) Looks like you're ready to do the full qualification?"); + println!( + "\n> fast qualification is done :-) Looks like you're ready to do the full qualification?" + ); } diff --git a/src/code_examples.md b/src/code_examples.md index f5cae6e..a7beccd 100644 --- a/src/code_examples.md +++ b/src/code_examples.md @@ -193,7 +193,28 @@ With [`Logger::format`](crate::Logger::format) you set the format for all used output channels of `flexi_logger`. `flexi_logger` provides a couple of format functions, and you can also create and use your own, -e.g. by copying and modifying one of the provided format functions. +e.g. by copying and modifying one of the provided format functions (see [formats.rs](https://github.com/emabee/flexi_logger/blob/master/src/formats.rs)). + +Here's an example that you could create somewhere in your code. +It also illustrates the signature the format function must have. + +```rust,ignore +pub fn my_own_format( + w: &mut dyn std::io::Write, + now: &mut DeferredNow, + record: &Record, +) -> Result<(), std::io::Error> { + let level = record.level(); + write!( + w, + "{} [Thread {}] Severity {}, Message: {}", + now.format(TS_DASHES_BLANK_COLONS_DOT_BLANK), + thread::current().name().unwrap_or(""), + record.level(), + &record.args() + ) +} +``` Depending on the configuration, `flexi_logger` can write logs to multiple channels (stdout, stderr, files, or additional writers) diff --git a/src/flexi_error.rs b/src/flexi_error.rs index a9b3655..73d7127 100644 --- a/src/flexi_error.rs +++ b/src/flexi_error.rs @@ -10,6 +10,10 @@ pub enum FlexiLoggerError { #[error("Chosen reset not possible")] Reset, + /// Method not possible because duplication is not possible. + #[error("Method not possible because duplication is not possible")] + NoDuplication, + /// Method not possible because no file logger is configured. #[error("Method not possible because no file logger is configured")] NoFileLogger, diff --git a/src/logger.rs b/src/logger.rs index 1fdf200..483f1be 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -966,19 +966,33 @@ pub(crate) fn log_spec_string_from_file>( #[derive(Debug, Clone, Copy)] pub enum Duplicate { /// No messages are duplicated. - None, + None = 0, /// Only error messages are duplicated. - Error, + Error = 1, /// Error and warn messages are duplicated. - Warn, + Warn = 2, /// Error, warn, and info messages are duplicated. - Info, + Info = 3, /// Error, warn, info, and debug messages are duplicated. - Debug, + Debug = 4, /// All messages are duplicated. - Trace, + Trace = 5, /// All messages are duplicated. - All, + All = 6, +} +impl From for Duplicate { + fn from(val: u8) -> Self { + match val { + 0 => Duplicate::None, + 1 => Duplicate::Error, + 2 => Duplicate::Warn, + 3 => Duplicate::Info, + 4 => Duplicate::Debug, + 5 => Duplicate::Trace, + 6 => Duplicate::All, + _ => unreachable!(), + } + } } impl From for Duplicate { diff --git a/src/logger_handle.rs b/src/logger_handle.rs index 7e27d07..a742e44 100644 --- a/src/logger_handle.rs +++ b/src/logger_handle.rs @@ -5,7 +5,7 @@ use crate::{ primary_writer::PrimaryWriter, util::{eprint_err, ErrorCode}, writers::{FileLogWriterBuilder, FileLogWriterConfig, LogWriter}, - {FlexiLoggerError, LogSpecification}, + Duplicate, FlexiLoggerError, LogSpecification, }; use std::{ collections::HashMap, @@ -290,6 +290,36 @@ impl LoggerHandle { Ok(log_files) } + /// Allows re-configuring duplication to stderr. + /// + /// # Errors + /// + /// `FlexiLoggerError::NoDuplication` + /// if `FlexiLogger` was initialized without duplication support + 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) + } + } + + /// Allows re-configuring duplication to stdout. + /// + /// # Errors + /// + /// `FlexiLoggerError::NoDuplication` + /// if `FlexiLogger` was initialized without duplication support + 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) + } + } + // Allows checking the logs written so far to the writer #[doc(hidden)] pub fn validate_logs(&self, expected: &[(&'static str, &'static str, &'static str)]) { diff --git a/src/primary_writer/multi_writer.rs b/src/primary_writer/multi_writer.rs index 5fda53d..2018256 100644 --- a/src/primary_writer/multi_writer.rs +++ b/src/primary_writer/multi_writer.rs @@ -5,13 +5,17 @@ use crate::{ {DeferredNow, FlexiLoggerError, FormatFunction}, }; use log::Record; -use std::{io::Write, path::PathBuf}; +use std::{ + io::Write, + path::PathBuf, + sync::atomic::{AtomicU8, Ordering}, +}; // The `MultiWriter` writes logs to a FileLogWriter and/or another Writer, // and can duplicate messages to stderr or stdout. pub(crate) struct MultiWriter { - duplicate_stderr: Duplicate, - duplicate_stdout: Duplicate, + duplicate_stderr: AtomicU8, + duplicate_stdout: AtomicU8, support_capture: bool, format_for_stderr: FormatFunction, format_for_stdout: FormatFunction, @@ -30,8 +34,8 @@ impl MultiWriter { o_other_writer: Option>, ) -> Self { MultiWriter { - duplicate_stderr, - duplicate_stdout, + duplicate_stderr: AtomicU8::new(duplicate_stderr as u8), + duplicate_stdout: AtomicU8::new(duplicate_stdout as u8), support_capture, format_for_stderr, format_for_stdout, @@ -66,6 +70,21 @@ impl MultiWriter { Ok(Vec::new()) } } + + pub(crate) fn adapt_duplication_to_stderr(&self, dup: Duplicate) { + self.duplicate_stderr.store(dup as u8, Ordering::Relaxed); + } + + pub(crate) fn adapt_duplication_to_stdout(&self, dup: Duplicate) { + self.duplicate_stdout.store(dup as u8, Ordering::Relaxed); + } + + fn duplication_to_stderr(&self) -> Duplicate { + Duplicate::from(self.duplicate_stderr.load(Ordering::Relaxed)) + } + fn duplication_to_stdout(&self) -> Duplicate { + Duplicate::from(self.duplicate_stdout.load(Ordering::Relaxed)) + } } impl LogWriter for MultiWriter { @@ -79,7 +98,7 @@ impl LogWriter for MultiWriter { } fn write(&self, now: &mut DeferredNow, record: &Record) -> std::io::Result<()> { - if match self.duplicate_stderr { + if match self.duplication_to_stderr() { Duplicate::Error => record.level() == log::Level::Error, Duplicate::Warn => record.level() <= log::Level::Warn, Duplicate::Info => record.level() <= log::Level::Info, @@ -104,7 +123,7 @@ impl LogWriter for MultiWriter { } } - if match self.duplicate_stdout { + if match self.duplication_to_stdout() { Duplicate::Error => record.level() == log::Level::Error, Duplicate::Warn => record.level() <= log::Level::Warn, Duplicate::Info => record.level() <= log::Level::Info, @@ -163,10 +182,10 @@ impl LogWriter for MultiWriter { writer.flush()?; } - if !matches!(self.duplicate_stderr, Duplicate::None) { + if !matches!(self.duplication_to_stderr(), Duplicate::None) { std::io::stderr().flush()?; } - if !matches!(self.duplicate_stdout, Duplicate::None) { + if !matches!(self.duplication_to_stdout(), Duplicate::None) { std::io::stdout().flush()?; } Ok(()) diff --git a/src/writers/file_log_writer/state_handle.rs b/src/writers/file_log_writer/state_handle.rs index 71fb47d..b0fdad2 100644 --- a/src/writers/file_log_writer/state_handle.rs +++ b/src/writers/file_log_writer/state_handle.rs @@ -49,7 +49,7 @@ impl std::fmt::Debug for SyncHandle { .field("am_state", &self.am_state) .field("format", &"<..>") .field("line_ending", &self.line_ending) - .finish() + .finish_non_exhaustive() } } @@ -128,7 +128,7 @@ impl std::fmt::Debug for AsyncHandle { .field("message_capa", &self.message_capa) .field("format", &"<..>") .field("line_ending", &self.line_ending) - .finish() + .finish_non_exhaustive() } } diff --git a/tests/test_reconfigure_methods.rs b/tests/test_reconfigure_methods.rs index f9ead2c..f5d6ce3 100644 --- a/tests/test_reconfigure_methods.rs +++ b/tests/test_reconfigure_methods.rs @@ -15,12 +15,12 @@ fn test_reconfigure_methods() { .start() .unwrap_or_else(|e| panic!("Logger initialization failed with {e}")); - test_parse_new_spec(&mut logger); + test_parse_new_spec(&logger); test_push_new_spec(&mut logger); - validate_logs(&mut logger); + validate_logs(&logger); } -fn test_parse_new_spec(logger: &mut LoggerHandle) { +fn test_parse_new_spec(logger: &LoggerHandle) { error!("1-error message"); warn!("1-warning"); info!("1-info message"); @@ -83,7 +83,7 @@ fn test_push_new_spec(logger: &mut LoggerHandle) { logger.pop_temp_spec(); // should be a no-op } -fn validate_logs(logger: &mut LoggerHandle) { +fn validate_logs(logger: &LoggerHandle) { logger.validate_logs(&[ ("ERROR", "test_reconfigure_methods", "1-error"), ("WARN", "test_reconfigure_methods", "1-warning"),