Skip to content

Commit 33b3ae1

Browse files
authored
fix: adhere to the max log level (#5)
1 parent c8d769d commit 33b3ae1

File tree

2 files changed

+140
-29
lines changed

2 files changed

+140
-29
lines changed

src/lib.rs

Lines changed: 105 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,16 @@
100100
101101
use std::{
102102
cell::RefCell,
103-
sync::{Mutex, OnceLock},
103+
sync::{LazyLock, Mutex, OnceLock},
104104
time::SystemTime,
105105
};
106106

107107
use log::{Level, LevelFilter, Log};
108108

109109
static LOGGER: OnceLock<Logger> = OnceLock::new();
110-
thread_local! {static THREAD_RECORDS: RefCell<Vec<CapturedLog>> = RefCell::new(Vec::new())}
111-
static PROCESS_RECORDS: Mutex<Vec<CapturedLog>> = Mutex::new(Vec::new());
110+
static PROCESS_LOGGER_DATA: LazyLock<Mutex<LoggerData>> =
111+
LazyLock::new(|| Mutex::new(LoggerData::new()));
112+
thread_local! {static THREAD_LOGGER_DATA: RefCell<LoggerData> = RefCell::new(LoggerData::new())}
112113

113114
/// A log captured by calls to the logging macros ([info!](log::info), [warn!](log::warn), etc.).
114115
#[derive(Debug)]
@@ -139,16 +140,37 @@ pub enum LogOutput {
139140
Stdout,
140141
}
141142

143+
/// Properties of the logger. These need to be stored separately from the
144+
/// logger because they can exist per thread or for the process, while the
145+
/// logger itself must be static based on the design of the [`log`] facade.
146+
struct LoggerData {
147+
records: Vec<CapturedLog>,
148+
max_level: LevelFilter,
149+
output: Option<LogOutput>,
150+
}
151+
152+
impl LoggerData {
153+
pub fn new() -> Self {
154+
Self {
155+
records: Vec::new(),
156+
max_level: LevelFilter::Trace,
157+
output: Some(LogOutput::Stderr),
158+
}
159+
}
160+
}
161+
142162
#[derive(Debug)]
143163
struct Logger {
144164
scope: CaptureScope,
145-
max_level: LevelFilter,
146-
output: Option<LogOutput>,
147165
}
148166

149167
impl Log for Logger {
150168
fn enabled(&self, metadata: &log::Metadata) -> bool {
151-
metadata.level() <= self.max_level
169+
match self.scope {
170+
CaptureScope::Process => metadata.level() <= log::max_level(),
171+
CaptureScope::Thread => THREAD_LOGGER_DATA
172+
.with(|logger_data| metadata.level() <= logger_data.borrow().max_level),
173+
}
152174
}
153175

154176
fn log(&self, record: &log::Record) {
@@ -163,16 +185,27 @@ impl Log for Logger {
163185
};
164186

165187
match self.scope {
166-
CaptureScope::Process => PROCESS_RECORDS
188+
CaptureScope::Process => PROCESS_LOGGER_DATA
167189
.lock()
168-
.expect("failed to lock log records")
190+
.expect("failed to lock process logger data")
191+
.records
169192
.push(captured_log),
170-
CaptureScope::Thread => THREAD_RECORDS.with(|records| {
171-
records.borrow_mut().push(captured_log);
193+
CaptureScope::Thread => THREAD_LOGGER_DATA.with(|logger_data| {
194+
logger_data.borrow_mut().records.push(captured_log);
172195
}),
173196
}
174197

175-
if let Some(output) = self.output {
198+
if let Some(output) = match self.scope {
199+
CaptureScope::Process => {
200+
PROCESS_LOGGER_DATA
201+
.lock()
202+
.expect("failed to lock process logger data")
203+
.output
204+
}
205+
CaptureScope::Thread => {
206+
THREAD_LOGGER_DATA.with(|logger_data| logger_data.borrow().output)
207+
}
208+
} {
176209
match output {
177210
LogOutput::Stderr => {
178211
eprintln!(
@@ -234,25 +267,38 @@ impl Builder {
234267
}
235268

236269
pub fn setup(&self) {
237-
let logger = Logger {
238-
scope: self.scope,
239-
max_level: self.max_level,
240-
output: self.output,
241-
};
270+
let logger = Logger { scope: self.scope };
242271

243272
match LOGGER.set(logger) {
244273
Ok(_) => {
245274
log::set_logger(LOGGER.get().unwrap()).expect(
246275
"cannot set logcap because another logger has already been initialized",
247276
);
248-
log::set_max_level(self.max_level);
249277
}
250278
Err(_) => {
251279
if LOGGER.get().unwrap().scope != self.scope {
252280
panic!("logcap has already been set up with a different scope");
253281
}
254282
}
255283
}
284+
285+
// Reset the max level or set it on a per-thread basis if the logger already exists
286+
match self.scope {
287+
CaptureScope::Process => {
288+
log::set_max_level(self.max_level);
289+
let mut logger_data = PROCESS_LOGGER_DATA.lock().unwrap();
290+
logger_data.max_level = self.max_level;
291+
logger_data.output = self.output;
292+
}
293+
CaptureScope::Thread => {
294+
log::set_max_level(LevelFilter::Trace);
295+
THREAD_LOGGER_DATA.set(LoggerData {
296+
records: Vec::new(),
297+
max_level: self.max_level,
298+
output: self.output,
299+
});
300+
}
301+
};
256302
}
257303
}
258304

@@ -278,16 +324,14 @@ pub fn consume(f: impl FnOnce(Vec<CapturedLog>)) {
278324
match LOGGER.get() {
279325
Some(logger) => match logger.scope {
280326
CaptureScope::Process => {
281-
let mut records = PROCESS_RECORDS.lock().unwrap();
282327
let mut moved: Vec<CapturedLog> = Vec::new();
283-
moved.extend(records.drain(..));
328+
moved.extend(PROCESS_LOGGER_DATA.lock().unwrap().records.drain(..));
284329
f(moved);
285330
}
286331
CaptureScope::Thread => {
287-
THREAD_RECORDS.with(|records| {
288-
let mut records = records.borrow_mut();
332+
THREAD_LOGGER_DATA.with(|logger_data| {
289333
let mut moved: Vec<CapturedLog> = Vec::new();
290-
moved.extend(records.drain(..));
334+
moved.extend(logger_data.borrow_mut().records.drain(..));
291335
f(moved);
292336
});
293337
}
@@ -301,13 +345,11 @@ pub fn clear() {
301345
match LOGGER.get() {
302346
Some(logger) => match logger.scope {
303347
CaptureScope::Process => {
304-
let mut records = PROCESS_RECORDS.lock().unwrap();
305-
records.clear();
348+
PROCESS_LOGGER_DATA.lock().unwrap().records.clear();
306349
}
307350
CaptureScope::Thread => {
308-
THREAD_RECORDS.with(|records| {
309-
let mut records = records.borrow_mut();
310-
records.clear();
351+
THREAD_LOGGER_DATA.with(|logger_data| {
352+
logger_data.borrow_mut().records.clear();
311353
});
312354
}
313355
},
@@ -319,7 +361,7 @@ pub fn clear() {
319361
mod tests {
320362
use std::thread;
321363

322-
use log::{debug, info, warn};
364+
use log::{debug, error, info, warn};
323365

324366
use super::*;
325367

@@ -439,4 +481,39 @@ mod tests {
439481
assert!(logs.is_empty());
440482
})
441483
}
484+
485+
#[test]
486+
fn captures_at_specified_level() {
487+
super::builder().max_level(LevelFilter::Warn).setup();
488+
489+
warn!("foobar");
490+
491+
super::consume(|logs| {
492+
assert_eq!(1, logs.len());
493+
assert_eq!("foobar", logs[0].body);
494+
});
495+
}
496+
497+
#[test]
498+
fn captures_below_specified_level() {
499+
super::builder().max_level(LevelFilter::Warn).setup();
500+
501+
error!("foobar");
502+
503+
super::consume(|logs| {
504+
assert_eq!(1, logs.len());
505+
assert_eq!("foobar", logs[0].body);
506+
});
507+
}
508+
509+
#[test]
510+
fn does_not_capture_above_specified_level() {
511+
super::builder().max_level(LevelFilter::Info).setup();
512+
513+
debug!("foobar");
514+
515+
super::consume(|logs| {
516+
assert!(logs.is_empty());
517+
});
518+
}
442519
}

tests/process.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::{
33
thread,
44
};
55

6-
use log::{info, Level};
6+
use log::{debug, info, Level, LevelFilter};
77
use logcap::CaptureScope;
88

99
extern crate logcap;
@@ -67,3 +67,37 @@ fn captures_logs_across_threads() {
6767

6868
after_each();
6969
}
70+
71+
#[test]
72+
pub fn overwites_max_log_level_on_subsequent_calls() {
73+
let __ = before_each();
74+
75+
logcap::builder()
76+
.scope(CaptureScope::Process)
77+
.max_level(LevelFilter::Info)
78+
.setup();
79+
80+
info!("foobar");
81+
debug!("moocow");
82+
83+
logcap::consume(|logs| {
84+
assert_eq!(1, logs.len());
85+
assert_eq!("foobar", logs[0].body);
86+
});
87+
88+
logcap::builder()
89+
.scope(CaptureScope::Process)
90+
.max_level(LevelFilter::Debug)
91+
.setup();
92+
93+
info!("foobar");
94+
debug!("moocow");
95+
96+
logcap::consume(|logs| {
97+
assert_eq!(2, logs.len());
98+
assert_eq!("foobar", logs[0].body);
99+
assert_eq!("moocow", logs[1].body);
100+
});
101+
102+
after_each();
103+
}

0 commit comments

Comments
 (0)