logforth_core/
record.rs

1// Copyright 2024 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Log record and metadata.
16
17use std::cmp;
18use std::fmt;
19use std::str::FromStr;
20use std::time::SystemTime;
21
22use crate::Error;
23use crate::kv;
24use crate::kv::KeyValues;
25use crate::str::Str;
26
27#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
28enum MaybeStaticStr<'a> {
29    Str(&'a str),
30    Static(&'static str),
31}
32
33impl<'a> MaybeStaticStr<'a> {
34    fn get(&self) -> &'a str {
35        match *self {
36            MaybeStaticStr::Str(s) => s,
37            MaybeStaticStr::Static(s) => s,
38        }
39    }
40
41    fn into_str(self) -> Str<'static> {
42        match self {
43            MaybeStaticStr::Str(s) => Str::new_owned(s.to_owned()),
44            MaybeStaticStr::Static(s) => Str::new(s),
45        }
46    }
47}
48
49/// The payload of a log message.
50#[derive(Clone, Debug)]
51pub struct Record<'a> {
52    // the observed time
53    now: SystemTime,
54
55    // the metadata
56    metadata: Metadata<'a>,
57    module_path: Option<MaybeStaticStr<'a>>,
58    file: Option<MaybeStaticStr<'a>>,
59    line: Option<u32>,
60
61    // the payload
62    args: fmt::Arguments<'a>,
63
64    // structural logging
65    kvs: KeyValues<'a>,
66}
67
68impl<'a> Record<'a> {
69    /// The observed time.
70    pub fn time(&self) -> SystemTime {
71        self.now
72    }
73
74    /// Metadata about the log directive.
75    pub fn metadata(&self) -> &Metadata<'a> {
76        &self.metadata
77    }
78
79    /// The verbosity level of the message.
80    pub fn level(&self) -> Level {
81        self.metadata.level()
82    }
83
84    /// The name of the target of the directive.
85    pub fn target(&self) -> &'a str {
86        self.metadata.target()
87    }
88
89    /// The module path of the message.
90    pub fn module_path(&self) -> Option<&'a str> {
91        self.module_path.map(|s| s.get())
92    }
93
94    /// The source file containing the message.
95    pub fn file(&self) -> Option<&'a str> {
96        self.file.map(|s| s.get())
97    }
98
99    /// The filename of the source file.
100    // obtain filename only from record's full file path
101    // reason: the module is already logged + full file path is noisy for some layouts
102    pub fn filename(&self) -> std::borrow::Cow<'a, str> {
103        self.file()
104            .map(std::path::Path::new)
105            .and_then(std::path::Path::file_name)
106            .map(std::ffi::OsStr::to_string_lossy)
107            .unwrap_or_default()
108    }
109
110    /// The line containing the message.
111    pub fn line(&self) -> Option<u32> {
112        self.line
113    }
114
115    /// The message body.
116    pub fn args(&self) -> &fmt::Arguments<'a> {
117        &self.args
118    }
119
120    /// The key-values.
121    pub fn key_values(&self) -> &KeyValues<'a> {
122        &self.kvs
123    }
124
125    /// Convert to an owned record.
126    pub fn to_owned(&self) -> RecordOwned {
127        RecordOwned {
128            now: self.now,
129            metadata: MetadataOwned {
130                level: self.metadata.level,
131                target: Str::new_owned(self.metadata.target),
132            },
133            module_path: self.module_path.map(MaybeStaticStr::into_str),
134            file: self.file.map(MaybeStaticStr::into_str),
135            line: self.line,
136            args: match self.args.as_str() {
137                Some(s) => Str::new(s),
138                None => Str::new_owned(self.args.to_string()),
139            },
140            kvs: self
141                .kvs
142                .iter()
143                .map(|(k, v)| (k.to_owned(), v.to_owned()))
144                .collect(),
145        }
146    }
147}
148
149/// Builder for [`Record`].
150#[derive(Debug)]
151pub struct RecordBuilder<'a> {
152    record: Record<'a>,
153}
154
155impl Default for RecordBuilder<'_> {
156    fn default() -> Self {
157        RecordBuilder {
158            record: Record {
159                now: SystemTime::now(),
160                metadata: MetadataBuilder::default().build(),
161                module_path: None,
162                file: None,
163                line: None,
164                args: format_args!(""),
165                kvs: Default::default(),
166            },
167        }
168    }
169}
170
171impl<'a> RecordBuilder<'a> {
172    /// Set [`args`](Record::args).
173    pub fn args(mut self, args: fmt::Arguments<'a>) -> Self {
174        self.record.args = args;
175        self
176    }
177
178    /// Set [`metadata`](Record::metadata).
179    ///
180    /// Construct a `Metadata` object with [`MetadataBuilder`].
181    pub fn metadata(mut self, metadata: Metadata<'a>) -> Self {
182        self.record.metadata = metadata;
183        self
184    }
185
186    /// Set [`Metadata::level`].
187    pub fn level(mut self, level: Level) -> Self {
188        self.record.metadata.level = level;
189        self
190    }
191
192    /// Set [`Metadata::target`].
193    pub fn target(mut self, target: &'a str) -> Self {
194        self.record.metadata.target = target;
195        self
196    }
197
198    /// Set [`module_path`](Record::module_path).
199    pub fn module_path(mut self, path: Option<&'a str>) -> Self {
200        self.record.module_path = path.map(MaybeStaticStr::Str);
201        self
202    }
203
204    /// Set [`module_path`](Record::module_path) to a `'static` string.
205    pub fn module_path_static(mut self, path: &'static str) -> Self {
206        self.record.module_path = Some(MaybeStaticStr::Static(path));
207        self
208    }
209
210    /// Set [`file`](Record::file).
211    pub fn file(mut self, file: Option<&'a str>) -> Self {
212        self.record.file = file.map(MaybeStaticStr::Str);
213        self
214    }
215
216    /// Set [`file`](Record::file) to a `'static` string.
217    pub fn file_static(mut self, file: &'static str) -> Self {
218        self.record.file = Some(MaybeStaticStr::Static(file));
219        self
220    }
221
222    /// Set [`line`](Record::line).
223    pub fn line(mut self, line: Option<u32>) -> Self {
224        self.record.line = line;
225        self
226    }
227
228    /// Set [`key_values`](struct.Record.html#method.key_values)
229    pub fn key_values(mut self, kvs: impl Into<KeyValues<'a>>) -> Self {
230        self.record.kvs = kvs.into();
231        self
232    }
233
234    /// Invoke the builder and return a `Record`
235    pub fn build(self) -> Record<'a> {
236        self.record
237    }
238}
239
240/// Metadata about a log message.
241#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
242pub struct Metadata<'a> {
243    level: Level,
244    target: &'a str,
245}
246
247impl<'a> Metadata<'a> {
248    /// Get the level.
249    pub fn level(&self) -> Level {
250        self.level
251    }
252
253    /// Get the target.
254    pub fn target(&self) -> &'a str {
255        self.target
256    }
257}
258
259/// Builder for [`Metadata`].
260#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
261pub struct MetadataBuilder<'a> {
262    metadata: Metadata<'a>,
263}
264
265impl Default for MetadataBuilder<'_> {
266    fn default() -> Self {
267        MetadataBuilder {
268            metadata: Metadata {
269                level: Level::Info,
270                target: Default::default(),
271            },
272        }
273    }
274}
275
276impl<'a> MetadataBuilder<'a> {
277    /// Setter for [`level`](Metadata::level).
278    pub fn level(mut self, arg: Level) -> Self {
279        self.metadata.level = arg;
280        self
281    }
282
283    /// Setter for [`target`](Metadata::target).
284    pub fn target(mut self, target: &'a str) -> Self {
285        self.metadata.target = target;
286        self
287    }
288
289    /// Invoke the builder and return a `Metadata`
290    pub fn build(self) -> Metadata<'a> {
291        self.metadata
292    }
293}
294
295/// Owned version of a log record.
296#[derive(Clone, Debug)]
297pub struct RecordOwned {
298    // the observed time
299    now: SystemTime,
300
301    // the metadata
302    metadata: MetadataOwned,
303    module_path: Option<Str<'static>>,
304    file: Option<Str<'static>>,
305    line: Option<u32>,
306
307    // the payload
308    args: Str<'static>,
309
310    // structural logging
311    kvs: Vec<(kv::KeyOwned, kv::ValueOwned)>,
312}
313
314/// Owned version of metadata about a log message.
315#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
316struct MetadataOwned {
317    level: Level,
318    target: Str<'static>,
319}
320
321impl RecordOwned {
322    /// Process a `Record`.
323    ///
324    /// This is a workaround before `format_args` can return a value outlives the function call.
325    pub fn execute(&self, f: impl FnOnce(&Record) -> Result<(), Error>) -> Result<(), Error> {
326        f(&Record {
327            now: self.now,
328            metadata: Metadata {
329                level: self.metadata.level,
330                target: &self.metadata.target,
331            },
332            module_path: self.module_path.as_deref().map(MaybeStaticStr::Str),
333            file: self.file.as_deref().map(MaybeStaticStr::Str),
334            line: self.line,
335            args: format_args!("{}", self.args),
336            kvs: KeyValues::from(self.kvs.as_slice()),
337        })
338    }
339}
340
341/// An enum representing the available verbosity levels of the logger.
342#[repr(usize)]
343#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
344pub enum Level {
345    /// The "error" level.
346    ///
347    /// Designates very serious errors.
348    Error = 100,
349    /// The "warn" level.
350    ///
351    /// Designates hazardous situations.
352    Warn = 200,
353    /// The "info" level.
354    ///
355    /// Designates useful information.
356    Info = 300,
357    /// The "debug" level.
358    ///
359    /// Designates lower priority information.
360    Debug = 400,
361    /// The "trace" level.
362    ///
363    /// Designates very low priority, often extremely verbose, information.
364    Trace = 500,
365}
366
367impl Level {
368    /// Return the string representation of the `Level`.
369    ///
370    /// This returns the same string as the `fmt::Display` implementation.
371    pub fn as_str(&self) -> &'static str {
372        match self {
373            Level::Error => "ERROR",
374            Level::Warn => "WARN",
375            Level::Info => "INFO",
376            Level::Debug => "DEBUG",
377            Level::Trace => "TRACE",
378        }
379    }
380}
381
382impl fmt::Display for Level {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        f.pad(self.as_str())
385    }
386}
387
388/// An enum representing the available verbosity level filters of the logger.
389#[repr(usize)]
390#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
391pub enum LevelFilter {
392    /// A level lower than all log levels.
393    Off = 0,
394    /// Corresponds to the `Error` log level.
395    Error = 100,
396    /// Corresponds to the `Warn` log level.
397    Warn = 200,
398    /// Corresponds to the `Info` log level.
399    Info = 300,
400    /// Corresponds to the `Debug` log level.
401    Debug = 400,
402    /// Corresponds to the `Trace` log level.
403    Trace = 500,
404}
405
406impl LevelFilter {
407    /// Return the string representation of the `LevelFilter`.
408    ///
409    /// This returns the same string as the `fmt::Display` implementation.
410    pub fn as_str(&self) -> &'static str {
411        match self {
412            LevelFilter::Off => "OFF",
413            LevelFilter::Error => "ERROR",
414            LevelFilter::Warn => "WARN",
415            LevelFilter::Info => "INFO",
416            LevelFilter::Debug => "DEBUG",
417            LevelFilter::Trace => "TRACE",
418        }
419    }
420}
421
422impl fmt::Display for LevelFilter {
423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424        f.pad(self.as_str())
425    }
426}
427
428impl PartialEq<LevelFilter> for Level {
429    fn eq(&self, other: &LevelFilter) -> bool {
430        PartialEq::eq(&(*self as usize), &(*other as usize))
431    }
432}
433
434impl PartialOrd<LevelFilter> for Level {
435    fn partial_cmp(&self, other: &LevelFilter) -> Option<cmp::Ordering> {
436        Some(Ord::cmp(&(*self as usize), &(*other as usize)))
437    }
438}
439
440impl PartialEq<Level> for LevelFilter {
441    fn eq(&self, other: &Level) -> bool {
442        other.eq(self)
443    }
444}
445
446impl PartialOrd<Level> for LevelFilter {
447    fn partial_cmp(&self, other: &Level) -> Option<cmp::Ordering> {
448        Some(Ord::cmp(&(*self as usize), &(*other as usize)))
449    }
450}
451
452/// The type returned by `from_str` when the string doesn't match any of the log levels.
453#[derive(Debug, PartialEq, Eq)]
454#[non_exhaustive]
455pub struct ParseLevelError {}
456
457impl fmt::Display for ParseLevelError {
458    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
459        fmt.write_str("malformed log level")
460    }
461}
462
463impl std::error::Error for ParseLevelError {}
464
465impl FromStr for Level {
466    type Err = ParseLevelError;
467    fn from_str(s: &str) -> Result<Level, Self::Err> {
468        for (name, level) in [
469            ("error", Level::Error),
470            ("warn", Level::Warn),
471            ("info", Level::Info),
472            ("debug", Level::Debug),
473            ("trace", Level::Trace),
474        ] {
475            if s.eq_ignore_ascii_case(name) {
476                return Ok(level);
477            }
478        }
479
480        Err(ParseLevelError {})
481    }
482}
483
484impl FromStr for LevelFilter {
485    type Err = ParseLevelError;
486    fn from_str(s: &str) -> Result<LevelFilter, Self::Err> {
487        for (name, level) in [
488            ("off", LevelFilter::Off),
489            ("error", LevelFilter::Error),
490            ("warn", LevelFilter::Warn),
491            ("info", LevelFilter::Info),
492            ("debug", LevelFilter::Debug),
493            ("trace", LevelFilter::Trace),
494        ] {
495            if s.eq_ignore_ascii_case(name) {
496                return Ok(level);
497            }
498        }
499
500        Err(ParseLevelError {})
501    }
502}