Skip to main content

spdlog/
error.rs

1//! Provides error types.
2//!
3//! # Default error handler
4//!
5//! If a logger or sink does not have an error handler set up, a default error
6//! handler will be used, which will print the error to `stderr`.
7
8use std::{
9    error::Error as StdError,
10    fmt::{self, Display},
11    io::{self, Write as _},
12    result,
13    sync::Arc,
14};
15
16pub use crate::env_level::EnvLevelError;
17#[cfg(feature = "multi-thread")]
18use crate::{sink::Task, RecordOwned};
19
20/// Contains most errors of this crate.
21#[derive(Debug)]
22#[non_exhaustive]
23pub enum Error {
24    /// Returned by [`Formatter`]s when an error occurs in formatting a record.
25    ///
26    /// [`Formatter`]: crate::formatter::Formatter
27    FormatRecord(fmt::Error),
28
29    /// Returned by [`Sink`]s when an error occurs in writing a record to the
30    /// target.
31    ///
32    /// [`Sink`]: crate::sink::Sink
33    WriteRecord(io::Error),
34
35    /// Returned by [`Sink`]s when an error occurs in flushing the buffer.
36    ///
37    /// [`Sink`]: crate::sink::Sink
38    FlushBuffer(io::Error),
39
40    /// Returned by [`Sink`]s when an error occurs in creating a directory.
41    ///
42    /// [`Sink`]: crate::sink::Sink
43    CreateDirectory(io::Error),
44
45    /// Returned by [`Sink`]s when an error occurs in opening a file.
46    ///
47    /// [`Sink`]: crate::sink::Sink
48    OpenFile(io::Error),
49
50    /// Returned by [`Sink`]s when an error occurs in querying the metadata of a
51    /// file.
52    ///
53    /// [`Sink`]: crate::sink::Sink
54    QueryFileMetadata(io::Error),
55
56    /// Returned by [`Sink`]s when an error occurs in renaming a file.
57    ///
58    /// [`Sink`]: crate::sink::Sink
59    RenameFile(io::Error),
60
61    /// Returned by [`Sink`]s when an error occurs in removing a file.
62    ///
63    /// [`Sink`]: crate::sink::Sink
64    RemoveFile(io::Error),
65
66    /// Returned by [`from_str`] when the string doesn't match any of the log
67    /// levels.
68    ///
69    /// [`from_str`]: std::str::FromStr::from_str
70    ParseLevel(String),
71
72    /// Returned if an invalid argument was passed in.
73    InvalidArgument(InvalidArgumentError),
74
75    /// Returned by [`Sink`]s when an error occurs in sending to the channel.
76    ///
77    /// [`Sink`]: crate::sink::Sink
78    #[cfg(feature = "multi-thread")]
79    SendToChannel(SendToChannelError, SendToChannelErrorDropped),
80
81    /// Returned by [`runtime_pattern!`] when the pattern is failed to be built
82    /// at runtime.
83    ///
84    /// [`runtime_pattern!`]: crate::formatter::runtime_pattern
85    #[cfg(feature = "runtime-pattern")]
86    BuildPattern(BuildPatternError),
87
88    /// Returned by [`Formatter`]s when an error occurs in serializing a log.
89    ///
90    /// [`Formatter`]: crate::formatter::Formatter
91    #[cfg(feature = "serde")]
92    SerializeRecord(io::Error),
93
94    /// Returned from a downstream implementation of `spdlog-rs`. Its actual
95    /// error type may be a downstream struct.
96    ///
97    /// When downstream crates encounter errors, other more specific error
98    /// variants should be used first, this variant should only be used as a
99    /// last option when other variant types are incompatible.
100    Downstream(Box<dyn StdError + Send + Sync>),
101
102    /// Returned when multiple errors occurred.
103    Multiple(Vec<Error>),
104
105    #[cfg(test)]
106    #[doc(hidden)]
107    __ForInternalTestsUseOnly(i32),
108}
109
110impl StdError for Error {}
111
112impl Display for Error {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            Self::FormatRecord(err) => write!(f, "format record error: {err}"),
116            Self::WriteRecord(err) => write!(f, "write record error: {err}"),
117            Self::FlushBuffer(err) => write!(f, "flush buffer error: {err}"),
118            Self::CreateDirectory(err) => write!(f, "create directory error: {err}"),
119            Self::OpenFile(err) => write!(f, "open file error: {err}"),
120            Self::QueryFileMetadata(err) => write!(f, "query file metadata error: {err}"),
121            Self::RenameFile(err) => write!(f, "rename file error: {err}"),
122            Self::RemoveFile(err) => write!(f, "remove file error: {err}"),
123            Self::ParseLevel(level_str) => {
124                write!(f, "attempted to convert a string that doesn't match an existing log level: {level_str}")
125            }
126            Self::InvalidArgument(err) => write!(f, "invalid argument {err}"),
127            #[cfg(feature = "multi-thread")]
128            Self::SendToChannel(err, _) => write!(f, "failed to send message to channel: {err}"),
129            #[cfg(feature = "runtime-pattern")]
130            Self::BuildPattern(err) => write!(f, "failed to build pattern at runtime: {err}"),
131            #[cfg(feature = "serde")]
132            Self::SerializeRecord(err) => write!(f, "failed to serialize log: {err}"),
133            Self::Downstream(err) => write!(f, "{err}"),
134            Self::Multiple(errs) => write!(f, "{errs:?}"),
135            #[cfg(test)]
136            Self::__ForInternalTestsUseOnly(i) => write!(f, "{i}"),
137        }
138    }
139}
140
141impl From<InvalidArgumentError> for Error {
142    fn from(err: InvalidArgumentError) -> Self {
143        Self::InvalidArgument(err)
144    }
145}
146
147/// Indicates that an invalid parameter was specified.
148#[derive(Debug)]
149#[non_exhaustive]
150pub enum InvalidArgumentError {
151    /// Invalid logger name.
152    ///
153    /// See the documentation of [`LoggerBuilder::name`] for the name
154    /// requirements.
155    ///
156    /// [`LoggerBuilder::name`]: crate::LoggerBuilder::name
157    LoggerName(SetLoggerNameError),
158
159    /// Invalid [`RotationPolicy`].
160    ///
161    /// See the documentation of [`RotationPolicy`] for the input requirements.
162    ///
163    /// [`RotationPolicy`]: crate::sink::RotationPolicy
164    RotationPolicy(String),
165
166    /// Invalid thread pool capacity.
167    #[deprecated(
168        since = "0.5.0",
169        note = "non-zero thread pool capacity is now guarded by NonZeroUsize type"
170    )]
171    ThreadPoolCapacity(String),
172}
173
174impl StdError for InvalidArgumentError {}
175
176impl Display for InvalidArgumentError {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        match self {
179            Self::LoggerName(err) => write!(f, "'logger name': {err}"),
180            Self::RotationPolicy(value) => write!(f, "'rotation policy': {value}"),
181            #[allow(deprecated)]
182            Self::ThreadPoolCapacity(value) => write!(f, "'thread pool capacity': {value}"),
183        }
184    }
185}
186
187impl From<SetLoggerNameError> for InvalidArgumentError {
188    fn from(err: SetLoggerNameError) -> Self {
189        Self::LoggerName(err)
190    }
191}
192
193/// Indicates that an invalid logger name was set.
194///
195/// See the documentation of [`LoggerBuilder::name`] for the name requirements.
196///
197/// [`LoggerBuilder::name`]: crate::LoggerBuilder::name
198#[derive(Debug)]
199pub struct SetLoggerNameError {
200    name: String,
201}
202
203impl SetLoggerNameError {
204    #[must_use]
205    pub(crate) fn new(name: impl Into<String>) -> Self {
206        Self { name: name.into() }
207    }
208
209    #[cfg(test)]
210    #[must_use]
211    pub(crate) fn name(&self) -> &str {
212        &self.name
213    }
214}
215
216impl StdError for SetLoggerNameError {}
217
218impl Display for SetLoggerNameError {
219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220        write!(f, "name '{}' contains disallowed characters", self.name)
221    }
222}
223
224/// Indicates that an error occurred while sending to channel.
225#[cfg(feature = "multi-thread")]
226#[derive(Debug)]
227#[non_exhaustive]
228pub enum SendToChannelError {
229    /// The channel is full.
230    ///
231    /// The variant returned only when [`OverflowPolicy::DropIncoming`] is used.
232    ///
233    /// [`OverflowPolicy::DropIncoming`]: crate::sink::async_sink::OverflowPolicy::DropIncoming
234    Full,
235
236    /// The channel is disconnected.
237    Disconnected,
238}
239
240#[cfg(feature = "multi-thread")]
241impl StdError for SendToChannelError {}
242
243#[cfg(feature = "multi-thread")]
244impl Display for SendToChannelError {
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        match self {
247            Self::Full => write!(f, "the channel is full"),
248            Self::Disconnected => write!(f, "the channel is disconnected"),
249        }
250    }
251}
252
253/// Contains data that is dropped after sending to the channel failed.
254///
255/// You can handle them manually or just ignore them.
256#[cfg(feature = "multi-thread")]
257#[derive(Debug)]
258#[non_exhaustive]
259pub enum SendToChannelErrorDropped {
260    /// A `log` operation and a record are dropped.
261    Record(Box<RecordOwned>), // Boxed because `RecordOwned` is a bit large.
262    /// A `flush` operation is dropped.
263    Flush,
264}
265
266impl Error {
267    pub(crate) fn push_err<T>(result: Result<T>, new: Self) -> Result<T> {
268        match result {
269            Ok(_) => Err(new),
270            Err(Self::Multiple(mut errors)) => {
271                errors.push(new);
272                Err(Self::Multiple(errors))
273            }
274            Err(prev) => Err(Error::Multiple(vec![prev, new])),
275        }
276    }
277
278    pub(crate) fn push_result<T, N>(result: Result<T>, new: Result<N>) -> Result<T> {
279        match new {
280            Ok(_) => result,
281            Err(err) => Self::push_err(result, err),
282        }
283    }
284}
285
286#[cfg(feature = "multi-thread")]
287impl Error {
288    #[must_use]
289    pub(crate) fn from_crossbeam_send(err: crossbeam::channel::SendError<Task>) -> Self {
290        Self::SendToChannel(
291            SendToChannelError::Disconnected,
292            SendToChannelErrorDropped::from_task(err.0),
293        )
294    }
295
296    #[must_use]
297    pub(crate) fn from_crossbeam_try_send(err: crossbeam::channel::TrySendError<Task>) -> Self {
298        use crossbeam::channel::TrySendError;
299
300        let (error, dropped_task) = match err {
301            TrySendError::Full(dropped) => (SendToChannelError::Full, dropped),
302            TrySendError::Disconnected(dropped) => (SendToChannelError::Disconnected, dropped),
303        };
304
305        Self::SendToChannel(error, SendToChannelErrorDropped::from_task(dropped_task))
306    }
307}
308
309#[cfg(feature = "multi-thread")]
310impl SendToChannelErrorDropped {
311    #[must_use]
312    pub(crate) fn from_task(task: Task) -> Self {
313        match task {
314            Task::Log { record, .. } => Self::Record(Box::new(record)),
315            Task::Flush { .. } => Self::Flush,
316            #[cfg(test)]
317            Task::__ForTestUse { .. } => unreachable!(),
318        }
319    }
320}
321
322/// Indicates that an error occurred while building a pattern at compile-time.
323#[cfg(feature = "runtime-pattern")]
324#[derive(Debug)]
325pub struct BuildPatternError(pub(crate) spdlog_internal::pattern_parser::Error);
326
327#[cfg(feature = "runtime-pattern")]
328impl StdError for BuildPatternError {}
329
330#[cfg(feature = "runtime-pattern")]
331impl Display for BuildPatternError {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        write!(f, "{}", self.0)
334    }
335}
336
337/// The result type of this crate.
338pub type Result<T> = result::Result<T, Error>;
339
340/// Represents an error handler.
341///
342/// In most cases, it can be constructed by just a `.into()`.
343///
344/// Call [`ErrorHandler::default`] to construct an empty error handler, when an
345/// error is triggered, a built-in fallback handler will be used which prints
346/// the error to `stderr`.
347#[derive(Clone)]
348pub struct ErrorHandler(Option<Arc<dyn Fn(Error) + Send + Sync>>);
349
350impl ErrorHandler {
351    /// Constructs an error handler with a custom function.
352    #[must_use]
353    pub fn new<F>(custom: F) -> Self
354    where
355        F: Fn(Error) + Send + Sync + 'static,
356    {
357        Self(Some(Arc::new(custom)))
358    }
359
360    /// Calls the error handler with an error.
361    pub fn call(&self, err: Error) {
362        self.call_internal("External", err);
363    }
364
365    pub(crate) fn call_internal(&self, from: impl AsRef<str>, err: Error) {
366        if let Some(handler) = &self.0 {
367            handler(err);
368        } else {
369            Self::default_impl(from, err);
370        }
371    }
372
373    fn default_impl(from: impl AsRef<str>, error: Error) {
374        if let Error::Multiple(errs) = error {
375            errs.into_iter()
376                .for_each(|err| Self::default_impl(from.as_ref(), err));
377            return;
378        }
379
380        let date = chrono::Local::now()
381            .format("%Y-%m-%d %H:%M:%S.%3f")
382            .to_string();
383
384        // https://github.com/SpriteOvO/spdlog-rs/discussions/87
385        //
386        // Don't use `eprintln!` here, as it may fail to write and then panic.
387        let _ = writeln!(
388            io::stderr(),
389            "[*** SPDLOG-RS UNHANDLED ERROR ***] [{}] [{}] {}",
390            date,
391            from.as_ref(),
392            error
393        );
394    }
395}
396
397impl<F> From<F> for ErrorHandler
398where
399    F: Fn(Error) + Send + Sync + 'static,
400{
401    fn from(handler: F) -> Self {
402        Self::new(handler)
403    }
404}
405
406impl Default for ErrorHandler {
407    /// Constructs an error handler with the built-in handler which prints
408    /// errors to `stderr`.
409    fn default() -> Self {
410        Self(None)
411    }
412}
413
414impl fmt::Debug for ErrorHandler {
415    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416        f.debug_tuple("ErrorHandler")
417            .field(&self.0.as_ref().map_or("default", |_| "custom"))
418            .finish()
419    }
420}
421
422#[cfg(test)]
423mod tests {
424    use super::*;
425    use crate::test_utils::*;
426
427    #[test]
428    fn error_traits() {
429        assert_trait!(Error: Send + Sync);
430    }
431
432    #[test]
433    fn push_err() {
434        macro_rules! make_err {
435            ( $($inputs:tt)+ ) => {
436                Error::__ForInternalTestsUseOnly($($inputs)*)
437            };
438        }
439
440        assert!(matches!(
441            Error::push_err(Ok(()), make_err!(1)),
442            Err(make_err!(1))
443        ));
444
445        assert!(matches!(
446            Error::push_err::<()>(Err(make_err!(1)), make_err!(2)),
447            Err(Error::Multiple(v)) if matches!(v[..], [make_err!(1), make_err!(2)])
448        ));
449    }
450}