obs_core/emit.rs
1//! `Emit` blanket trait — provides `.emit()` / `.emit_at(sev)` on
2//! every type that implements [`EventSchema`].
3//!
4//! Spec 61 § 2.4. The macro path emits straight calls to these
5//! methods. Hand-rolled tests can call them directly.
6
7use obs_proto::obs::v1::Severity;
8
9use crate::{
10 callsite::ObsCallsite,
11 envelope::{EventSchema, build_envelope_at},
12 observer::{enter_emit_envelope, observer},
13};
14
15/// Blanket trait giving every `EventSchema` an `.emit()` /
16/// `.emit_at(sev)` shortcut.
17pub trait Emit: EventSchema + Sized {
18 /// Emit at the schema-declared default severity.
19 fn emit(self) {
20 emit_inner::<Self>(&self, Self::DEFAULT_SEV)
21 }
22
23 /// Emit at the supplied severity (escalate or demote — spec 13 § 1.1).
24 fn emit_at(self, sev: Severity) {
25 emit_inner::<Self>(&self, sev)
26 }
27}
28
29impl<E: EventSchema + Sized> Emit for E {}
30
31/// Internal helper used by the blanket `Emit` impl when no caller-side
32/// `static __CALLSITE` is available.
33///
34/// **Hot-path emit sites must NOT route through this function.** The
35/// `obs::emit!` macro and the `<Builder>::emit()` setter both inline a
36/// `static __CALLSITE: ObsCallsite` so the atomic-Interest cache can
37/// short-circuit (spec 11 § 2.1). The blanket helper exists for tests
38/// and one-shot constructions where the per-emit callsite cost is
39/// dominated by the test's own work.
40fn emit_inner<E: EventSchema>(event: &E, sev: Severity) {
41 // Stack callsite — no Interest cache benefit, but no allocation
42 // either. The hot path goes through `emit_with_callsite` with a
43 // `static`.
44 let callsite = ObsCallsite::new(
45 E::FULL_NAME,
46 E::DEFAULT_SEV,
47 module_path!(),
48 file!(),
49 line!(),
50 );
51 emit_one::<E>(&callsite, event, sev);
52}
53
54/// Emit through a caller-supplied **static** callsite. Macro-emitted
55/// code calls this so the atomic-Interest cache populates and stays
56/// alive across emits. Spec 11 § 2.1.
57#[doc(hidden)]
58#[inline]
59pub fn emit_with_callsite<E: EventSchema>(
60 callsite: &'static ObsCallsite,
61 event: &E,
62 sev: Severity,
63) {
64 emit_one::<E>(callsite, event, sev)
65}
66
67/// Shared implementation: probe the cache, optionally consult the
68/// observer, project, dispatch.
69#[inline]
70fn emit_one<E: EventSchema>(callsite: &ObsCallsite, event: &E, sev: Severity) {
71 use crate::callsite::{EnabledOutcome, Interest};
72 let o = observer();
73 let cur_gen = o.generation();
74 let outcome = callsite.enabled(cur_gen);
75 let permitted = match outcome {
76 EnabledOutcome::AlwaysOn => true,
77 EnabledOutcome::Off => false,
78 EnabledOutcome::SometimesOn => o.enabled(callsite),
79 EnabledOutcome::ReProbe => {
80 let allowed = o.enabled(callsite);
81 callsite.cache(
82 if allowed {
83 Interest::Always
84 } else {
85 Interest::Never
86 },
87 cur_gen,
88 );
89 allowed
90 }
91 };
92 if !permitted {
93 return;
94 }
95 let mut env = build_envelope_at::<E>(callsite, event, sev);
96 event.project(&mut env);
97 enter_emit_envelope(&o, env);
98}