Skip to content

Commit 9f0ac7d

Browse files
authored
Add test to validate log shutdown (#1620)
1 parent b7b7d7e commit 9f0ac7d

File tree

1 file changed

+129
-0
lines changed

1 file changed

+129
-0
lines changed

opentelemetry-sdk/src/logs/log_emitter.rs

+129
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)