Files
core/test/runtime_logging.rs
T

171 lines
5.6 KiB
Rust

use super::*;
use std::sync::mpsc;
use std::time::Duration;
fn record(level: Level, message: impl Into<String>) -> Record {
Record {
level,
message: message.into(),
fields: Vec::new(),
dropped: 0,
}
}
fn next_record(logger: &Logger) -> Record {
match logger.receive() {
Some(Command::Record(record)) => record,
_ => panic!("expected a queued record"),
}
}
#[test]
fn bounded_queue_keeps_fifo_and_carries_dropped_count_to_next_record() {
let logger = Logger::new();
for index in 0..QUEUE_CAPACITY {
logger.submit(record(Level::Info, index.to_string()));
}
logger.submit(record(Level::Debug, "discard debug"));
logger.submit(record(Level::Info, "discard info"));
assert_eq!(logger.queue().commands.len(), QUEUE_CAPACITY);
assert_eq!(logger.dropped.load(Ordering::Relaxed), 2);
assert_eq!(next_record(&logger).message, "0");
logger.submit(record(Level::Info, "after overflow"));
for index in 1..QUEUE_CAPACITY {
assert_eq!(next_record(&logger).message, index.to_string());
}
let recovered = next_record(&logger);
assert_eq!(recovered.message, "after overflow");
assert_eq!(recovered.dropped, 2);
assert_eq!(logger.dropped.load(Ordering::Relaxed), 0);
let mut output = Vec::new();
write_record(&mut output, &recovered).unwrap();
assert_eq!(
String::from_utf8(output).unwrap(),
"{\"level\":\"info\",\"message\":\"after overflow\",\"dropped_events\":2,\"fields\":{}}\n"
);
}
#[test]
fn warn_and_error_wait_for_capacity_without_being_dropped() {
for level in [Level::Warn, Level::Error] {
let logger = Arc::new(Logger::new());
for index in 0..QUEUE_CAPACITY {
logger.submit(record(Level::Info, index.to_string()));
}
let writer = Arc::clone(&logger);
let (started, starting) = mpsc::channel();
let (finished, finishing) = mpsc::channel();
let producer = std::thread::spawn(move || {
started.send(()).unwrap();
writer.submit(record(level, "must survive"));
finished.send(()).unwrap();
});
starting.recv_timeout(Duration::from_secs(1)).unwrap();
assert!(matches!(
finishing.recv_timeout(Duration::from_millis(20)),
Err(mpsc::RecvTimeoutError::Timeout)
));
assert_eq!(next_record(&logger).message, "0");
finishing.recv_timeout(Duration::from_secs(1)).unwrap();
producer.join().unwrap();
for index in 1..QUEUE_CAPACITY {
assert_eq!(next_record(&logger).message, index.to_string());
}
let important = next_record(&logger);
assert_eq!(important.message, "must survive");
assert_eq!(important.level, level);
assert_eq!(important.dropped, 0);
}
}
#[derive(Clone, Default)]
struct CapturedOutput {
bytes: Arc<Mutex<Vec<u8>>>,
flushes: Arc<AtomicU64>,
}
impl Write for CapturedOutput {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.bytes.lock().unwrap().extend_from_slice(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
self.flushes.fetch_add(1, Ordering::Relaxed);
Ok(())
}
}
#[test]
fn concurrent_flushes_acknowledge_each_callers_prior_record() {
let logger = Arc::new(Logger::new());
let output = CapturedOutput::default();
let writer = Arc::clone(&logger);
let mut captured = output.clone();
let writing = std::thread::spawn(move || writer.write(&mut captured));
std::thread::scope(|scope| {
for caller in 0..8 {
let logger = &logger;
let output = &output;
scope.spawn(move || {
for sequence in 0..8 {
let message = format!("caller-{caller}-record-{sequence}");
logger.submit(record(Level::Warn, message.clone()));
logger.flush();
let bytes = output.bytes.lock().unwrap();
let text = std::str::from_utf8(&bytes).unwrap();
assert!(text.contains(&format!("\"message\":\"{message}\"")));
}
});
}
});
assert_eq!(output.flushes.load(Ordering::Relaxed), 64);
logger.close();
writing.join().unwrap();
}
struct PanickingOutput;
impl Write for PanickingOutput {
fn write(&mut self, _: &[u8]) -> io::Result<usize> {
panic!("fixture writer stopped")
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn writer_failure_releases_queue_and_wakes_producers_and_flushers() {
let logger = Arc::new(Logger::new());
for index in 0..QUEUE_CAPACITY {
logger.submit(record(Level::Info, index.to_string()));
}
let (finished, finishing) = mpsc::channel();
let mut waiting = Vec::new();
for index in 0..3 {
let logger = Arc::clone(&logger);
let finished = finished.clone();
waiting.push(std::thread::spawn(move || {
if index == 0 {
logger.submit(record(Level::Warn, "writer closed"));
} else {
logger.flush();
}
finished.send(()).unwrap();
}));
}
let writer = Arc::clone(&logger);
let writing = std::thread::spawn(move || writer.write(&mut PanickingOutput));
assert!(writing.join().is_err());
for _ in 0..3 {
finishing.recv_timeout(Duration::from_secs(1)).unwrap();
}
for thread in waiting {
thread.join().unwrap();
}
let queue = logger.queue();
assert!(queue.closed);
assert!(queue.commands.is_empty());
}