stakker/
log.rs

1use std::convert::TryFrom;
2use std::error::Error;
3use std::fmt::{Arguments, Display};
4use std::ops::{BitOr, BitOrAssign};
5use std::str::FromStr;
6
7// TODO: Add "call-trace" feature to trace calls across actors,
8// allowing a full backtrace to be generated later if necessary.  Uses
9// a span for each call.  When call is queued: Open the span, with
10// 'parent' set from Core::get_exec_id() if non-zero, and extra
11// details such as 'after' to show how this was queued.  When
12// execution starts: Log an event, and call Core::set_exec_id.  When
13// execution stops: Close span and call Core::set_exec_id(0).  Most of
14// this code needs to go in the macros, so add this feature when
15// macros are rewritten as proc-macros.  `ret!` and `fwd!` are
16// problematic as they don't have access to `cx`, so maybe also store
17// the exec_id in the Deferrer when "call-trace" feature is enabled.
18
19/// Trait used when visiting all the log-record's key-value pairs
20///
21/// This is designed as a superset of both JSON and the OpenTelemetry
22/// `KeyValueList`.  Anything that cannot be represented with these
23/// types must be logged as a string, a map or an array.
24///
25/// Notes:
26///
27/// - The `key` argument is normally `Some(key)`.  However where this
28///   value is a member of an array the key must be `None`.
29///
30/// - All signed integer types from `i8` up to `i64` are passed to
31///   `kv_i64`, and all unsigned integer types from `u8` up to `u64` are
32///   passed to `kv_u64`.
33///
34/// - `kv_null` may be used to represent a key with presence but no
35///   value, i.e. a JSON `null` or Rust unit `()` value.  It might be
36///   converted into an empty-string or `true` value depending on the
37///   logger if presence values are not supported downstream.
38///
39/// - Anything that needs to be formatted as a string, e.g. `Display`
40///   or `Debug` types, is passed through to `kv_fmt` using
41///   `format_args!`.  This way the logger can format it directly into
42///   the output, avoiding the allocations that would occur if you used
43///   `format!`.
44///
45/// - Maps are introduced with a call to `kv_map`, then zero or more
46///   `kv_*` calls for the contents of that map, then a call to
47///   `kv_mapend` to close the map and continue at the original level.
48///
49/// - Arrays are introduced with a call to `kv_arr`, then zero or more
50///   `kv_*` calls for the contents of the array, then a call to
51///   `kv_arrend` to close the array and continue at the original level.
52///   The `key` argument on the `kv_*` calls within an array should be
53///   `None`.
54///
55/// For maps and arrays, unlimited levels of nesting may be
56/// represented.  The same key must be passed to both `kv_map` and
57/// `kv_mapend` (or `kv_arr` and `kv_arrend`), for the convenience of
58/// different kinds of logging output methods.
59///
60/// A logger might not support all these data types due to limitations
61/// of the downstream format, so some data might get converted into
62/// another form if it cannot be passed on as is.
63///
64/// ## No early termination
65///
66/// The visitor interface doesn't support terminating early with an
67/// error.  This is intentional.  Other approaches were considered,
68/// coded up and discarded: both `std::fmt`'s approach which
69/// effectively returns `Result<(), ()>` and insists that the visitor
70/// store the full error, and the approach used by `slog` which tries
71/// to wrap various different types within its error type.  Given that
72/// the non-error case will be most common, there is little advantage
73/// in letting the visit terminate early.  So, as for `std::fmt`, if
74/// the visitor encounters an error, it needs to store it itself.  It
75/// will then generally choose to set things up so that any further
76/// calls are ignored.  If that makes the code awkward, it can usually
77/// be made manageable with a macro.
78pub trait LogVisitor {
79    fn kv_u64(&mut self, key: Option<&str>, val: u64);
80    fn kv_i64(&mut self, key: Option<&str>, val: i64);
81    fn kv_f64(&mut self, key: Option<&str>, val: f64);
82    fn kv_bool(&mut self, key: Option<&str>, val: bool);
83    fn kv_null(&mut self, key: Option<&str>);
84    fn kv_str(&mut self, key: Option<&str>, val: &str);
85    fn kv_fmt(&mut self, key: Option<&str>, val: &Arguments<'_>);
86    fn kv_map(&mut self, key: Option<&str>);
87    fn kv_mapend(&mut self, key: Option<&str>);
88    fn kv_arr(&mut self, key: Option<&str>);
89    fn kv_arrend(&mut self, key: Option<&str>);
90}
91
92/// Log record that is passed to a logger
93pub struct LogRecord<'a> {
94    /// Logging span identifier, or 0 for outside of a span
95    pub id: LogID,
96    /// Logging level
97    pub level: LogLevel,
98    /// Logging target, or ""
99    pub target: &'a str,
100    /// Freeform formatted text.  This can be output with any macro
101    /// that accepts a format-string, e.g. `println!("{}", fmt)`.
102    pub fmt: Arguments<'a>,
103    /// Key-value pairs.  Call this function with your own
104    /// [`LogVisitor`] and all the key-value pairs will be passed to
105    /// that visitor in sequence.
106    ///
107    /// [`LogVisitor`]: trait.LogVisitor.html
108    pub kvscan: &'a dyn Fn(&mut dyn LogVisitor),
109}
110
111/// Logging span identifier
112///
113/// A span is a period of time.  It is marked by a [`LogLevel::Open`]
114/// logging event, any number of normal logging events and then a
115/// [`LogLevel::Close`] event, all associated with the same [`LogID`].
116/// It corresponds to the lifetime of an actor, or a call, or whatever
117/// other process is being described.
118///
119/// The [`LogID`] numbers are allocated sequentially from one, with
120/// zero reserved for "none" or "missing".  If 2^64 values are
121/// allocated, they wrap around to one again.  If this would cause
122/// problems downstream, then the logger should detect the situation
123/// and warn or terminate the process.
124///
125/// [`LogID`]: type.LogID.html
126/// [`LogLevel::Close`]: enum.LogLevel.html#variant.Close
127/// [`LogLevel::Open`]: enum.LogLevel.html#variant.Open
128pub type LogID = u64;
129
130/// Levels for logging
131#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
132#[repr(u32)]
133#[non_exhaustive]
134pub enum LogLevel {
135    /// Tracing (very low priority or verbose logging)
136    Trace = 0,
137
138    /// Debugging (low priority logging)
139    Debug = 1,
140
141    /// Informational logging
142    Info = 2,
143
144    /// Warnings
145    Warn = 3,
146
147    /// Errors
148    Error = 4,
149
150    /// Logging level used to disable logging of severity-based
151    /// logging levels.  Anything logged at this level will be ignored.
152    Off = 8,
153
154    /// Audit-level log-records are sets of key-value pairs that are
155    /// intended for machine processing.  The formatted log-message
156    /// should be a simple record tag, with all the variable data in
157    /// key-value pairs.  This corresponds to trace events in
158    /// OpenTelemetry, or what are called 'metrics' in some other
159    /// systems.
160    Audit = 5,
161
162    /// Span open.  For an actor, this means actor startup.  The
163    /// formatted text contains the name of the object, e.g. the actor
164    /// name. If the parent logging-ID is known, it is passed as a
165    /// `parent` key-value pair.
166    Open = 6,
167
168    /// Span close.  For an actor, this means actor termination.  The
169    /// formatted text may give a description of why the span closed
170    /// if there was a problem.  In case of actor failure, one of
171    /// these presence key-values will be added: `failed`, `dropped`
172    /// or `killed`.
173    Close = 7,
174}
175
176impl LogLevel {
177    /// Return the name of the [`LogLevel`] as a static string.
178    ///
179    /// [`LogLevel`]: enum.LogLevel.html
180    pub fn name(self) -> &'static str {
181        match self {
182            Self::Trace => "TRACE",
183            Self::Debug => "DEBUG",
184            Self::Info => "INFO",
185            Self::Warn => "WARN",
186            Self::Error => "ERROR",
187            Self::Off => "OFF",
188            Self::Audit => "AUDIT",
189            Self::Open => "OPEN",
190            Self::Close => "CLOSE",
191        }
192    }
193
194    /// Return a slice containing all defined logging levels
195    pub fn all_levels() -> &'static [LogLevel] {
196        &[
197            Self::Trace,
198            Self::Debug,
199            Self::Info,
200            Self::Warn,
201            Self::Error,
202            Self::Off,
203            Self::Audit,
204            Self::Open,
205            Self::Close,
206        ]
207    }
208}
209
210impl Display for LogLevel {
211    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212        self.name().fmt(f)
213    }
214}
215
216impl TryFrom<u8> for LogLevel {
217    type Error = LogLevelError;
218
219    /// Convert back from `u8` to a LogLevel, if value is valid
220    fn try_from(value: u8) -> Result<Self, LogLevelError> {
221        const TRACE: u8 = LogLevel::Trace as u8;
222        const DEBUG: u8 = LogLevel::Debug as u8;
223        const INFO: u8 = LogLevel::Info as u8;
224        const WARN: u8 = LogLevel::Warn as u8;
225        const ERROR: u8 = LogLevel::Error as u8;
226        const OFF: u8 = LogLevel::Off as u8;
227        const AUDIT: u8 = LogLevel::Audit as u8;
228        const OPEN: u8 = LogLevel::Open as u8;
229        const CLOSE: u8 = LogLevel::Close as u8;
230
231        Ok(match value {
232            TRACE => Self::Trace,
233            DEBUG => Self::Debug,
234            INFO => Self::Info,
235            WARN => Self::Warn,
236            ERROR => Self::Error,
237            OFF => Self::Off,
238            AUDIT => Self::Audit,
239            OPEN => Self::Open,
240            CLOSE => Self::Close,
241            _ => return Err(LogLevelError),
242        })
243    }
244}
245
246impl FromStr for LogLevel {
247    type Err = LogLevelError;
248
249    /// This does a case-insensitive match to the level names as
250    /// returned by [`LogLevel::name`]
251    ///
252    /// [`LogLevel::name`]: enum.LogLevel.html#method.name
253    fn from_str(s: &str) -> Result<LogLevel, LogLevelError> {
254        let s = s.trim();
255        macro_rules! ret_if_matches {
256            ($name:literal, $val:expr) => {
257                if s.eq_ignore_ascii_case($name) {
258                    return Ok($val);
259                }
260            };
261        }
262        if let Some(c) = s.as_bytes().first() {
263            match c {
264                b'T' | b't' => ret_if_matches!("TRACE", Self::Trace),
265                b'D' | b'd' => ret_if_matches!("DEBUG", Self::Debug),
266                b'I' | b'i' => ret_if_matches!("INFO", Self::Info),
267                b'W' | b'w' => ret_if_matches!("WARN", Self::Warn),
268                b'E' | b'e' => ret_if_matches!("ERROR", Self::Error),
269                b'O' | b'o' => {
270                    ret_if_matches!("OFF", Self::Off);
271                    ret_if_matches!("OPEN", Self::Open);
272                }
273                b'A' | b'a' => ret_if_matches!("AUDIT", Self::Audit),
274                b'C' | b'c' => ret_if_matches!("CLOSE", Self::Close),
275                _ => (),
276            }
277        }
278        Err(LogLevelError)
279    }
280}
281
282/// Invalid [`LogLevel`] passed to [`LogLevel::from_str`]
283///
284/// [`LogLevel::from_str`]: enum.LogLevel.html#method.from_str
285/// [`LogLevel`]: enum.LogLevel.html
286#[derive(Debug, Eq, PartialEq)]
287pub struct LogLevelError;
288impl Error for LogLevelError {}
289impl Display for LogLevelError {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        "invalid logging level".fmt(f)
292    }
293}
294
295/// Filter for logging levels
296///
297/// This is a "copy" value which represents a set of enabled logging
298/// levels.  Filters can be combined using the `|` and `|=` bit-or
299/// operators.  A filter can be generated from a [`LogLevel`] using
300/// [`LogFilter::from`].  Note that converting from a [`LogLevel`]
301/// will also enable any related levels -- see [`LogFilter::from`]
302/// documentation.
303///
304/// [`LogFilter::from`]: struct.LogFilter.html#method.from
305/// [`LogLevel`]: enum.LogLevel.html
306#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
307pub struct LogFilter(u32);
308
309impl LogFilter {
310    /// Return a [`LogFilter`] with no levels enabled
311    ///
312    /// [`LogFilter`]: struct.LogFilter.html
313    #[inline]
314    pub fn new() -> Self {
315        Self::default()
316    }
317
318    /// Return a [`LogFilter`] with all the listed levels enabled, as
319    /// converted by [`LogFilter::from`].
320    ///
321    /// [`LogFilter::from`]: struct.LogFilter.html#method.from
322    /// [`LogFilter`]: struct.LogFilter.html
323    #[inline]
324    pub fn all(levels: &[LogLevel]) -> Self {
325        let mut rv = Self::new();
326        for level in levels {
327            rv |= Self::from(*level);
328        }
329        rv
330    }
331
332    /// Test whether the given [`LogLevel`] is enabled
333    ///
334    /// [`LogLevel`]: enum.LogLevel.html
335    #[inline]
336    pub fn allows(&self, level: LogLevel) -> bool {
337        0 != (self.0 & (1 << (level as u32)))
338    }
339
340    /// Test whether the set of enabled levels is empty
341    #[inline]
342    pub fn is_empty(&self) -> bool {
343        self.0 == 0
344    }
345}
346
347impl From<LogLevel> for LogFilter {
348    /// Convert a [`LogLevel`] into a [`LogFilter`].  Where a severity
349    /// level ([`LogLevel::Trace`] to [`LogLevel::Error`]) is passed,
350    /// all higher severity levels are also enabled.  Where
351    /// [`LogLevel::Open`] or [`LogLevel::Close`] is passed, the other
352    /// is also enabled.  [`LogLevel::Audit`] only enables itself.
353    /// [`LogLevel::Off`] gives no levels enabled.
354    ///
355    /// [`LogFilter`]: struct.LogFilter.html
356    /// [`LogLevel::Audit`]: enum.LogLevel.html#variant.Audit
357    /// [`LogLevel::Close`]: enum.LogLevel.html#variant.Close
358    /// [`LogLevel::Error`]: enum.LogLevel.html#variant.Error
359    /// [`LogLevel::Off`]: enum.LogLevel.html#variant.Off
360    /// [`LogLevel::Open`]: enum.LogLevel.html#variant.Open
361    /// [`LogLevel::Trace`]: enum.LogLevel.html#variant.Trace
362    /// [`LogLevel`]: enum.LogLevel.html
363    #[inline]
364    fn from(level: LogLevel) -> Self {
365        match level {
366            LogLevel::Trace
367            | LogLevel::Debug
368            | LogLevel::Info
369            | LogLevel::Warn
370            | LogLevel::Error
371            | LogLevel::Off => Self(0x1F & (0x1F << level as u32)),
372            LogLevel::Audit => Self(1 << LogLevel::Audit as u32),
373            LogLevel::Open | LogLevel::Close => {
374                Self((1 << LogLevel::Open as u32) | (1 << LogLevel::Close as u32))
375            }
376        }
377    }
378}
379
380impl BitOr for LogFilter {
381    type Output = Self;
382
383    #[inline]
384    fn bitor(self, rhs: Self) -> Self {
385        Self(self.0 | rhs.0)
386    }
387}
388
389impl BitOrAssign for LogFilter {
390    fn bitor_assign(&mut self, rhs: Self) {
391        self.0 |= rhs.0;
392    }
393}
394
395impl Display for LogFilter {
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397        "LogFilter(".fmt(f)?;
398        let mut first = true;
399        for level in LogLevel::all_levels() {
400            if 0 != (self.0 & (1 << *level as u32)) {
401                if first {
402                    first = false;
403                } else {
404                    ",".fmt(f)?;
405                }
406                level.name().fmt(f)?;
407            }
408        }
409        ")".fmt(f)
410    }
411}
412
413impl FromStr for LogFilter {
414    type Err = LogLevelError;
415
416    fn from_str(s: &str) -> Result<LogFilter, LogLevelError> {
417        let mut rv = LogFilter::new();
418        for level in s.split(',') {
419            let level = LogLevel::from_str(level)?;
420            rv |= LogFilter::from(level);
421        }
422        Ok(rv)
423    }
424}