Skip to main content

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::fmt;
19use std::str::FromStr;
20use std::time::SystemTime;
21
22use crate::Error;
23use crate::kv;
24use crate::kv::KeyValues;
25use crate::str::RefStr;
26
27/// The payload of a log message.
28#[derive(Clone, Debug)]
29pub struct Record<'a> {
30    // the observed time
31    now: SystemTime,
32
33    // the metadata
34    level: Level,
35    target: RefStr<'a>,
36    module_path: Option<RefStr<'a>>,
37    file: Option<RefStr<'a>>,
38    line: Option<u32>,
39    column: Option<u32>,
40
41    // the payload
42    payload: fmt::Arguments<'a>,
43
44    // structural logging
45    kvs: KeyValues<'a>,
46}
47
48impl<'a> Record<'a> {
49    /// The observed time.
50    pub fn time(&self) -> SystemTime {
51        self.now
52    }
53
54    /// The severity level of the message.
55    ///
56    /// See [`Level`] for details.
57    pub fn level(&self) -> Level {
58        self.level
59    }
60
61    /// The name of the target of the directive.
62    ///
63    /// This is typically the same as the module path, but can be set explicitly.
64    pub fn target(&self) -> &'a str {
65        self.target.get()
66    }
67
68    /// The name of the target of the directive, if it is a `'static` str.
69    ///
70    /// This is typically the same as the module path, but can be set explicitly.
71    pub fn target_static(&self) -> Option<&'static str> {
72        self.target.get_static()
73    }
74
75    /// The module path of the message.
76    pub fn module_path(&self) -> Option<&'a str> {
77        self.module_path.map(|s| s.get())
78    }
79
80    /// The module path of the message, if it is a `'static` str.
81    pub fn module_path_static(&self) -> Option<&'static str> {
82        self.module_path.and_then(|s| s.get_static())
83    }
84
85    /// The source file containing the message.
86    pub fn file(&self) -> Option<&'a str> {
87        self.file.map(|s| s.get())
88    }
89
90    /// The source file containing the message, if it is a `'static` str.
91    pub fn file_static(&self) -> Option<&'static str> {
92        self.file.and_then(|s| s.get_static())
93    }
94
95    /// The filename of the source file.
96    // obtain filename only from record's full file path
97    // reason: the module is already logged + full file path is noisy for some layouts
98    pub fn filename(&self) -> Cow<'a, str> {
99        self.file()
100            .map(std::path::Path::new)
101            .and_then(std::path::Path::file_name)
102            .map(std::ffi::OsStr::to_string_lossy)
103            .unwrap_or_default()
104    }
105
106    /// The line number in the source file.
107    ///
108    /// This is typically set by the logging macro. If set, returns `Some(column)`; otherwise,
109    /// returns `None`.
110    pub fn line(&self) -> Option<u32> {
111        self.line
112    }
113
114    /// The column number in the source file.
115    ///
116    /// This is typically not set. If set, returns `Some(column)`; otherwise, returns `None`.
117    pub fn column(&self) -> Option<u32> {
118        self.column
119    }
120
121    /// The message body.
122    pub fn payload(&self) -> fmt::Arguments<'a> {
123        self.payload
124    }
125
126    /// The message body, if it is a `'static` str.
127    pub fn payload_static(&self) -> Option<&'static str> {
128        self.payload.as_str()
129    }
130
131    /// The key-values.
132    pub fn key_values(&self) -> &KeyValues<'a> {
133        &self.kvs
134    }
135
136    /// Create a builder initialized with the current record's values.
137    pub fn to_builder(&self) -> RecordBuilder<'a> {
138        RecordBuilder {
139            record: Record {
140                now: self.now,
141                level: self.level,
142                target: self.target,
143                module_path: self.module_path,
144                file: self.file,
145                line: self.line,
146                column: self.column,
147                payload: self.payload,
148                kvs: self.kvs,
149            },
150        }
151    }
152
153    /// Convert to an owned record.
154    pub fn to_owned(&self) -> RecordOwned {
155        RecordOwned {
156            now: self.now,
157            level: self.level,
158            target: self.target.into_cow_static(),
159            module_path: self.module_path.map(|m| m.into_cow_static()),
160            file: self.file.map(|f| f.into_cow_static()),
161            line: self.line,
162            column: self.column,
163            payload: if let Some(s) = self.payload.as_str() {
164                Cow::Borrowed(s)
165            } else {
166                Cow::Owned(self.payload.to_string())
167            },
168            kvs: self
169                .kvs
170                .iter()
171                .map(|(k, v)| (k.to_owned(), v.to_owned()))
172                .collect(),
173        }
174    }
175
176    /// Returns a new builder.
177    pub fn builder() -> RecordBuilder<'a> {
178        RecordBuilder::default()
179    }
180}
181
182/// Builder for [`Record`].
183#[derive(Debug)]
184pub struct RecordBuilder<'a> {
185    record: Record<'a>,
186}
187
188impl Default for RecordBuilder<'_> {
189    fn default() -> Self {
190        RecordBuilder {
191            record: Record {
192                now: SystemTime::now(),
193                level: Level::Info,
194                target: RefStr::Static(""),
195                module_path: None,
196                file: None,
197                line: None,
198                column: None,
199                payload: format_args!(""),
200                kvs: KeyValues::empty(),
201            },
202        }
203    }
204}
205
206impl<'a> RecordBuilder<'a> {
207    /// Set [`payload`](Record::payload).
208    pub fn payload(mut self, payload: fmt::Arguments<'a>) -> Self {
209        self.record.payload = payload;
210        self
211    }
212
213    /// Set [`level`](Record::level).
214    pub fn level(mut self, level: Level) -> Self {
215        self.record.level = level;
216        self
217    }
218
219    /// Set [`target`](Record::target).
220    pub fn target(mut self, target: &'a str) -> Self {
221        self.record.target = RefStr::Borrowed(target);
222        self
223    }
224
225    /// Set [`target`](Record::target) to a `'static` string.
226    pub fn target_static(mut self, target: &'static str) -> Self {
227        self.record.target = RefStr::Static(target);
228        self
229    }
230
231    /// Set [`module_path`](Record::module_path).
232    pub fn module_path(mut self, path: Option<&'a str>) -> Self {
233        self.record.module_path = path.map(RefStr::Borrowed);
234        self
235    }
236
237    /// Set [`module_path`](Record::module_path) to a `'static` string.
238    pub fn module_path_static(mut self, path: &'static str) -> Self {
239        self.record.module_path = Some(RefStr::Static(path));
240        self
241    }
242
243    /// Set [`file`](Record::file).
244    pub fn file(mut self, file: Option<&'a str>) -> Self {
245        self.record.file = file.map(RefStr::Borrowed);
246        self
247    }
248
249    /// Set [`file`](Record::file) to a `'static` string.
250    pub fn file_static(mut self, file: &'static str) -> Self {
251        self.record.file = Some(RefStr::Static(file));
252        self
253    }
254
255    /// Set [`line`](Record::line).
256    pub fn line(mut self, line: Option<u32>) -> Self {
257        self.record.line = line;
258        self
259    }
260
261    /// Set [`column`](Record::column).
262    pub fn column(mut self, column: Option<u32>) -> Self {
263        self.record.column = column;
264        self
265    }
266
267    /// Set [`key_values`](struct.Record.html#method.key_values)
268    pub fn key_values(mut self, kvs: impl Into<KeyValues<'a>>) -> Self {
269        self.record.kvs = kvs.into();
270        self
271    }
272
273    /// Invoke the builder and return a `Record`
274    pub fn build(self) -> Record<'a> {
275        self.record
276    }
277}
278
279/// Owned version of a log record.
280#[derive(Clone, Debug)]
281pub struct RecordOwned {
282    // the observed time
283    now: SystemTime,
284
285    // the metadata
286    level: Level,
287    target: Cow<'static, str>,
288    module_path: Option<Cow<'static, str>>,
289    file: Option<Cow<'static, str>>,
290    line: Option<u32>,
291    column: Option<u32>,
292
293    // the payload
294    payload: Cow<'static, str>,
295
296    // structural logging
297    kvs: Vec<(kv::KeyOwned, kv::ValueOwned)>,
298}
299
300impl RecordOwned {
301    /// Execute the given function with the `Record`.
302    pub fn with(&self, f: impl FnOnce(Record<'_>)) {
303        f(Record {
304            now: self.now,
305            level: self.level,
306            target: match &self.target {
307                Cow::Borrowed(s) => RefStr::Static(s),
308                Cow::Owned(s) => RefStr::Borrowed(s.as_ref()),
309            },
310            module_path: match &self.module_path {
311                Some(Cow::Borrowed(s)) => Some(RefStr::Static(s)),
312                Some(Cow::Owned(s)) => Some(RefStr::Borrowed(s)),
313                None => None,
314            },
315            file: match &self.file {
316                Some(Cow::Borrowed(s)) => Some(RefStr::Static(s)),
317                Some(Cow::Owned(s)) => Some(RefStr::Borrowed(s)),
318                None => None,
319            },
320            line: self.line,
321            column: self.column,
322            payload: format_args!("{}", self.payload),
323            kvs: KeyValues::from(self.kvs.as_slice()),
324        });
325    }
326}
327
328/// A minimal set of criteria for pre-filtering purposes.
329#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
330pub struct FilterCriteria<'a> {
331    level: Level,
332    target: &'a str,
333}
334
335impl<'a> FilterCriteria<'a> {
336    /// Get the [`level`](Record::level).
337    pub fn level(&self) -> Level {
338        self.level
339    }
340
341    /// Get the [`target`](Record::target).
342    pub fn target(&self) -> &'a str {
343        self.target
344    }
345
346    /// Create a builder initialized with the current criteria's values.
347    pub fn to_builder(&self) -> FilterCriteriaBuilder<'a> {
348        FilterCriteriaBuilder {
349            metadata: FilterCriteria {
350                level: self.level,
351                target: self.target,
352            },
353        }
354    }
355
356    /// Return a brand-new builder.
357    pub fn builder() -> FilterCriteriaBuilder<'a> {
358        FilterCriteriaBuilder::default()
359    }
360}
361
362/// Builder for [`FilterCriteria`].
363#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
364pub struct FilterCriteriaBuilder<'a> {
365    metadata: FilterCriteria<'a>,
366}
367
368impl Default for FilterCriteriaBuilder<'_> {
369    fn default() -> Self {
370        FilterCriteriaBuilder {
371            metadata: FilterCriteria {
372                level: Level::Info,
373                target: "",
374            },
375        }
376    }
377}
378
379impl<'a> FilterCriteriaBuilder<'a> {
380    /// Setter for [`level`](FilterCriteria::level).
381    pub fn level(mut self, arg: Level) -> Self {
382        self.metadata.level = arg;
383        self
384    }
385
386    /// Setter for [`target`](FilterCriteria::target).
387    pub fn target(mut self, target: &'a str) -> Self {
388        self.metadata.target = target;
389        self
390    }
391
392    /// Invoke the builder and return a `Metadata`
393    pub fn build(self) -> FilterCriteria<'a> {
394        self.metadata
395    }
396}
397
398/// A Level is the importance or severity of a log event.
399///
400/// The higher the level, the more important or severe the event.
401///
402/// The level design follows the [OpenTelemetry severity number specification][severity-number]
403/// and [mapping guideline][mapping-guideline].
404///
405/// [severity-number]: https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
406/// [mapping-guideline]: https://opentelemetry.io/docs/specs/otel/logs/data-model-appendix/#appendix-b-severitynumber-example-mappings
407#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
408#[repr(u8)]
409pub enum Level {
410    /// TRACE. A fine-grained debugging event.
411    ///
412    /// Typically disabled in default configurations.
413    Trace = 1,
414    /// TRACE2. A fine-grained debugging event.
415    ///
416    /// Typically disabled in default configurations.
417    Trace2 = 2,
418    /// TRACE3. A fine-grained debugging event.
419    ///
420    /// Typically disabled in default configurations.
421    Trace3 = 3,
422    /// TRACE4. A fine-grained debugging event.
423    ///
424    /// Typically disabled in default configurations.
425    Trace4 = 4,
426    /// DEBUG. A debugging event.
427    Debug = 5,
428    /// DEBUG2. A debugging event.
429    Debug2 = 6,
430    /// DEBUG2. A debugging event.
431    Debug3 = 7,
432    /// DEBUG3. A debugging event.
433    Debug4 = 8,
434    /// INFO. An informational event.
435    ///
436    /// Indicates that an event happened.
437    Info = 9,
438    /// INFO2. An informational event.
439    ///
440    /// Indicates that an event happened.
441    Info2 = 10,
442    /// INFO3. An informational event.
443    ///
444    /// Indicates that an event happened.
445    Info3 = 11,
446    /// INFO4. An informational event.
447    ///
448    /// Indicates that an event happened.
449    Info4 = 12,
450    /// WARN. A warning event.
451    ///
452    /// Not an error but is likely more important than an informational event.
453    Warn = 13,
454    /// WARN2. A warning event.
455    ///
456    /// Not an error but is likely more important than an informational event.
457    Warn2 = 14,
458    /// WARN3. A warning event.
459    ///
460    /// Not an error but is likely more important than an informational event.
461    Warn3 = 15,
462    /// WARN4. A warning event.
463    ///
464    /// Not an error but is likely more important than an informational event.
465    Warn4 = 16,
466    /// ERROR. An error event.
467    ///
468    /// Something went wrong.
469    Error = 17,
470    /// ERROR2. An error event.
471    ///
472    /// Something went wrong.
473    Error2 = 18,
474    /// ERROR3. An error event.
475    ///
476    /// Something went wrong.
477    Error3 = 19,
478    /// ERROR4. An error event.
479    ///
480    /// Something went wrong.
481    Error4 = 20,
482    /// FATAL. A fatal error such as application or system crash.
483    Fatal = 21,
484    /// FATAL2. A fatal error such as application or system crash.
485    Fatal2 = 22,
486    /// FATAL3. A fatal error such as application or system crash.
487    Fatal3 = 23,
488    /// FATAL4. A fatal error such as application or system crash.
489    Fatal4 = 24,
490}
491
492impl Level {
493    /// Return the string representation the short name for the `Level`.
494    ///
495    /// This returns the same string as the `fmt::Display` implementation.
496    pub const fn name(&self) -> &'static str {
497        const LEVEL_NAMES: [&str; 24] = [
498            "TRACE", "TRACE2", "TRACE3", "TRACE4", "DEBUG", "DEBUG2", "DEBUG3", "DEBUG4", "INFO",
499            "INFO2", "INFO3", "INFO4", "WARN", "WARN2", "WARN3", "WARN4", "ERROR", "ERROR2",
500            "ERROR3", "ERROR4", "FATAL", "FATAL2", "FATAL3", "FATAL4",
501        ];
502        LEVEL_NAMES[*self as usize - 1]
503    }
504}
505
506impl fmt::Debug for Level {
507    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
508        f.pad(self.name())
509    }
510}
511
512impl fmt::Display for Level {
513    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514        f.pad(self.name())
515    }
516}
517
518/// An enum representing the available verbosity level filters of the logger.
519#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
520pub enum LevelFilter {
521    /// Disable all levels.
522    Off,
523    /// Enabled if the target level is equal to the filter level.
524    Equal(Level),
525    /// Enabled if the target level is not equal to the filter level.
526    NotEqual(Level),
527    /// Enabled if the target level is more severe than the filter level.
528    MoreSevere(Level),
529    /// Enabled if the target level is more severe than or equal to the filter
530    /// level.
531    MoreSevereEqual(Level),
532    /// Enabled if the target level is more verbose than the filter level.
533    MoreVerbose(Level),
534    /// Enabled if the target level is more verbose than or equal to the filter
535    /// level.
536    MoreVerboseEqual(Level),
537    /// Enable all levels.
538    All,
539}
540
541impl LevelFilter {
542    /// Check the given level if satisfies the filter condition.
543    ///
544    /// # Examples
545    ///
546    /// ```
547    /// use logforth_core::record::Level;
548    /// use logforth_core::record::LevelFilter;
549    ///
550    /// let level_filter = LevelFilter::MoreSevere(Level::Info);
551    ///
552    /// assert_eq!(level_filter.test(Level::Trace), false);
553    /// assert_eq!(level_filter.test(Level::Info), false);
554    /// assert_eq!(level_filter.test(Level::Warn), true);
555    /// assert_eq!(level_filter.test(Level::Error), true);
556    /// ```
557    pub fn test(&self, level: Level) -> bool {
558        match self {
559            LevelFilter::Off => false,
560            LevelFilter::Equal(l) => level == *l,
561            LevelFilter::NotEqual(l) => level != *l,
562            LevelFilter::MoreSevere(l) => level > *l,
563            LevelFilter::MoreSevereEqual(l) => level >= *l,
564            LevelFilter::MoreVerbose(l) => level < *l,
565            LevelFilter::MoreVerboseEqual(l) => level <= *l,
566            LevelFilter::All => true,
567        }
568    }
569}
570
571impl FromStr for Level {
572    type Err = Error;
573    fn from_str(s: &str) -> Result<Level, Self::Err> {
574        for (repr, level) in [
575            // common cases
576            ("fatal", Level::Fatal),
577            ("error", Level::Error),
578            ("warn", Level::Warn),
579            ("info", Level::Info),
580            ("debug", Level::Debug),
581            ("trace", Level::Trace),
582            // other offset levels
583            ("fatal2", Level::Fatal2),
584            ("fatal3", Level::Fatal3),
585            ("fatal4", Level::Fatal4),
586            ("error2", Level::Error2),
587            ("error3", Level::Error3),
588            ("error4", Level::Error4),
589            ("warn2", Level::Warn2),
590            ("warn3", Level::Warn3),
591            ("warn4", Level::Warn4),
592            ("info2", Level::Info2),
593            ("info3", Level::Info3),
594            ("info4", Level::Info4),
595            ("debug2", Level::Debug2),
596            ("debug3", Level::Debug3),
597            ("debug4", Level::Debug4),
598            ("trace2", Level::Trace2),
599            ("trace3", Level::Trace3),
600            ("trace4", Level::Trace4),
601        ] {
602            if s.eq_ignore_ascii_case(repr) {
603                return Ok(level);
604            }
605        }
606
607        Err(Error::new(format!("malformed level: {s:?}")))
608    }
609}
610
611#[cfg(test)]
612mod tests {
613    use super::*;
614
615    #[test]
616    fn round_trip_level() {
617        let levels = [
618            Level::Trace,
619            Level::Trace2,
620            Level::Trace3,
621            Level::Trace4,
622            Level::Debug,
623            Level::Debug2,
624            Level::Debug3,
625            Level::Debug4,
626            Level::Info,
627            Level::Info2,
628            Level::Info3,
629            Level::Info4,
630            Level::Warn,
631            Level::Warn2,
632            Level::Warn3,
633            Level::Warn4,
634            Level::Error,
635            Level::Error2,
636            Level::Error3,
637            Level::Error4,
638            Level::Fatal,
639            Level::Fatal2,
640            Level::Fatal3,
641            Level::Fatal4,
642        ];
643
644        for &level in &levels {
645            let s = level.name();
646            let parsed = s.parse::<Level>().unwrap();
647            assert_eq!(level, parsed);
648        }
649    }
650}