Skip to content

Commit 204c2ef

Browse files
tisonkunSpriteOvO
andauthored
Flush sinks on program exit with a special method flush_on_exit (#102)
Co-authored-by: Asuna <[email protected]>
1 parent f2d4816 commit 204c2ef

File tree

5 files changed

+74
-25
lines changed

5 files changed

+74
-25
lines changed

spdlog/src/lib.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -743,18 +743,15 @@ pub fn log_crate_proxy() -> &'static LogCrateProxy {
743743
&PROXY
744744
}
745745

746-
static IS_TEARING_DOWN: AtomicBool = AtomicBool::new(false);
747-
748746
fn flush_default_logger_at_exit() {
749747
// Rust never calls `drop` for static variables.
750748
//
751749
// Setting up an exit handler gives us a chance to flush the default logger
752750
// once at the program exit, thus we don't lose the last logs.
753751

754752
extern "C" fn handler() {
755-
IS_TEARING_DOWN.store(true, Ordering::SeqCst);
756753
if let Some(default_logger) = DEFAULT_LOGGER.get() {
757-
default_logger.load().flush()
754+
default_logger.load().flush_sinks_on_exit()
758755
}
759756
}
760757

spdlog/src/logger.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,14 +497,22 @@ impl Logger {
497497
}
498498
}
499499

500-
fn flush_sinks(&self) {
500+
fn flush_sinks_with(&self, with: impl Fn(&dyn Sink) -> Result<()>) {
501501
self.sinks.iter().for_each(|sink| {
502-
if let Err(err) = sink.flush() {
502+
if let Err(err) = with(&**sink) {
503503
self.handle_error(err);
504504
}
505505
});
506506
}
507507

508+
pub(crate) fn flush_sinks_on_exit(&self) {
509+
self.flush_sinks_with(|sink| sink.flush_on_exit());
510+
}
511+
512+
pub(crate) fn flush_sinks(&self) {
513+
self.flush_sinks_with(|sink| sink.flush());
514+
}
515+
508516
fn handle_error(&self, err: Error) {
509517
self.error_handler.read_expect().call_internal(
510518
format!(

spdlog/src/sink/async_sink/async_pool_sink.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -129,21 +129,21 @@ impl Sink for AsyncPoolSink {
129129
}
130130

131131
fn flush(&self) -> Result<()> {
132-
if crate::IS_TEARING_DOWN.load(Ordering::SeqCst) {
133-
// https://github.com/SpriteOvO/spdlog-rs/issues/64
134-
//
135-
// If the program is tearing down, this will be the final flush. `crossbeam`
136-
// uses thread-local internally, which is not supported in `atexit` callback.
137-
// This can be bypassed by flushing sinks directly on the current thread, but
138-
// before we do that we have to destroy the thread pool to ensure that any
139-
// pending log tasks are completed.
140-
self.thread_pool.destroy();
141-
self.backend.flush()
142-
} else {
143-
self.assign_task(Task::Flush {
144-
backend: self.clone_backend(),
145-
})
146-
}
132+
self.assign_task(Task::Flush {
133+
backend: self.clone_backend(),
134+
})
135+
}
136+
137+
fn flush_on_exit(&self) -> Result<()> {
138+
// https://github.com/SpriteOvO/spdlog-rs/issues/64
139+
//
140+
// If the program is tearing down, this will be the final flush. `crossbeam`
141+
// uses thread-local internally, which is not supported in `atexit` callback.
142+
// This can be bypassed by flushing sinks directly on the current thread, but
143+
// before we do that we have to destroy the thread pool to ensure that any
144+
// pending log tasks are completed.
145+
self.thread_pool.destroy();
146+
self.backend.flush_on_exit()
147147
}
148148
}
149149

@@ -258,14 +258,22 @@ impl Backend {
258258
result
259259
}
260260

261-
fn flush(&self) -> Result<()> {
261+
fn flush_with(&self, with: impl Fn(&dyn Sink) -> Result<()>) -> Result<()> {
262262
let mut result = Ok(());
263263
for sink in &self.sinks {
264-
result = Error::push_result(result, sink.flush());
264+
result = Error::push_result(result, with(&**sink));
265265
}
266266
result
267267
}
268268

269+
fn flush(&self) -> Result<()> {
270+
self.flush_with(|sink| sink.flush())
271+
}
272+
273+
fn flush_on_exit(&self) -> Result<()> {
274+
self.flush_with(|sink| sink.flush_on_exit())
275+
}
276+
269277
fn handle_error(&self, err: Error) {
270278
self.prop.call_error_handler_internal("AsyncPoolSink", err)
271279
}

spdlog/src/sink/dedup_sink.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,20 @@ impl DedupSink {
152152
})
153153
}
154154

155-
fn flush_sinks(&self) -> Result<()> {
155+
fn flush_with(&self, with: fn(&dyn Sink) -> Result<()>) -> Result<()> {
156156
#[allow(clippy::manual_try_fold)] // https://github.com/rust-lang/rust-clippy/issues/11554
157157
self.sinks.iter().fold(Ok(()), |result, sink| {
158-
Error::push_result(result, sink.flush())
158+
Error::push_result(result, with(sink.as_ref()))
159159
})
160160
}
161+
162+
fn flush_sinks(&self) -> Result<()> {
163+
self.flush_with(|sink| sink.flush())
164+
}
165+
166+
fn flush_sinks_on_exit(&self) -> Result<()> {
167+
self.flush_with(|sink| sink.flush_on_exit())
168+
}
161169
}
162170

163171
impl GetSinkProp for DedupSink {
@@ -186,6 +194,10 @@ impl Sink for DedupSink {
186194
fn flush(&self) -> Result<()> {
187195
self.flush_sinks()
188196
}
197+
198+
fn flush_on_exit(&self) -> Result<()> {
199+
self.flush_sinks_on_exit()
200+
}
189201
}
190202

191203
impl Drop for DedupSink {

spdlog/src/sink/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,30 @@ pub trait Sink: SinkPropAccess + Sync + Send {
238238

239239
/// Flushes any buffered records.
240240
fn flush(&self) -> Result<()>;
241+
242+
/// Flushes any buffered records at program exit.
243+
///
244+
/// _spdlog-rs_ will perform a flush for sinks in the default logger when
245+
/// the program exits, and the flush will be called to this method
246+
/// `flush_on_exit` instead of `flush`. This is because the execution
247+
/// context may be in the [`atexit`] callback or in the panic handler when
248+
/// exiting. In such a context, some operations are restricted, e.g.
249+
/// Thread-local Storage (TLS) may not be available in `atexit` callbacks.
250+
///
251+
/// This method calls directly to `flush` method by default. When users'
252+
/// `flush` method implementation is not usable in a program exit context,
253+
/// users should override the implementation of this method to provide an
254+
/// alternative flushing implementation. See the implementation of
255+
/// [`AsyncPoolSink::flush_on_exit`] as an example.
256+
///
257+
/// For combined sinks, this method should always be overridden to propagate
258+
/// the information that "the program is exiting" to their sub-sinks. See
259+
/// the implementation of [`DedupSink::flush_on_exit`] as an example.
260+
///
261+
/// [`atexit`]: https://en.cppreference.com/w/c/program/atexit
262+
fn flush_on_exit(&self) -> Result<()> {
263+
self.flush()
264+
}
241265
}
242266

243267
/// Container type for [`Sink`]s.

0 commit comments

Comments
 (0)