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}