Skip to main content

obs_core/
self_event.rs

1//! Shared primitive for building labels-only self-event envelopes.
2//!
3//! Sinks, middleware, and tok-side code that emits runtime self-events
4//! all end up wanting the same envelope shape: a `full_name`, a `tier`,
5//! a `sev`, a wall-clock `ts_ns`, and `sampling_reason =
6//! SAMPLING_REASON_RUNTIME` (so downstream consumers can distinguish
7//! framework emissions from user emissions). Each of the three
8//! self-event modules under `obs-core` / `obs-sink-batch` / `obs-prom`
9//! used to hand-roll that envelope plus its own `tier_to_proto` /
10//! `sev_to_proto` / `now_ns` helpers; this module collapses those
11//! copies into one primitive.
12//!
13//! After Phase 3b (obs-types retirement), [`Tier`] and [`Severity`]
14//! *are* the proto-generated enums — no conversion layer remains.
15
16use buffa::EnumValue;
17use obs_proto::obs::v1::{ObsEnvelope, SamplingReason, Severity, Tier};
18
19/// Build a labels-only self-event envelope with `sampling_reason =
20/// SAMPLING_REASON_RUNTIME` and the current wall-clock `ts_ns`.
21///
22/// Typical usage from a sink, middleware adapter, or runtime module:
23///
24/// ```no_run
25/// use obs_core::{observer, self_event, Severity, Tier};
26///
27/// let mut env = self_event("mylib.v1.WorkerRestart", Tier::Log, Severity::Warn);
28/// env.labels.insert("reason".into(), "timeout".into());
29/// observer().emit_envelope(env);
30/// ```
31#[must_use]
32pub fn self_event(full_name: &str, tier: Tier, sev: Severity) -> ObsEnvelope {
33    ObsEnvelope {
34        full_name: full_name.to_string(),
35        tier: EnumValue::Known(tier),
36        sev: EnumValue::Known(sev),
37        sampling_reason: EnumValue::Known(SamplingReason::SAMPLING_REASON_RUNTIME),
38        ts_ns: now_ns(),
39        ..Default::default()
40    }
41}
42
43/// Wall-clock timestamp in nanoseconds since the Unix epoch, saturated
44/// at `u64::MAX`.
45#[must_use]
46pub fn now_ns() -> u64 {
47    std::time::SystemTime::now()
48        .duration_since(std::time::UNIX_EPOCH)
49        .map(|d| u64::try_from(d.as_nanos()).unwrap_or(u64::MAX))
50        .unwrap_or(0)
51}
52
53#[cfg(test)]
54mod tests {
55    use buffa::EnumValue;
56
57    use super::*;
58
59    #[test]
60    fn test_should_populate_full_name_tier_sev() {
61        let env = self_event("obs.runtime.v1.ObsTest", Tier::Log, Severity::Info);
62        assert_eq!(env.full_name, "obs.runtime.v1.ObsTest");
63        assert!(matches!(env.tier, EnumValue::Known(Tier::TIER_LOG)));
64        assert!(matches!(env.sev, EnumValue::Known(Severity::SEVERITY_INFO)));
65    }
66
67    #[test]
68    fn test_should_set_sampling_reason_runtime() {
69        let env = self_event("obs.runtime.v1.ObsTest", Tier::Metric, Severity::Warn);
70        assert!(matches!(
71            env.sampling_reason,
72            EnumValue::Known(SamplingReason::SAMPLING_REASON_RUNTIME)
73        ));
74    }
75
76    #[test]
77    fn test_should_populate_ts_ns() {
78        let before = now_ns();
79        let env = self_event("obs.runtime.v1.ObsTest", Tier::Log, Severity::Info);
80        let after = now_ns();
81        assert!(env.ts_ns >= before);
82        assert!(env.ts_ns <= after);
83    }
84}