Skip to main content

obs_types/
severity.rs

1//! [`Severity`] — six levels aligned with OTel `SeverityNumber` buckets.
2
3use std::str::FromStr;
4
5use buffa::Enumeration;
6use serde::{Deserialize, Serialize};
7
8use crate::UnknownVariant;
9
10/// Six-level severity matching OTel `SeverityNumber` buckets.
11///
12/// A schema declares a `default_sev`. Call sites may **escalate or demote**
13/// through `emit_at(sev)`; the value passed wins. See
14/// [10-data-model.md § 3](../../specs/10-data-model.md#3-severity).
15#[derive(
16    Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
17)]
18#[serde(rename_all = "snake_case")]
19#[repr(i32)]
20#[non_exhaustive]
21pub enum Severity {
22    /// `SEVERITY_UNSPECIFIED`; never appears in a well-formed envelope.
23    #[default]
24    Unspecified = 0,
25    /// Most verbose; for fine-grained tracing only.
26    Trace = 1,
27    /// Diagnostic detail useful when chasing a problem.
28    Debug = 2,
29    /// Default for normal operational events.
30    Info = 3,
31    /// Something noteworthy that may require attention.
32    Warn = 4,
33    /// A failure that affected this operation.
34    Error = 5,
35    /// A failure severe enough that the process is likely tearing down.
36    Fatal = 6,
37}
38
39impl Severity {
40    /// Stable string label; used by sinks (`labels[\"sev\"]`) and CLI rendering.
41    #[must_use]
42    pub const fn as_str(self) -> &'static str {
43        match self {
44            Self::Unspecified => "unspecified",
45            Self::Trace => "trace",
46            Self::Debug => "debug",
47            Self::Info => "info",
48            Self::Warn => "warn",
49            Self::Error => "error",
50            Self::Fatal => "fatal",
51        }
52    }
53
54    /// Map to OTLP `SeverityNumber` (1..=24 with 4 buckets per band).
55    /// Unspecified maps to 0; fatal maps to 21 (`Fatal`).
56    /// See [20-otel-and-sinks.md §
57    /// 2.2](../../specs/20-otel-and-sinks.md#22-severity--otlp-severitynumber).
58    #[must_use]
59    pub const fn otlp_number(self) -> i32 {
60        match self {
61            Self::Unspecified => 0,
62            Self::Trace => 1,
63            Self::Debug => 5,
64            Self::Info => 9,
65            Self::Warn => 13,
66            Self::Error => 17,
67            Self::Fatal => 21,
68        }
69    }
70}
71
72impl Enumeration for Severity {
73    fn from_i32(value: i32) -> Option<Self> {
74        match value {
75            0 => Some(Self::Unspecified),
76            1 => Some(Self::Trace),
77            2 => Some(Self::Debug),
78            3 => Some(Self::Info),
79            4 => Some(Self::Warn),
80            5 => Some(Self::Error),
81            6 => Some(Self::Fatal),
82            _ => None,
83        }
84    }
85
86    fn to_i32(&self) -> i32 {
87        *self as i32
88    }
89
90    fn proto_name(&self) -> &'static str {
91        match self {
92            Self::Unspecified => "SEVERITY_UNSPECIFIED",
93            Self::Trace => "SEVERITY_TRACE",
94            Self::Debug => "SEVERITY_DEBUG",
95            Self::Info => "SEVERITY_INFO",
96            Self::Warn => "SEVERITY_WARN",
97            Self::Error => "SEVERITY_ERROR",
98            Self::Fatal => "SEVERITY_FATAL",
99        }
100    }
101
102    fn from_proto_name(name: &str) -> Option<Self> {
103        match name {
104            "SEVERITY_UNSPECIFIED" => Some(Self::Unspecified),
105            "SEVERITY_TRACE" => Some(Self::Trace),
106            "SEVERITY_DEBUG" => Some(Self::Debug),
107            "SEVERITY_INFO" => Some(Self::Info),
108            "SEVERITY_WARN" => Some(Self::Warn),
109            "SEVERITY_ERROR" => Some(Self::Error),
110            "SEVERITY_FATAL" => Some(Self::Fatal),
111            _ => None,
112        }
113    }
114
115    fn values() -> &'static [Self] {
116        &[
117            Self::Unspecified,
118            Self::Trace,
119            Self::Debug,
120            Self::Info,
121            Self::Warn,
122            Self::Error,
123            Self::Fatal,
124        ]
125    }
126}
127
128impl FromStr for Severity {
129    type Err = UnknownVariant;
130
131    fn from_str(s: &str) -> Result<Self, Self::Err> {
132        match s.to_ascii_lowercase().as_str() {
133            "trace" => Ok(Self::Trace),
134            "debug" => Ok(Self::Debug),
135            "info" => Ok(Self::Info),
136            "warn" | "warning" => Ok(Self::Warn),
137            "error" | "err" => Ok(Self::Error),
138            "fatal" => Ok(Self::Fatal),
139            _ => Err(UnknownVariant {
140                kind: "Severity",
141                value: s.to_string(),
142            }),
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_should_round_trip_via_i32() {
153        for v in Severity::values() {
154            assert_eq!(Severity::from_i32(v.to_i32()), Some(*v));
155        }
156    }
157
158    #[test]
159    fn test_should_order_correctly() {
160        assert!(Severity::Info < Severity::Warn);
161        assert!(Severity::Error < Severity::Fatal);
162    }
163
164    #[test]
165    fn test_should_map_otlp_numbers() {
166        assert_eq!(Severity::Info.otlp_number(), 9);
167        assert_eq!(Severity::Fatal.otlp_number(), 21);
168    }
169
170    #[test]
171    fn test_should_parse_aliases() {
172        assert_eq!("warning".parse::<Severity>().unwrap(), Severity::Warn);
173        assert_eq!("err".parse::<Severity>().unwrap(), Severity::Error);
174    }
175}