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}