fix(lib): keep the log level in an atomic and let hosts change it
The level lived in a `static mut` read from every logging thread and written by `init`, an unsynchronised access the compiler only tolerates inside `unsafe`. It is an atomic now, read without `unsafe`, and a host can raise or lower it while running through `set_log_level`.
This commit is contained in:
@@ -3,7 +3,7 @@ use std::fs::{File, OpenOptions};
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Mutex, Once,
|
Mutex, Once,
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, AtomicU8, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@@ -16,7 +16,23 @@ pub enum LogLevel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
static mut LOG_LEVEL: LogLevel = LogLevel::Warn;
|
static LOG_LEVEL: AtomicU8 = AtomicU8::new(LogLevel::Warn as u8);
|
||||||
|
|
||||||
|
/// Change the level at runtime, from any thread.
|
||||||
|
pub fn set_log_level(level: LogLevel) {
|
||||||
|
LOG_LEVEL.store(level as u8, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn log_level() -> LogLevel {
|
||||||
|
match LOG_LEVEL.load(Ordering::Relaxed) {
|
||||||
|
0 => LogLevel::Error,
|
||||||
|
2 => LogLevel::Info,
|
||||||
|
3 => LogLevel::Debug,
|
||||||
|
4 => LogLevel::Trace,
|
||||||
|
_ => LogLevel::Warn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static BUFFER: Mutex<Vec<String>> = Mutex::new(Vec::new());
|
static BUFFER: Mutex<Vec<String>> = Mutex::new(Vec::new());
|
||||||
static BUFFERING: AtomicBool = AtomicBool::new(false);
|
static BUFFERING: AtomicBool = AtomicBool::new(false);
|
||||||
@@ -29,15 +45,13 @@ pub fn init() {
|
|||||||
.unwrap_or_else(|_| "WARN".to_string())
|
.unwrap_or_else(|_| "WARN".to_string())
|
||||||
.to_uppercase();
|
.to_uppercase();
|
||||||
|
|
||||||
unsafe {
|
set_log_level(match level.as_str() {
|
||||||
LOG_LEVEL = match level.as_str() {
|
"ERROR" => LogLevel::Error,
|
||||||
"ERROR" => LogLevel::Error,
|
"INFO" => LogLevel::Info,
|
||||||
"INFO" => LogLevel::Info,
|
"DEBUG" | "VERBOSE" => LogLevel::Debug,
|
||||||
"DEBUG" | "VERBOSE" => LogLevel::Debug, // Map VERBOSE to DEBUG
|
"TRACE" => LogLevel::Trace,
|
||||||
"TRACE" => LogLevel::Trace,
|
_ => LogLevel::Warn,
|
||||||
_ => LogLevel::Warn, // "WARN" or default
|
});
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize log file if LOG_FILE env var is set
|
// Initialize log file if LOG_FILE env var is set
|
||||||
if let Ok(log_file_path) = env::var("LOG_FILE") {
|
if let Ok(log_file_path) = env::var("LOG_FILE") {
|
||||||
@@ -85,8 +99,8 @@ fn has_log_file() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn log(level: LogLevel, message: &str) {
|
pub fn log(level: LogLevel, message: &str) {
|
||||||
unsafe {
|
if level <= log_level() {
|
||||||
if level <= LOG_LEVEL {
|
{
|
||||||
let (name, colour) = match level {
|
let (name, colour) = match level {
|
||||||
LogLevel::Error => ("ERROR", "\x1b[31m"), // Red
|
LogLevel::Error => ("ERROR", "\x1b[31m"), // Red
|
||||||
LogLevel::Warn => ("WARN", "\x1b[33m"), // Yellow
|
LogLevel::Warn => ("WARN", "\x1b[33m"), // Yellow
|
||||||
@@ -239,7 +253,16 @@ macro_rules! trace {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{LogSink, choose_sink};
|
use super::{LogLevel, LogSink, choose_sink, log_level, set_log_level};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_level_can_be_changed_while_running() {
|
||||||
|
let before = log_level();
|
||||||
|
set_log_level(LogLevel::Trace);
|
||||||
|
assert_eq!(log_level(), LogLevel::Trace);
|
||||||
|
set_log_level(before);
|
||||||
|
assert_eq!(log_level(), before);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_configured_file_bypasses_buffering_so_lines_are_not_duplicated() {
|
fn a_configured_file_bypasses_buffering_so_lines_are_not_duplicated() {
|
||||||
|
|||||||
Reference in New Issue
Block a user