Skip to main content

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_types::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}