@@ -236,3 +236,132 @@ impl opentelemetry::logs::Logger for Logger {
236
236
enabled
237
237
}
238
238
}
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