Skip to main content

obs_kit/
self_event_builder.rs

1//! Fluent builder on top of [`obs_core::self_event`] for multi-label
2//! self-events. Spec § 5.3.
3
4use obs_core::{ObsEnvelope, Severity, Tier, observer, self_event};
5
6/// Fluent builder for runtime-style self-events.
7///
8/// Sinks, middleware, and tok's own init paths emit labels-only
9/// self-events (`ObsConfigReloaded`, `ObsBatchSinkUploaded`, …). The
10/// plain [`obs_core::self_event`] helper returns a bare envelope that
11/// the caller mutates; the builder wraps that in a fluent chain for
12/// call sites that set two or more labels.
13///
14/// ```no_run
15/// use obs_kit::SelfEventBuilder;
16/// use obs_kit::{Severity, Tier};
17///
18/// SelfEventBuilder::new("mylib.v1.WorkerRestart", Tier::Log, Severity::Warn)
19///     .label("reason", "timeout")
20///     .label("worker_id", "42")
21///     .emit();
22/// ```
23#[derive(Debug)]
24pub struct SelfEventBuilder {
25    env: ObsEnvelope,
26}
27
28impl SelfEventBuilder {
29    /// Start a new builder with the given `full_name`, `tier`, and
30    /// `sev`. `sampling_reason` is set to `SAMPLING_REASON_RUNTIME`
31    /// and `ts_ns` to the current wall-clock via
32    /// [`obs_core::self_event`].
33    #[must_use]
34    pub fn new(full_name: &str, tier: Tier, sev: Severity) -> Self {
35        Self {
36            env: self_event(full_name, tier, sev),
37        }
38    }
39
40    /// Insert (or replace) a label.
41    #[must_use]
42    pub fn label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
43        self.env.labels.insert(key.into(), value.into());
44        self
45    }
46
47    /// Extend the envelope with an iterator of `(key, value)` pairs.
48    /// Useful when the labels come from a pre-built
49    /// `BTreeMap`/`HashMap`.
50    #[must_use]
51    pub fn labels<I, K, V>(mut self, iter: I) -> Self
52    where
53        I: IntoIterator<Item = (K, V)>,
54        K: Into<String>,
55        V: Into<String>,
56    {
57        for (k, v) in iter {
58            self.env.labels.insert(k.into(), v.into());
59        }
60        self
61    }
62
63    /// Emit the envelope through the active observer.
64    pub fn emit(self) {
65        observer().emit_envelope(self.env);
66    }
67
68    /// Return the built envelope without emitting. Useful for tests
69    /// and for call sites that want to attach a payload before
70    /// handing the envelope to a sink manually.
71    #[must_use]
72    pub fn build(self) -> ObsEnvelope {
73        self.env
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_should_collect_labels() {
83        let env = SelfEventBuilder::new("obs.runtime.v1.ObsTest", Tier::Log, Severity::Info)
84            .label("k1", "v1")
85            .label("k2", "v2")
86            .build();
87        assert_eq!(env.full_name, "obs.runtime.v1.ObsTest");
88        assert_eq!(env.labels.get("k1"), Some(&"v1".to_string()));
89        assert_eq!(env.labels.get("k2"), Some(&"v2".to_string()));
90    }
91
92    #[test]
93    fn test_should_extend_from_iter() {
94        let env = SelfEventBuilder::new("obs.runtime.v1.ObsTest", Tier::Log, Severity::Info)
95            .labels([("a", "1"), ("b", "2")])
96            .build();
97        assert_eq!(env.labels.get("a"), Some(&"1".to_string()));
98        assert_eq!(env.labels.get("b"), Some(&"2".to_string()));
99    }
100}