captains_log/
ring.rs

1use crate::{
2    config::{LogFormat, SinkConfigBuild, SinkConfigTrait},
3    log_impl::{LogSink, LogSinkTrait},
4    time::Timer,
5};
6use log::*;
7use ring_file::*;
8
9use std::cell::UnsafeCell;
10use std::hash::{Hash, Hasher};
11use std::io::Write;
12use std::mem::transmute;
13use std::path::{Path, PathBuf};
14use std::sync::atomic::{AtomicBool, Ordering};
15
16/// The LogRingFile sink is a tool for debugging deadlock or race condition,
17/// when the problem cannot be reproduce with ordinary log (because disk I/O will slow down the
18/// execution and prevent the bug to occur).
19///
20/// # Usage
21///
22/// Enable feature `ringfile` in your Cargo.toml.
23///
24/// Replace the log setup with the following in your test case:
25/// ``` rust
26/// use captains_log::*;
27/// recipe::ring_file("/tmp/ring.log", 512*1024*1024, Level::Info,
28///     signal_consts::SIGHUP).test().build().expect("log setup");
29/// ```
30///
31/// Then add some high-level log to critical path in the code, try to reproduce the problem, and
32/// reduce the amount of log if the bug not occur.
33///
34/// On start-up, it will create a limited-size ring-buffer-like memory. The log content will be hold within memory but
35/// not written to disk, old logs will be overwritten by new ones. Until specified signal arrives, the last
36/// part of log message will be dumped to file, in time order.
37///
38/// Once your program hangs up completely , find your process pid and send a signal to it.
39///
40/// ``` shell
41/// kill -SIGHUP <pid>
42/// ```
43/// There will be messages print to stdout:
44///
45/// ``` text
46/// RingFile: start dumping
47/// RingFile: dump complete
48/// ```
49///
50/// Then you can inspect your log content on disk (for this example `/tmp/ring.log`).
51///
52/// The backend is provided by [RingFile crate](https://docs.rs/ring-file). To ensure low
53/// latency, the buffer is protected by a spinlock instead of a mutex. After the program hangs, because
54/// no more message will be written to the buffer, log content can be safely copied from the buffer area to disk.
55///
56/// A real-life debugging story can be found on <https://github.com/frostyplanet/crossfire-rs/issues/24>.
57#[derive(Hash)]
58pub struct LogRingFile {
59    pub file_path: Box<Path>,
60    pub level: Level,
61    pub format: LogFormat,
62    /// 0 < buf_size < i32::MAX
63    pub buf_size: i32,
64}
65
66impl LogRingFile {
67    pub fn new<P: Into<PathBuf>>(
68        file_path: P, buf_size: i32, max_level: Level, format: LogFormat,
69    ) -> Self {
70        assert!(buf_size > 0);
71        Self { buf_size, file_path: file_path.into().into_boxed_path(), level: max_level, format }
72    }
73}
74
75impl SinkConfigBuild for LogRingFile {
76    fn build(&self) -> LogSink {
77        LogSink::RingFile(LogSinkRingFile::new(self))
78    }
79}
80
81impl SinkConfigTrait for LogRingFile {
82    fn get_level(&self) -> Level {
83        self.level
84    }
85
86    fn get_file_path(&self) -> Option<Box<Path>> {
87        Some(self.file_path.clone())
88    }
89
90    fn write_hash(&self, hasher: &mut Box<dyn Hasher>) {
91        self.hash(hasher);
92        hasher.write(b"LogRingFile");
93    }
94}
95
96pub(crate) struct LogSinkRingFile {
97    max_level: Level,
98    inner: UnsafeCell<RingFile>,
99    formatter: LogFormat,
100    /// In order to be fast, use a spin lock instead of Mutex
101    locked: AtomicBool,
102}
103
104unsafe impl Send for LogSinkRingFile {}
105unsafe impl Sync for LogSinkRingFile {}
106
107impl LogSinkRingFile {
108    fn new(config: &LogRingFile) -> Self {
109        Self {
110            max_level: config.level,
111            inner: UnsafeCell::new(RingFile::new(config.buf_size, config.file_path.to_path_buf())),
112            formatter: config.format.clone(),
113            locked: AtomicBool::new(false),
114        }
115    }
116
117    #[inline(always)]
118    fn get_inner(&self) -> &RingFile {
119        unsafe { transmute(self.inner.get()) }
120    }
121
122    #[inline(always)]
123    fn get_inner_mut(&self) -> &mut RingFile {
124        unsafe { transmute(self.inner.get()) }
125    }
126}
127
128impl LogSinkTrait for LogSinkRingFile {
129    fn open(&self) -> std::io::Result<()> {
130        Ok(())
131    }
132
133    fn reopen(&self) -> std::io::Result<()> {
134        println!("RingFile: start dumping");
135        if let Err(e) = self.get_inner().dump() {
136            println!("RingFile: dump error {:?}", e);
137            Err(e)
138        } else {
139            println!("RingFile: dump complete");
140            Ok(())
141        }
142    }
143
144    #[inline(always)]
145    fn log(&self, now: &Timer, r: &Record) {
146        if r.level() <= self.max_level {
147            let buf = self.formatter.process(now, r);
148            while self
149                .locked
150                .compare_exchange_weak(false, true, Ordering::SeqCst, Ordering::Relaxed)
151                .is_err()
152            {
153                std::hint::spin_loop();
154            }
155            let _ = self.get_inner_mut().write_all(buf.as_bytes());
156            self.locked.store(false, Ordering::Release);
157        }
158    }
159
160    #[inline(always)]
161    fn flush(&self) {}
162}