1use std::fmt;
6use std::sync::{Arc, PoisonError, RwLock};
7
8pub type LogSink = Arc<dyn Fn(&str) + Send + Sync>;
10
11static SINK: RwLock<Option<LogSink>> = RwLock::new(None);
12
13pub fn set_log_sink(sink: Option<LogSink>) {
15 *SINK.write().unwrap_or_else(PoisonError::into_inner) = sink;
16}
17
18#[allow(clippy::print_stderr)]
20pub(crate) fn warn(message: fmt::Arguments<'_>) {
21 let sink = SINK.read().unwrap_or_else(PoisonError::into_inner).clone();
22 match sink {
23 Some(sink) => sink(&message.to_string()),
24 None => eprintln!("msime: {message}"),
25 }
26}
27
28#[cfg(test)]
29mod tests {
30 use std::sync::Mutex;
31
32 use super::*;
33
34 #[test]
36 fn an_installed_sink_receives_lines_until_it_is_removed() {
37 let lines = Arc::new(Mutex::new(Vec::<String>::new()));
38 let captured = Arc::clone(&lines);
39 set_log_sink(Some(Arc::new(move |line: &str| {
40 captured.lock().unwrap().push(line.to_owned());
41 })));
42 warn(format_args!("synthetic log line {}", 1));
43 set_log_sink(None);
44 warn(format_args!("synthetic log line {}", 2));
45 let lines = lines.lock().unwrap();
46 assert!(lines.iter().any(|line| line == "synthetic log line 1"));
47 assert!(!lines.iter().any(|line| line == "synthetic log line 2"));
48 }
49}