use std::{
error::Error as StdError,
fmt::{self, Display},
io::{self, Write as _},
result,
sync::Arc,
};
pub use crate::env_level::EnvLevelError;
#[cfg(feature = "multi-thread")]
use crate::{sink::Task, RecordOwned};
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
FormatRecord(fmt::Error),
WriteRecord(io::Error),
FlushBuffer(io::Error),
CreateDirectory(io::Error),
OpenFile(io::Error),
QueryFileMetadata(io::Error),
RenameFile(io::Error),
RemoveFile(io::Error),
ParseLevel(String),
InvalidArgument(InvalidArgumentError),
#[cfg(feature = "multi-thread")]
SendToChannel(SendToChannelError, SendToChannelErrorDropped),
#[cfg(feature = "runtime-pattern")]
BuildPattern(BuildPatternError),
#[cfg(feature = "serde")]
SerializeRecord(io::Error),
Downstream(Box<dyn StdError + Send + Sync>),
Multiple(Vec<Error>),
#[cfg(test)]
#[doc(hidden)]
__ForInternalTestsUseOnly(i32),
}
impl StdError for Error {}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FormatRecord(err) => write!(f, "format record error: {err}"),
Self::WriteRecord(err) => write!(f, "write record error: {err}"),
Self::FlushBuffer(err) => write!(f, "flush buffer error: {err}"),
Self::CreateDirectory(err) => write!(f, "create directory error: {err}"),
Self::OpenFile(err) => write!(f, "open file error: {err}"),
Self::QueryFileMetadata(err) => write!(f, "query file metadata error: {err}"),
Self::RenameFile(err) => write!(f, "rename file error: {err}"),
Self::RemoveFile(err) => write!(f, "remove file error: {err}"),
Self::ParseLevel(level_str) => {
write!(f, "attempted to convert a string that doesn't match an existing log level: {level_str}")
}
Self::InvalidArgument(err) => write!(f, "invalid argument {err}"),
#[cfg(feature = "multi-thread")]
Self::SendToChannel(err, _) => write!(f, "failed to send message to channel: {err}"),
#[cfg(feature = "runtime-pattern")]
Self::BuildPattern(err) => write!(f, "failed to build pattern at runtime: {err}"),
#[cfg(feature = "serde")]
Self::SerializeRecord(err) => write!(f, "failed to serialize log: {err}"),
Self::Downstream(err) => write!(f, "{err}"),
Self::Multiple(errs) => write!(f, "{errs:?}"),
#[cfg(test)]
Self::__ForInternalTestsUseOnly(i) => write!(f, "{i}"),
}
}
}
impl From<InvalidArgumentError> for Error {
fn from(err: InvalidArgumentError) -> Self {
Self::InvalidArgument(err)
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum InvalidArgumentError {
LoggerName(SetLoggerNameError),
RotationPolicy(String),
#[deprecated(
since = "0.5.0",
note = "non-zero thread pool capacity is now guarded by NonZeroUsize type"
)]
ThreadPoolCapacity(String),
}
impl StdError for InvalidArgumentError {}
impl Display for InvalidArgumentError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LoggerName(err) => write!(f, "'logger name': {err}"),
Self::RotationPolicy(value) => write!(f, "'rotation policy': {value}"),
#[allow(deprecated)]
Self::ThreadPoolCapacity(value) => write!(f, "'thread pool capacity': {value}"),
}
}
}
impl From<SetLoggerNameError> for InvalidArgumentError {
fn from(err: SetLoggerNameError) -> Self {
Self::LoggerName(err)
}
}
#[derive(Debug)]
pub struct SetLoggerNameError {
name: String,
}
impl SetLoggerNameError {
#[must_use]
pub(crate) fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
#[cfg(test)]
#[must_use]
pub(crate) fn name(&self) -> &str {
&self.name
}
}
impl StdError for SetLoggerNameError {}
impl Display for SetLoggerNameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "name '{}' contains disallowed characters", self.name)
}
}
#[cfg(feature = "multi-thread")]
#[derive(Debug)]
#[non_exhaustive]
pub enum SendToChannelError {
Full,
Disconnected,
}
#[cfg(feature = "multi-thread")]
impl StdError for SendToChannelError {}
#[cfg(feature = "multi-thread")]
impl Display for SendToChannelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Full => write!(f, "the channel is full"),
Self::Disconnected => write!(f, "the channel is disconnected"),
}
}
}
#[cfg(feature = "multi-thread")]
#[derive(Debug)]
#[non_exhaustive]
pub enum SendToChannelErrorDropped {
Record(Box<RecordOwned>), Flush,
}
impl Error {
pub(crate) fn push_err<T>(result: Result<T>, new: Self) -> Result<T> {
match result {
Ok(_) => Err(new),
Err(Self::Multiple(mut errors)) => {
errors.push(new);
Err(Self::Multiple(errors))
}
Err(prev) => Err(Error::Multiple(vec![prev, new])),
}
}
pub(crate) fn push_result<T, N>(result: Result<T>, new: Result<N>) -> Result<T> {
match new {
Ok(_) => result,
Err(err) => Self::push_err(result, err),
}
}
}
#[cfg(feature = "multi-thread")]
impl Error {
#[must_use]
pub(crate) fn from_crossbeam_send(err: crossbeam::channel::SendError<Task>) -> Self {
Self::SendToChannel(
SendToChannelError::Disconnected,
SendToChannelErrorDropped::from_task(err.0),
)
}
#[must_use]
pub(crate) fn from_crossbeam_try_send(err: crossbeam::channel::TrySendError<Task>) -> Self {
use crossbeam::channel::TrySendError;
let (error, dropped_task) = match err {
TrySendError::Full(dropped) => (SendToChannelError::Full, dropped),
TrySendError::Disconnected(dropped) => (SendToChannelError::Disconnected, dropped),
};
Self::SendToChannel(error, SendToChannelErrorDropped::from_task(dropped_task))
}
}
#[cfg(feature = "multi-thread")]
impl SendToChannelErrorDropped {
#[must_use]
pub(crate) fn from_task(task: Task) -> Self {
match task {
Task::Log { record, .. } => Self::Record(Box::new(record)),
Task::Flush { .. } => Self::Flush,
#[cfg(test)]
Task::__ForTestUse { .. } => unreachable!(),
}
}
}
#[cfg(feature = "runtime-pattern")]
#[derive(Debug)]
pub struct BuildPatternError(pub(crate) spdlog_internal::pattern_parser::Error);
#[cfg(feature = "runtime-pattern")]
impl StdError for BuildPatternError {}
#[cfg(feature = "runtime-pattern")]
impl Display for BuildPatternError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
pub type Result<T> = result::Result<T, Error>;
#[derive(Clone)]
pub struct ErrorHandler(Option<Arc<dyn Fn(Error) + Send + Sync>>);
impl ErrorHandler {
#[must_use]
pub fn new<F>(custom: F) -> Self
where
F: Fn(Error) + Send + Sync + 'static,
{
Self(Some(Arc::new(custom)))
}
pub fn call(&self, err: Error) {
self.call_internal("External", err);
}
pub(crate) fn call_internal(&self, from: impl AsRef<str>, err: Error) {
if let Some(handler) = &self.0 {
handler(err);
} else {
Self::default_impl(from, err);
}
}
fn default_impl(from: impl AsRef<str>, error: Error) {
if let Error::Multiple(errs) = error {
errs.into_iter()
.for_each(|err| Self::default_impl(from.as_ref(), err));
return;
}
let date = chrono::Local::now()
.format("%Y-%m-%d %H:%M:%S.%3f")
.to_string();
let _ = writeln!(
io::stderr(),
"[*** SPDLOG-RS UNHANDLED ERROR ***] [{}] [{}] {}",
date,
from.as_ref(),
error
);
}
}
impl<F> From<F> for ErrorHandler
where
F: Fn(Error) + Send + Sync + 'static,
{
fn from(handler: F) -> Self {
Self::new(handler)
}
}
impl Default for ErrorHandler {
fn default() -> Self {
Self(None)
}
}
impl fmt::Debug for ErrorHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("ErrorHandler")
.field(&self.0.as_ref().map_or("default", |_| "custom"))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
#[test]
fn error_traits() {
assert_trait!(Error: Send + Sync);
}
#[test]
fn push_err() {
macro_rules! make_err {
( $($inputs:tt)+ ) => {
Error::__ForInternalTestsUseOnly($($inputs)*)
};
}
assert!(matches!(
Error::push_err(Ok(()), make_err!(1)),
Err(make_err!(1))
));
assert!(matches!(
Error::push_err::<()>(Err(make_err!(1)), make_err!(2)),
Err(Error::Multiple(v)) if matches!(v[..], [make_err!(1), make_err!(2)])
));
}
}