use core::cell::RefCell;
use std::io::{self, Write};
use std::sync::{Mutex, MutexGuard};
use crate::error::{Error, Result};
use crate::format::Format;
use crate::record::Record;
#[cfg_attr(all(feature = "json"), doc = "```")]
#[cfg_attr(not(all(feature = "json")), doc = "```ignore")]
pub trait Sink: Send + Sync {
fn write_record(&self, record: &Record<'_>) -> Result<()>;
fn flush(&self) -> Result<()>;
fn request_flush(&self) -> Result<()> {
self.flush()
}
fn shutdown(&self) -> Result<()> {
self.flush()
}
}
impl<S: Sink + ?Sized> Sink for std::sync::Arc<S> {
fn write_record(&self, record: &Record<'_>) -> Result<()> {
(**self).write_record(record)
}
fn flush(&self) -> Result<()> {
(**self).flush()
}
fn request_flush(&self) -> Result<()> {
(**self).request_flush()
}
fn shutdown(&self) -> Result<()> {
(**self).shutdown()
}
}
impl<S: Sink + ?Sized> Sink for Box<S> {
fn write_record(&self, record: &Record<'_>) -> Result<()> {
(**self).write_record(record)
}
fn flush(&self) -> Result<()> {
(**self).flush()
}
fn request_flush(&self) -> Result<()> {
(**self).request_flush()
}
fn shutdown(&self) -> Result<()> {
(**self).shutdown()
}
}
mod file;
mod non_blocking;
mod rotating;
mod stdio;
mod writer;
pub use self::file::{FileSink, FileSinkBuilder};
pub(crate) use self::non_blocking::on_background_worker;
pub use self::non_blocking::{NonBlocking, NonBlockingBuilder, Overflow};
pub use self::rotating::{RotatingFile, RotatingFileBuilder, Rotation};
pub use self::stdio::{StderrSink, StdoutSink};
pub use self::writer::{NullSink, WriterSink};
#[cfg_attr(all(feature = "json"), doc = "```no_run")]
#[cfg_attr(not(all(feature = "json")), doc = "```ignore")]
pub type RotatingFileSink<F> = WriterSink<RotatingFile, F>;
#[repr(align(128))]
pub(crate) struct CachePadded<T>(pub(crate) T);
impl<T> core::ops::Deref for CachePadded<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
pub(crate) const MAX_RETAINED_CAPACITY: usize = 64 * 1024;
thread_local! {
static FORMAT_BUF: RefCell<String> = const { RefCell::new(String::new()) };
}
#[inline]
pub(crate) fn trim_buffer(buf: &mut String) {
if buf.capacity() > MAX_RETAINED_CAPACITY {
buf.clear();
buf.shrink_to(MAX_RETAINED_CAPACITY);
}
}
pub(crate) fn with_formatted<F, W>(format: &F, record: &Record<'_>, mut write: W) -> Result<()>
where
F: Format,
W: FnMut(&str) -> io::Result<()>,
{
let pooled = FORMAT_BUF.try_with(|cell| {
let mut buf = cell.try_borrow_mut().ok()?;
Some(format_into(&mut buf, format, record, &mut write))
});
match pooled {
Ok(Some(result)) => result,
_ => format_into(&mut String::with_capacity(256), format, record, &mut write),
}
}
fn format_into<F, W>(buf: &mut String, format: &F, record: &Record<'_>, write: &mut W) -> Result<()>
where
F: Format,
W: FnMut(&str) -> io::Result<()>,
{
buf.clear();
let result = format
.write_record_generic(record, buf)
.map_err(Error::Format)
.and_then(|()| write(buf).map_err(Error::Io));
trim_buffer(buf);
result
}
pub(crate) fn lock_recover<T: ?Sized>(m: &Mutex<T>) -> MutexGuard<'_, T> {
m.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
}
pub(crate) fn write_locked<W: Write + ?Sized, F: Format>(
target: &Mutex<W>,
format: &F,
record: &Record<'_>,
) -> Result<()> {
with_formatted(format, record, |text| {
lock_recover(target).write_all(text.as_bytes())
})
}
pub(crate) fn flush_locked<W: Write + ?Sized>(target: &Mutex<W>) -> Result<()> {
lock_recover(target).flush().map_err(Error::from)
}
#[cfg(all(test, feature = "json"))]
mod tests {
use super::*;
use crate::format::JsonFormat;
use crate::{Level, Metadata};
#[test]
fn test_trim_buffer_caps_retained_capacity() {
let mut buf = String::with_capacity(4 * MAX_RETAINED_CAPACITY);
buf.push('x');
trim_buffer(&mut buf);
assert!(buf.capacity() <= MAX_RETAINED_CAPACITY);
let mut small = String::with_capacity(128);
small.push('y');
trim_buffer(&mut small);
assert_eq!(small, "y", "small buffers are left alone");
}
#[test]
fn test_thread_local_buffer_shrinks_after_oversize_record() {
std::thread::spawn(|| {
let big = "x".repeat(1024 * 1024);
let record = Record::new(Metadata::new(Level::Info, "t"), &big, &[]);
let mut written = 0;
with_formatted(&JsonFormat::new(), &record, |s| {
written = s.len();
Ok(())
})
.unwrap();
assert!(written > big.len());
let cap = FORMAT_BUF.with(|b| b.borrow().capacity());
assert!(cap <= MAX_RETAINED_CAPACITY, "retained {cap} bytes");
})
.join()
.unwrap();
}
#[test]
fn test_with_formatted_reentrant_call_uses_temporary_buffer() {
let record = Record::new(Metadata::new(Level::Info, "t"), "outer", &[]);
let mut inner_text = String::new();
with_formatted(&JsonFormat::new(), &record, |outer| {
let inner = Record::new(Metadata::new(Level::Info, "t"), "inner", &[]);
with_formatted(&JsonFormat::new(), &inner, |s| {
inner_text = s.to_owned();
Ok(())
})
.map_err(|e| io::Error::other(e.to_string()))?;
assert!(outer.contains("outer"));
Ok(())
})
.unwrap();
assert!(inner_text.contains("inner"));
}
#[test]
fn test_with_formatted_surfaces_write_and_format_errors() {
let record = Record::new(Metadata::new(Level::Info, "t"), "m", &[]);
let err = with_formatted(&JsonFormat::new(), &record, |_| Err(io::Error::other("x")))
.unwrap_err();
assert!(matches!(err, Error::Io(_)));
struct Broken;
impl core::fmt::Display for Broken {
fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Err(core::fmt::Error)
}
}
let fields = [crate::Field::new("b", crate::Value::Display(&Broken))];
let record = Record::new(Metadata::new(Level::Info, "t"), "m", &fields);
let err = with_formatted(&JsonFormat::new(), &record, |_| Ok(())).unwrap_err();
assert!(matches!(err, Error::Format(_)));
}
}