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}