Skip to main content

log_io/
error.rs

1//! Crate-level error type.
2
3use std::fmt;
4use std::io;
5
6/// Crate result alias.
7pub type Result<T> = core::result::Result<T, Error>;
8
9/// Errors produced by the logging pipeline.
10///
11/// Logging is best-effort by design: [`crate::Logger::log`] and the
12/// macros never return these errors. They reach you through
13/// [`crate::Logger::try_log`] / [`crate::Logger::try_emit`], through
14/// [`crate::Logger::flush`] and [`crate::Logger::shutdown`], and
15/// through the handler installed with
16/// [`crate::LoggerBuilder::on_error`].
17///
18/// The enum is `#[non_exhaustive]`; match it with a wildcard arm.
19///
20/// # Examples
21///
22/// ```
23/// use log_io::Error;
24///
25/// let err = Error::from(std::io::Error::other("disk full"));
26/// assert_eq!(err.to_string(), "log sink I/O error: disk full");
27/// assert!(std::error::Error::source(&err).is_some());
28/// ```
29#[derive(Debug)]
30#[non_exhaustive]
31pub enum Error {
32    /// The sink's destination returned an I/O error (disk full, broken
33    /// pipe, permission denied, ...). Retrying may help for transient
34    /// conditions; persistent failures usually need operator action.
35    /// The record that triggered it was not written to that sink, but
36    /// every other sink still received it.
37    Io(io::Error),
38    /// A formatter failed. With the built-in formats this only happens
39    /// when a lazily formatted value (`Value::Display`, `Value::Debug`,
40    /// `Value::Error`) or a message's format arguments return an error
41    /// from their own `fmt` implementation; it indicates a bug in that
42    /// implementation or in a custom [`crate::format::Format`].
43    Format(fmt::Error),
44    /// A sink or format rejected its configuration. Among the built-in
45    /// components only [`crate::sink::NonBlockingBuilder::build`]
46    /// returns it (for an invalid thread name); fix the configuration.
47    Configuration(&'static str),
48    /// The sink cannot serve the request. Either its background worker
49    /// exited (for example after the writer panicked), in which case
50    /// records sent to it are lost and the sink must be rebuilt; or a
51    /// flush or shutdown was requested from the sink's own worker
52    /// thread (its writer or `on_error` handler), which cannot wait for
53    /// itself.
54    Closed,
55    /// An error from a custom sink that does not fit the other
56    /// variants. Construct with [`Error::custom`].
57    Custom(Box<dyn std::error::Error + Send + Sync + 'static>),
58}
59
60impl Error {
61    /// Wrap an arbitrary error from a custom sink.
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// let err = log_io::Error::custom(std::fmt::Error);
67    /// assert!(matches!(err, log_io::Error::Custom(_)));
68    /// ```
69    pub fn custom<E>(error: E) -> Self
70    where
71        E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
72    {
73        Self::Custom(error.into())
74    }
75}
76
77impl fmt::Display for Error {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match self {
80            Self::Io(e) => write!(f, "log sink I/O error: {e}"),
81            Self::Format(_) => f.write_str("log record formatting failed"),
82            Self::Configuration(msg) => write!(f, "log sink configuration error: {msg}"),
83            Self::Closed => f.write_str("log sink is closed and no longer accepts records"),
84            Self::Custom(e) => write!(f, "log sink error: {e}"),
85        }
86    }
87}
88
89impl std::error::Error for Error {
90    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
91        match self {
92            Self::Io(e) => Some(e),
93            Self::Format(e) => Some(e),
94            Self::Custom(e) => Some(&**e),
95            Self::Configuration(_) | Self::Closed => None,
96        }
97    }
98}
99
100impl From<io::Error> for Error {
101    fn from(value: io::Error) -> Self {
102        Self::Io(value)
103    }
104}
105
106impl From<fmt::Error> for Error {
107    fn from(value: fmt::Error) -> Self {
108        Self::Format(value)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use std::error::Error as _;
116
117    #[test]
118    fn test_error_display_has_context_for_every_variant() {
119        let cases: [(Error, &str); 5] = [
120            (
121                io::Error::other("disk full").into(),
122                "log sink I/O error: disk full",
123            ),
124            (fmt::Error.into(), "log record formatting failed"),
125            (
126                Error::Configuration("bad"),
127                "log sink configuration error: bad",
128            ),
129            (
130                Error::Closed,
131                "log sink is closed and no longer accepts records",
132            ),
133            (Error::custom("boom"), "log sink error: boom"),
134        ];
135        for (err, expected) in cases {
136            assert_eq!(err.to_string(), expected);
137        }
138    }
139
140    #[test]
141    fn test_error_source_chain() {
142        assert!(Error::from(io::Error::other("x")).source().is_some());
143        assert!(Error::from(fmt::Error).source().is_some());
144        assert!(Error::custom("x").source().is_some());
145        assert!(Error::Configuration("x").source().is_none());
146        assert!(Error::Closed.source().is_none());
147    }
148}