@@ -236,3 +236,132 @@ impl opentelemetry::logs::Logger for Logger {
236236 enabled
237237 }
238238}
239+
240+ #[ cfg( test) ]
241+ mod tests {
242+ use super :: * ;
243+ use opentelemetry:: global:: { logger, set_logger_provider, shutdown_logger_provider} ;
244+ use opentelemetry:: logs:: Logger ;
245+ use std:: sync:: { Arc , Mutex } ;
246+ use std:: thread;
247+
248+ #[ test]
249+ fn shutdown_test ( ) {
250+ // cargo test shutdown_test --features=logs
251+
252+ // Arrange
253+ let shutdown_called = Arc :: new ( Mutex :: new ( false ) ) ;
254+ let flush_called = Arc :: new ( Mutex :: new ( false ) ) ;
255+ let signal_to_end = Arc :: new ( Mutex :: new ( false ) ) ;
256+ let signal_to_thread_started = Arc :: new ( Mutex :: new ( false ) ) ;
257+ let logger_provider = LoggerProvider :: builder ( )
258+ . with_log_processor ( LazyLogProcessor :: new (
259+ shutdown_called. clone ( ) ,
260+ flush_called. clone ( ) ,
261+ ) )
262+ . build ( ) ;
263+ set_logger_provider ( logger_provider) ;
264+
265+ // Act
266+ let logger1 = logger ( "test-logger1" ) ;
267+ let logger2 = logger ( "test-logger2" ) ;
268+ logger1. emit ( LogRecord :: default ( ) ) ;
269+ logger2. emit ( LogRecord :: default ( ) ) ;
270+
271+ let signal_to_end_clone = signal_to_end. clone ( ) ;
272+ let signal_to_thread_started_clone = signal_to_thread_started. clone ( ) ;
273+
274+ let handle = thread:: spawn ( move || {
275+ let logger3 = logger ( "test-logger3" ) ;
276+ loop {
277+ // signal the main thread that this thread has started.
278+ * signal_to_thread_started_clone. lock ( ) . unwrap ( ) = true ;
279+ logger3. emit ( LogRecord :: default ( ) ) ;
280+ if * signal_to_end_clone. lock ( ) . unwrap ( ) {
281+ break ;
282+ }
283+ }
284+ } ) ;
285+
286+ // wait for the spawned thread to start before calling shutdown This is
287+ // very important - if shutdown is called before the spawned thread
288+ // obtains its logger, then the logger will be no-op one, and the test
289+ // will pass, but it will not be testing the intended scenario.
290+ while !* signal_to_thread_started. lock ( ) . unwrap ( ) {
291+ thread:: sleep ( std:: time:: Duration :: from_millis ( 10 ) ) ;
292+ }
293+
294+ // Intentionally *not* calling shutdown/flush on the provider, but
295+ // instead relying on shutdown_logger_provider which causes the global
296+ // provider to be dropped, leading to the sdk logger provider's drop to
297+ // be called, which is expected to call shutdown on processors.
298+ shutdown_logger_provider ( ) ;
299+
300+ // Assert
301+
302+ // shutdown_logger_provider is necessary but not sufficient, as loggers
303+ // hold on to the the provider (via inner provider clones).
304+ assert ! ( !* shutdown_called. lock( ) . unwrap( ) ) ;
305+
306+ // flush is never called by the sdk.
307+ assert ! ( !* flush_called. lock( ) . unwrap( ) ) ;
308+
309+ // Drop one of the logger. Not enough!
310+ drop ( logger1) ;
311+ assert ! ( !* shutdown_called. lock( ) . unwrap( ) ) ;
312+
313+ // drop logger2, which is the only remaining logger in this thread.
314+ // Still not enough!
315+ drop ( logger2) ;
316+ assert ! ( !* shutdown_called. lock( ) . unwrap( ) ) ;
317+
318+ // now signal the spawned thread to end, which causes it to drop its
319+ // logger. Since that is the last logger, the provider (inner provider)
320+ // is finally dropped, triggering shutdown
321+ * signal_to_end. lock ( ) . unwrap ( ) = true ;
322+ handle. join ( ) . unwrap ( ) ;
323+ assert ! ( * shutdown_called. lock( ) . unwrap( ) ) ;
324+
325+ // flush is never called by the sdk.
326+ assert ! ( !* flush_called. lock( ) . unwrap( ) ) ;
327+ }
328+
329+ #[ derive( Debug ) ]
330+ pub ( crate ) struct LazyLogProcessor {
331+ shutdown_called : Arc < Mutex < bool > > ,
332+ flush_called : Arc < Mutex < bool > > ,
333+ }
334+
335+ impl LazyLogProcessor {
336+ pub ( crate ) fn new (
337+ shutdown_called : Arc < Mutex < bool > > ,
338+ flush_called : Arc < Mutex < bool > > ,
339+ ) -> Self {
340+ LazyLogProcessor {
341+ shutdown_called,
342+ flush_called,
343+ }
344+ }
345+ }
346+
347+ impl LogProcessor for LazyLogProcessor {
348+ fn emit ( & self , _data : LogData ) {
349+ // nothing to do.
350+ }
351+
352+ fn force_flush ( & self ) -> LogResult < ( ) > {
353+ * self . flush_called . lock ( ) . unwrap ( ) = true ;
354+ Ok ( ( ) )
355+ }
356+
357+ fn shutdown ( & mut self ) -> LogResult < ( ) > {
358+ * self . shutdown_called . lock ( ) . unwrap ( ) = true ;
359+ Ok ( ( ) )
360+ }
361+
362+ #[ cfg( feature = "logs_level_enabled" ) ]
363+ fn event_enabled ( & self , _level : Severity , _target : & str , _name : & str ) -> bool {
364+ true
365+ }
366+ }
367+ }
0 commit comments