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::borrow::Cow;
18use std::cmp;
19use std::fmt;
20use std::str::FromStr;
21use std::time::SystemTime;
22
23use crate::kv;
24use crate::kv::KeyValues;
25use crate::str::Str;
26
27// This struct is preferred over `Str` because we need to return a &'a str
28// when holding only a reference to the str ref. But `Str::get` return a &str
29// that lives as long as the `Str` itself, which is not necessarily 'a.
30#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
31enum MaybeStaticStr<'a> {
32    Str(&'a str),
33    Static(&'static str),
34}
35
36impl<'a> MaybeStaticStr<'a> {
37    fn get(&self) -> &'a str {
38        match *self {
39            MaybeStaticStr::Str(s) => s,
40            MaybeStaticStr::Static(s) => s,
41        }
42    }
43
44    fn get_static(&self) -> Option<&'static str> {
45        match *self {
46            MaybeStaticStr::Str(_) => None,
47            MaybeStaticStr::Static(s) => Some(s),
48        }
49    }
50
51    fn into_str(self) -> Str<'static> {
52        match self {
53            MaybeStaticStr::Str(s) => Str::new_shared(s),
54            MaybeStaticStr::Static(s) => Str::new(s),
55        }
56    }
57}
58
59/// The payload of a log message.
60#[derive(Clone, Debug)]
61pub struct Record<'a> {
62    // the observed time
63    now: SystemTime,
64
65    // the metadata
66    metadata: Metadata<'a>,
67    module_path: Option<MaybeStaticStr<'a>>,
68    file: Option<MaybeStaticStr<'a>>,
69    line: Option<u32>,
70
71    // the payload
72    payload: Str<'static>,
73
74    // structural logging
75    kvs: KeyValues<'a>,
76}
77
78impl<'a> Record<'a> {
79    /// The observed time.
80    pub fn time(&self) -> SystemTime {
81        self.now
82    }
83
84    /// Metadata about the log directive.
85    pub fn metadata(&self) -> &Metadata<'a> {
86        &self.metadata
87    }
88
89    /// The verbosity level of the message.
90    pub fn level(&self) -> Level {
91        self.metadata.level()
92    }
93
94    /// The name of the target of the directive.
95    pub fn target(&self) -> &'a str {
96        self.metadata.target()
97    }
98
99    /// The module path of the message.
100    pub fn module_path(&self) -> Option<&'a str> {
101        self.module_path.map(|s| s.get())
102    }
103
104    /// The module path of the message, if it is a `'static` str.
105    pub fn module_path_static(&self) -> Option<&'static str> {
106        self.module_path.and_then(|s| s.get_static())
107    }
108
109    /// The source file containing the message.
110    pub fn file(&self) -> Option<&'a str> {
111        self.file.map(|s| s.get())
112    }
113
114    /// The source file containing the message, if it is a `'static` str.
115    pub fn file_static(&self) -> Option<&'static str> {
116        self.file.and_then(|s| s.get_static())
117    }
118
119    /// The filename of the source file.
120    // obtain filename only from record's full file path
121    // reason: the module is already logged + full file path is noisy for some layouts
122    pub fn filename(&self) -> Cow<'a, str> {
123        self.file()
124            .map(std::path::Path::new)
125            .and_then(std::path::Path::file_name)
126            .map(std::ffi::OsStr::to_string_lossy)
127            .unwrap_or_default()
128    }
129
130    /// The line containing the message.
131    pub fn line(&self) -> Option<u32> {
132        self.line
133    }
134
135    /// The message body.
136    pub fn payload(&self) -> &str {
137        self.payload.get()
138    }
139
140    /// The message body, if it is a `'static` str.
141    pub fn payload_static(&self) -> Option<&'static str> {
142        self.payload.get_static()
143    }
144
145    /// The key-values.
146    pub fn key_values(&self) -> &KeyValues<'a> {
147        &self.kvs
148    }
149
150    /// Convert to an owned record.
151    pub fn to_owned(&self) -> RecordOwned {
152        RecordOwned {
153            now: self.now,
154            metadata: MetadataOwned {
155                level: self.metadata.level,
156                target: Str::new_shared(self.metadata.target),
157            },
158            module_path: self.module_path.map(MaybeStaticStr::into_str),
159            file: self.file.map(MaybeStaticStr::into_str),
160            line: self.line,
161            payload: self.payload.clone(),
162            kvs: self
163                .kvs
164                .iter()
165                .map(|(k, v)| (k.to_owned(), v.to_owned()))
166                .collect(),
167        }
168    }
169
170    /// Create a builder initialized with the current record's values.
171    pub fn to_builder(&self) -> RecordBuilder<'a> {
172        RecordBuilder {
173            record: Record {
174                now: self.now,
175                metadata: Metadata {
176                    level: self.metadata.level,
177                    target: self.metadata.target,
178                },
179                module_path: self.module_path,
180                file: self.file,
181                line: self.line,
182                payload: self.payload.clone(),
183                kvs: self.kvs.clone(),
184            },
185        }
186    }
187
188    /// Returns a new builder.
189    pub fn builder() -> RecordBuilder<'a> {
190        RecordBuilder::default()
191    }
192}
193
194/// Builder for [`Record`].
195#[derive(Debug)]
196pub struct RecordBuilder<'a> {
197    record: Record<'a>,
198}
199
200impl Default for RecordBuilder<'_> {
201    fn default() -> Self {
202        RecordBuilder {
203            record: Record {
204                now: SystemTime::now(),
205                metadata: MetadataBuilder::default().build(),
206                module_path: None,
207                file: None,
208                line: None,
209                payload: Default::default(),
210                kvs: Default::default(),
211            },
212        }
213    }
214}
215
216impl<'a> RecordBuilder<'a> {
217    /// Set [`payload`](Record::payload).
218    pub fn payload(mut self, payload: impl Into<Cow<'static, str>>) -> Self {
219        self.record.payload = match payload.into() {
220            Cow::Borrowed(s) => Str::new(s),
221            Cow::Owned(s) => Str::new_shared(s),
222        };
223        self
224    }
225
226    /// Set [`metadata`](Record::metadata).
227    ///
228    /// Construct a `Metadata` object with [`MetadataBuilder`].
229    pub fn metadata(mut self, metadata: Metadata<'a>) -> Self {
230        self.record.metadata = metadata;
231        self
232    }
233
234    /// Set [`Metadata::level`].
235    pub fn level(mut self, level: Level) -> Self {
236        self.record.metadata.level = level;
237        self
238    }
239
240    /// Set [`Metadata::target`].
241    pub fn target(mut self, target: &'a str) -> Self {
242        self.record.metadata.target = target;
243        self
244    }
245
246    /// Set [`module_path`](Record::module_path).
247    pub fn module_path(mut self, path: Option<&'a str>) -> Self {
248        self.record.module_path = path.map(MaybeStaticStr::Str);
249        self
250    }
251
252    /// Set [`module_path`](Record::module_path) to a `'static` string.
253    pub fn module_path_static(mut self, path: &'static str) -> Self {
254        self.record.module_path = Some(MaybeStaticStr::Static(path));
255        self
256    }
257
258    /// Set [`file`](Record::file).
259    pub fn file(mut self, file: Option<&'a str>) -> Self {
260        self.record.file = file.map(MaybeStaticStr::Str);
261        self
262    }
263
264    /// Set [`file`](Record::file) to a `'static` string.
265    pub fn file_static(mut self, file: &'static str) -> Self {
266        self.record.file = Some(MaybeStaticStr::Static(file));
267        self
268    }
269
270    /// Set [`line`](Record::line).
271    pub fn line(mut self, line: Option<u32>) -> Self {
272        self.record.line = line;
273        self
274    }
275
276    /// Set [`key_values`](struct.Record.html#method.key_values)
277    pub fn key_values(mut self, kvs: impl Into<KeyValues<'a>>) -> Self {
278        self.record.kvs = kvs.into();
279        self
280    }
281
282    /// Invoke the builder and return a `Record`
283    pub fn build(self) -> Record<'a> {
284        self.record
285    }
286}
287
288/// Metadata about a log message.
289#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
290pub struct Metadata<'a> {
291    level: Level,
292    target: &'a str,
293}
294
295impl<'a> Metadata<'a> {
296    /// Get the level.
297    pub fn level(&self) -> Level {
298        self.level
299    }
300
301    /// Get the target.
302    pub fn target(&self) -> &'a str {
303        self.target
304    }
305
306    /// Create a builder initialized with the current metadata's values.
307    pub fn to_builder(&self) -> MetadataBuilder<'a> {
308        MetadataBuilder {
309            metadata: Metadata {
310                level: self.level,
311                target: self.target,
312            },
313        }
314    }
315
316    /// Returns a new builder.
317    pub fn builder() -> MetadataBuilder<'a> {
318        MetadataBuilder::default()
319    }
320}
321
322/// Builder for [`Metadata`].
323#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
324pub struct MetadataBuilder<'a> {
325    metadata: Metadata<'a>,
326}
327
328impl Default for MetadataBuilder<'_> {
329    fn default() -> Self {
330        MetadataBuilder {
331            metadata: Metadata {
332                level: Level::Info,
333                target: Default::default(),
334            },
335        }
336    }
337}
338
339impl<'a> MetadataBuilder<'a> {
340    /// Setter for [`level`](Metadata::level).
341    pub fn level(mut self, arg: Level) -> Self {
342        self.metadata.level = arg;
343        self
344    }
345
346    /// Setter for [`target`](Metadata::target).
347    pub fn target(mut self, target: &'a str) -> Self {
348        self.metadata.target = target;
349        self
350    }
351
352    /// Invoke the builder and return a `Metadata`
353    pub fn build(self) -> Metadata<'a> {
354        self.metadata
355    }
356}
357
358/// Owned version of a log record.
359#[derive(Clone, Debug)]
360pub struct RecordOwned {
361    // the observed time
362    now: SystemTime,
363
364    // the metadata
365    metadata: MetadataOwned,
366    module_path: Option<Str<'static>>,
367    file: Option<Str<'static>>,
368    line: Option<u32>,
369
370    // the payload
371    payload: Str<'static>,
372
373    // structural logging
374    kvs: Vec<(kv::KeyOwned, kv::ValueOwned)>,
375}
376
377/// Owned version of metadata about a log message.
378#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
379struct MetadataOwned {
380    level: Level,
381    target: Str<'static>,
382}
383
384impl RecordOwned {
385    /// Create a `Record` referencing the data in this `RecordOwned`.
386    pub fn as_record(&self) -> Record<'_> {
387        Record {
388            now: self.now,
389            metadata: Metadata {
390                level: self.metadata.level,
391                target: &self.metadata.target,
392            },
393            module_path: self.module_path.as_deref().map(MaybeStaticStr::Str),
394            file: self.file.as_deref().map(MaybeStaticStr::Str),
395            line: self.line,
396            payload: self.payload.clone(),
397            kvs: KeyValues::from(self.kvs.as_slice()),
398        }
399    }
400}
401
402/// An enum representing the available verbosity levels of the logger.
403#[repr(usize)]
404#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
405pub enum Level {
406    /// The "error" level.
407    ///
408    /// Designates very serious errors.
409    Error = 100,
410    /// The "warn" level.
411    ///
412    /// Designates hazardous situations.
413    Warn = 200,
414    /// The "info" level.
415    ///
416    /// Designates useful information.
417    Info = 300,
418    /// The "debug" level.
419    ///
420    /// Designates lower priority information.
421    Debug = 400,
422    /// The "trace" level.
423    ///
424    /// Designates very low priority, often extremely verbose, information.
425    Trace = 500,
426}
427
428impl Level {
429    /// Return the string representation of the `Level`.
430    ///
431    /// This returns the same string as the `fmt::Display` implementation.
432    pub fn as_str(&self) -> &'static str {
433        match self {
434            Level::Error => "ERROR",
435            Level::Warn => "WARN",
436            Level::Info => "INFO",
437            Level::Debug => "DEBUG",
438            Level::Trace => "TRACE",
439        }
440    }
441
442    /// Convert the `Level` to the equivalent `LevelFilter`.
443    pub fn to_level_filter(&self) -> LevelFilter {
444        match self {
445            Level::Error => LevelFilter::Error,
446            Level::Warn => LevelFilter::Warn,
447            Level::Info => LevelFilter::Info,
448            Level::Debug => LevelFilter::Debug,
449            Level::Trace => LevelFilter::Trace,
450        }
451    }
452}
453
454impl fmt::Display for Level {
455    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456        f.pad(self.as_str())
457    }
458}
459
460/// An enum representing the available verbosity level filters of the logger.
461#[repr(usize)]
462#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
463pub enum LevelFilter {
464    /// A level lower than all log levels.
465    Off = 0,
466    /// Corresponds to the `Error` log level.
467    Error = 100,
468    /// Corresponds to the `Warn` log level.
469    Warn = 200,
470    /// Corresponds to the `Info` log level.
471    Info = 300,
472    /// Corresponds to the `Debug` log level.
473    Debug = 400,
474    /// Corresponds to the `Trace` log level.
475    Trace = 500,
476}
477
478impl LevelFilter {
479    /// Return the string representation of the `LevelFilter`.
480    ///
481    /// This returns the same string as the `fmt::Display` implementation.
482    pub fn as_str(&self) -> &'static str {
483        match self {
484            LevelFilter::Off => "OFF",
485            LevelFilter::Error => "ERROR",
486            LevelFilter::Warn => "WARN",
487            LevelFilter::Info => "INFO",
488            LevelFilter::Debug => "DEBUG",
489            LevelFilter::Trace => "TRACE",
490        }
491    }
492}
493
494impl fmt::Display for LevelFilter {
495    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496        f.pad(self.as_str())
497    }
498}
499
500impl PartialEq<LevelFilter> for Level {
501    fn eq(&self, other: &LevelFilter) -> bool {
502        PartialEq::eq(&(*self as usize), &(*other as usize))
503    }
504}
505
506impl PartialOrd<LevelFilter> for Level {
507    fn partial_cmp(&self, other: &LevelFilter) -> Option<cmp::Ordering> {
508        Some(Ord::cmp(&(*self as usize), &(*other as usize)))
509    }
510}
511
512impl PartialEq<Level> for LevelFilter {
513    fn eq(&self, other: &Level) -> bool {
514        other.eq(self)
515    }
516}
517
518impl PartialOrd<Level> for LevelFilter {
519    fn partial_cmp(&self, other: &Level) -> Option<cmp::Ordering> {
520        Some(Ord::cmp(&(*self as usize), &(*other as usize)))
521    }
522}
523
524/// The type returned by `from_str` when the string doesn't match any of the log levels.
525#[derive(Debug, PartialEq, Eq)]
526#[non_exhaustive]
527pub struct ParseLevelError {}
528
529impl fmt::Display for ParseLevelError {
530    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
531        fmt.write_str("malformed log level")
532    }
533}
534
535impl std::error::Error for ParseLevelError {}
536
537impl FromStr for Level {
538    type Err = ParseLevelError;
539    fn from_str(s: &str) -> Result<Level, Self::Err> {
540        for (name, level) in [
541            ("error", Level::Error),
542            ("warn", Level::Warn),
543            ("info", Level::Info),
544            ("debug", Level::Debug),
545            ("trace", Level::Trace),
546        ] {
547            if s.eq_ignore_ascii_case(name) {
548                return Ok(level);
549            }
550        }
551
552        Err(ParseLevelError {})
553    }
554}
555
556impl FromStr for LevelFilter {
557    type Err = ParseLevelError;
558    fn from_str(s: &str) -> Result<LevelFilter, Self::Err> {
559        for (name, level) in [
560            ("off", LevelFilter::Off),
561            ("error", LevelFilter::Error),
562            ("warn", LevelFilter::Warn),
563            ("info", LevelFilter::Info),
564            ("debug", LevelFilter::Debug),
565            ("trace", LevelFilter::Trace),
566        ] {
567            if s.eq_ignore_ascii_case(name) {
568                return Ok(level);
569            }
570        }
571
572        Err(ParseLevelError {})
573    }
574}