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