100100
101101use std:: {
102102 cell:: RefCell ,
103- sync:: { Mutex , OnceLock } ,
103+ sync:: { LazyLock , Mutex , OnceLock } ,
104104 time:: SystemTime ,
105105} ;
106106
107107use log:: { Level , LevelFilter , Log } ;
108108
109109static 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 ) ]
143163struct Logger {
144164 scope : CaptureScope ,
145- max_level : LevelFilter ,
146- output : Option < LogOutput > ,
147165}
148166
149167impl 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() {
319361mod 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}
0 commit comments