Skip to main content

obs_core/
lib.rs

1#![forbid(unsafe_code)]
2#![warn(rust_2024_compatibility, missing_docs, missing_debug_implementations)]
3// Tests routinely use `.unwrap()` for clarity; production code uses `?`.
4#![cfg_attr(
5    test,
6    allow(
7        clippy::unwrap_used,
8        clippy::expect_used,
9        clippy::panic,
10        clippy::indexing_slicing
11    )
12)]
13
14//! Runtime engine for the obs SDK — the spine that user code emits into.
15//!
16//! Phase-3 surface (specs/91-impl-plan.md tasks 3.1–3.15):
17//!
18//! - [`callsite`] — `ObsCallsite`, atomic-`Interest` cache (spec 11 § 2).
19//! - [`observer`][mod@observer] — three-tier resolution + per-tier worker pool + `StandardObserver`
20//!   (spec 11 §§ 3, 4 + 6.4).
21//! - [`registry`] — schema registry + `ScrubbedEnvelope` (spec 14).
22//! - [`envelope`] — envelope builder + projection helpers (spec 11 § 5).
23//! - [`scope`] — `obs::scope!` / `obs::context!` runtime support (spec 13 §§ 2, 3, 6).
24//! - [`instrumented`] — `Instrumented<F>` future adapter and `Instrument` / `WithObserver` traits
25//!   (spec 13 § 3).
26//! - [`sampling`] — head sampler (spec 13 § 6).
27//! - [`filter`] — EnvFilter-shaped `obs::Filter` (spec 13 § 7).
28//! - [`audit_spool`] — AUDIT-tier disk spool (spec 11 § 6.4).
29//! - [`panic_hook`] — `install_panic_hook` (spec 11 § 6.1).
30//! - [`span_trace`] — `obs::SpanTrace` (spec 13 § 9).
31//! - [`sink`] — sink trait + writers (spec 20).
32
33pub mod audit_spool;
34// NB: previously `aux` — renamed because `aux` is a reserved Windows
35// filename (`aux.rs` silently omitted from published tarballs on
36// win32; flagged by `cargo publish`). The `aux` module path is still
37// re-exported below for backwards compatibility.
38pub mod callsite;
39pub mod codegen_helpers;
40pub mod config;
41pub mod config_watcher;
42pub mod emit;
43pub mod envelope;
44pub mod filter;
45pub mod forensic;
46pub mod instrumented;
47pub mod metric;
48pub mod observer;
49pub mod panic_hook;
50pub mod propagator;
51pub mod registry;
52pub mod resource;
53pub mod sampling;
54pub mod scope;
55mod self_event;
56pub(crate) mod self_events;
57pub use self_event::{now_ns, self_event};
58/// Public re-exports for self-event helpers consumed by sinks/middleware
59/// that emit them on behalf of the runtime (e.g. OTLP trace sink emits
60/// `ObsSpanPairOrphaned` after pair_timeout). Spec 93 P1-2 + P1-7.
61pub mod self_events_public {
62    pub use crate::self_events::{
63        emit_callsite_hash_collision_pub as emit_callsite_hash_collision,
64        emit_label_cardinality_high_pub as emit_label_cardinality_high,
65        emit_oversized_label_dropped_pub as emit_label_oversized,
66        emit_span_pair_orphaned_pub as emit_span_pair_orphaned,
67    };
68}
69pub mod sink;
70pub mod span_trace;
71#[cfg(feature = "test")]
72pub mod test;
73pub mod wire;
74
75#[doc(hidden)]
76#[cfg(feature = "test")]
77pub mod __macro_deps {
78    pub use serde_json;
79}
80
81/// Cap an external string at `max_bytes` (UTF-8-safe boundary), append
82/// the `…<truncated:N>` suffix when the input was clipped, and emit
83/// one `ObsLabelOversized` self-event for telemetry. Returns the
84/// (possibly truncated) string. Spec 95 § 3.10 / P2-AH.
85///
86/// Boundary callers (HTTP middleware, bridge field visitor) should
87/// run this on every untrusted string before it lands in
88/// `env.labels` or the typed payload.
89#[must_use]
90pub fn cap_external_string(field: &'static str, raw: String, max_bytes: u16) -> String {
91    let max = max_bytes as usize;
92    if raw.len() <= max {
93        return raw;
94    }
95    let original_size = raw.len() as u64;
96    // Find the largest UTF-8-safe truncation point ≤ max.
97    let mut end = max;
98    while end > 0 && !raw.is_char_boundary(end) {
99        end -= 1;
100    }
101    let mut out = String::with_capacity(end + 24);
102    out.push_str(&raw[..end]);
103    out.push_str(&format!("…<truncated:{}>", original_size));
104    self_events::emit_oversized_label_dropped_pub(field, original_size, out.len() as u64);
105    out
106}
107
108pub use callsite::ObsCallsite;
109/// Back-compat alias: the module used to be called `aux`. Kept as a
110/// re-export so downstream `use obs_core::aux::…` paths keep working.
111#[doc(hidden)]
112pub use codegen_helpers as aux;
113pub use codegen_helpers::{BuildableTo, EnumCount, FieldCapture, SpanCtx, SpanFrame};
114pub use config::{EventsConfig, SamplingConfig};
115pub use config_watcher::{ConfigWatcher, DEFAULT_DEBOUNCE};
116pub use emit::Emit;
117pub use envelope::{Envelope, EventSchema, FieldMeta, FieldRole};
118pub use filter::Filter;
119pub use instrumented::{Instrument, Instrumented, WithObserver};
120pub use metric::{MetricEmitter, NoopMetricEmitter};
121pub use obs_proto::{
122    ENVELOPE_FORMAT_VER,
123    obs::v1::{
124        Cardinality, Classification, FieldKind, MetricKind, ObsBatch, ObsEnvelope, SamplingReason,
125        Severity, Tier,
126    },
127};
128pub use observer::{
129    BuildError, InMemoryHandle, InMemoryObserver, NoopObserver, Observer, StandardObserver,
130    StandardObserverBuilder, ThreadObserverGuard, WeakObserver, WorkerCounters, install_observer,
131    install_observer_arc, observer, observer_weak, with_observer_task, with_observer_task_sync,
132    with_observer_thread_local, with_test_observer,
133};
134pub use panic_hook::install_panic_hook;
135pub use propagator::{
136    ObsTraceCtx, W3cPropagator, extract_w3c, fresh_span_id, fresh_trace_id, inject_w3c,
137    status_class,
138};
139pub use registry::{
140    ArrowEventSchema, ArrowField, ArrowLeafType, ArrowSchemaModel, ArrowStructBuilder,
141    CallsiteRecord, CallsiteSource, DecodeError, ENVELOPE_COLUMNS, EVENT_SCHEMAS,
142    EventSchemaErased, ObsCallsiteRegistry, OtelAttributeView, OtlpValue, SchemaRegistry,
143    ScrubError, ScrubbedEnvelope, callsite_id, render_payload_json, scrub_payload,
144};
145pub use resource::ResourceAttrs;
146pub use sampling::{SamplingDecision, decide as sample_decide};
147pub use scope::{ScopeField, ScopeFrame, ScopeFrameBuilder, ScopeGuard, ScopeKind};
148pub use sink::{
149    FanOutSink, FormatterStyle, InMemorySink, LevelSplitWriter, MakeWriter, NdjsonFileSink,
150    NonBlockingWriter, NoopSink, RollingFileWriter, RollingFileWriterBuilder, RollingPolicy, Sink,
151    StderrWriter, StdoutSink, StdoutWriter, TeeWriter, WorkerGuard,
152};
153pub use span_trace::SpanTrace;
154
155/// Re-exports for code generated by `obs-macros::derive(Event)` and
156/// `obs-build`. End users should not depend on these directly.
157#[doc(hidden)]
158pub mod __private {
159    pub use std::sync::OnceLock;
160
161    pub use buffa;
162    pub use bytes::BytesMut;
163    pub use linkme;
164    /// Enum re-exports so macros can reach these through the
165    /// `__private` namespace without forcing user code to depend on
166    /// `obs-proto` directly. The `Proto*` aliases survive as back-compat
167    /// symbols; after Phase 3b (obs-types retirement) they point at
168    /// the same types the short-name re-exports do.
169    pub use obs_proto::obs::v1::{
170        Cardinality, Classification, FieldKind, MetricKind, SamplingReason,
171        SamplingReason as ProtoSamplingReason, Severity, Severity as ProtoSeverity, Tier,
172        Tier as ProtoTier,
173    };
174    pub use obs_proto::{__private::*, UnknownVariant};
175    pub use secrecy;
176    pub use serde_json;
177
178    pub use crate::{
179        callsite::ObsCallsite,
180        codegen_helpers::{BuildableTo, EnumCount, FieldCapture, SpanCtx, SpanFrame},
181        forensic::{ForensicLimiter, ensure_limiter, try_acquire_forensic},
182        registry::{EVENT_SCHEMAS, EventSchemaErased, Sealed},
183        scope::{ScopeField, ScopeGuard, ScopeKind},
184        wire::BuffaEncodeField,
185    };
186
187    /// L011 helper: const-time test that a string starts with a known
188    /// prefix.
189    #[must_use]
190    #[allow(clippy::indexing_slicing)]
191    pub const fn starts_with_obs(name: &str, prefix: &str) -> bool {
192        let n = name.as_bytes();
193        let p = prefix.as_bytes();
194        if n.len() < p.len() {
195            return false;
196        }
197        let mut i = 0;
198        while i < p.len() {
199            if n[i] != p[i] {
200                return false;
201            }
202            i += 1;
203        }
204        true
205    }
206}