diff --git a/crates/fetch_winhttp_impl/src/telemetry.rs b/crates/fetch_winhttp_impl/src/telemetry.rs index 73a93899a..3ff596209 100644 --- a/crates/fetch_winhttp_impl/src/telemetry.rs +++ b/crates/fetch_winhttp_impl/src/telemetry.rs @@ -185,8 +185,7 @@ mod tests { ExpectedEvent::new("fetch.winhttp.session.initialization.failure", Severity::Error) .body("WinHTTP transport initialization failed") .dimension("winhttp.error_code", 1234_u32) - .dimension("winhttp.operation", "assured_non_blocking_callbacks") - .log(), + .dimension("winhttp.operation", "assured_non_blocking_callbacks"), ); assert_eq!( events[1], @@ -196,7 +195,6 @@ mod tests { events[2], ExpectedEvent::new("fetch.winhttp.request.error", Severity::Error) .body("WinHTTP transport request failed") - .log() .metric(), ); assert!(events[1].dimensions().is_empty()); @@ -216,7 +214,6 @@ mod tests { .body("WinHTTP transport request failed") .dimension("winhttp.connect.duration", 0.25_f64) .dimension("winhttp.connection.fresh", true) - .log() .metric(), ); diff --git a/crates/fetch_winhttp_impl/src/transport.rs b/crates/fetch_winhttp_impl/src/transport.rs index 97d8050ef..5f43d2ee3 100644 --- a/crates/fetch_winhttp_impl/src/transport.rs +++ b/crates/fetch_winhttp_impl/src/transport.rs @@ -483,7 +483,6 @@ mod tests { .body("WinHTTP transport request failed") .dimension("winhttp.connect.duration", 0.25_f64) .dimension("winhttp.connection.fresh", true) - .log() .metric(), ] ); diff --git a/crates/observed/benches/observed_benchmarks.rs b/crates/observed/benches/observed_benchmarks.rs index 1562c4f3d..34e47b7ba 100644 --- a/crates/observed/benches/observed_benchmarks.rs +++ b/crates/observed/benches/observed_benchmarks.rs @@ -7,7 +7,7 @@ //! - Full emit pipeline (event -> log record via `OTel` provider) //! - Enrichment resolution (context lookup + Vec building) //! - Metric dimension building -//! - Context operations (enrich) +//! - Sink operations (construction, emit through a no-op sink) //! //! Run with: //! ```sh @@ -277,11 +277,11 @@ fn entrypoint(c: &mut Criterion) { group.finish(); } - // --- Context operation benchmarks --- + // --- Sink operation benchmarks --- { - let mut group = c.benchmark_group("emit_context"); - bench_attach_emitter(&mut group, &allocs, &time); - bench_emit_event_direct(&mut group, &allocs, &time); + let mut group = c.benchmark_group("sink_operations"); + bench_construct_processor_free_sink(&mut group, &allocs, &time); + bench_emit_to_noop_sink(&mut group, &allocs, &time); group.finish(); } @@ -468,11 +468,15 @@ fn bench_enrich_push_pop(group: &mut BenchmarkGroup<'_, WallTime>, allocs: &allo } // --------------------------------------------------------------------------- -// Context operation benchmarks +// Sink operation benchmarks // --------------------------------------------------------------------------- -fn bench_attach_emitter(group: &mut BenchmarkGroup<'_, WallTime>, allocs: &alloc_tracker::Session, time: &all_the_time::Session) { - const ID: &str = "attach_emitter"; +fn bench_construct_processor_free_sink( + group: &mut BenchmarkGroup<'_, WallTime>, + allocs: &alloc_tracker::Session, + time: &all_the_time::Session, +) { + const ID: &str = "construct_processor_free_sink"; const EMPTY_PROCESSORS: Vec> = Vec::new(); bench_with_tracking(group, allocs, time, ID, || { @@ -481,11 +485,10 @@ fn bench_attach_emitter(group: &mut BenchmarkGroup<'_, WallTime>, allocs: &alloc }); } -fn bench_emit_event_direct(group: &mut BenchmarkGroup<'_, WallTime>, allocs: &alloc_tracker::Session, time: &all_the_time::Session) { - const ID: &str = "emit_event_no_emitter"; +fn bench_emit_to_noop_sink(group: &mut BenchmarkGroup<'_, WallTime>, allocs: &alloc_tracker::Session, time: &all_the_time::Session) { + const ID: &str = "emit_to_noop_sink"; - // Benchmark the emit path when no sink is registered - measures the - // cost of the context lookup + early return. + // Benchmark the emit path through an explicit no-op sink. let noop = Sink::noop(); bench_with_tracking(group, allocs, time, ID, || { observed::emit!(&noop, SimpleLogEvent { status: 200, retries: 0 }); diff --git a/crates/observed/examples/layered_app/token_issuer.rs b/crates/observed/examples/layered_app/token_issuer.rs index 0cb3cf403..fe006e261 100644 --- a/crates/observed/examples/layered_app/token_issuer.rs +++ b/crates/observed/examples/layered_app/token_issuer.rs @@ -14,7 +14,7 @@ use observed::{Enrichment, Sink, emit, event}; use crate::taxonomy::MicrosoftEnterpriseDataTaxonomy; -const DC: DataClass = DataClass::new("microsoft", "PublicNonPersonalData"); +const DC: DataClass = MicrosoftEnterpriseDataTaxonomy::PublicNonPersonalData.data_class(); // --------------------------------------------------------------------------- // Classified newtypes @@ -69,16 +69,11 @@ struct TokenValidated { /// A failed token validation attempt. /// -/// Records each failure as an up-down counter metric (increments on failure). +/// Records each failure as a monotonic counter metric. #[event("token.validation_failed")] #[warning("Token validation failed")] -#[updown_counter(failure_count, name = "token.validation.failures")] +#[counter(name = "token.validation.failures")] struct TokenValidationFailed { - /// Failure count - recorded as an up-down counter metric. - // TODO: replace #[unredacted] with classified type once metric fields support non-numeric Values - #[unredacted] - failure_count: i64, - /// Error code identifying the failure reason. #[data_class(DC)] error_code: i64, @@ -129,7 +124,6 @@ pub(crate) fn validate_token(sink: &Sink, valid: bool) { emit!( sink, TokenValidationFailed { - failure_count: 1, error_code: 401, // expired } ); diff --git a/crates/observed/examples/support/otel.rs b/crates/observed/examples/support/otel.rs index 1fec24209..2fc60eeb8 100644 --- a/crates/observed/examples/support/otel.rs +++ b/crates/observed/examples/support/otel.rs @@ -31,9 +31,6 @@ const CODE_FILE_PATH: &str = "code.file.path"; /// `OTel` attribute key for the source line a call site came from. const CODE_LINE_NUMBER: &str = "code.line.number"; -/// `OTel` attribute key for the crate a call site came from. -const CODE_NAMESPACE: &str = "code.namespace"; - /// Converts a [`Text`] into an `OTel` string, preserving the borrowed-versus- /// shared distinction so neither representation copies. /// @@ -133,6 +130,11 @@ pub(crate) fn populate_log_record(record: &mut impl LogRecord, event: &EventView ControlFlow::Continue(()) }); + // File and line use stable OpenTelemetry code attributes. The emitting + // crate is deliberately not exported: `code.namespace` is deprecated with + // no standalone replacement, and its successor `code.function.name` needs a + // fully qualified function name that the event model does not capture. + // https://opentelemetry.io/docs/specs/semconv/registry/attributes/code/ if let Some(file) = event.source_file() { record.add_attribute(opentelemetry::Key::from_static_str(CODE_FILE_PATH), AnyValue::String(file.into())); } @@ -142,12 +144,5 @@ pub(crate) fn populate_log_record(record: &mut impl LogRecord, event: &EventView AnyValue::Int(i64::from(line)), ); } - if let Some(crate_name) = event.source_crate() { - record.add_attribute( - opentelemetry::Key::from_static_str(CODE_NAMESPACE), - AnyValue::String(crate_name.into()), - ); - } - true } diff --git a/crates/observed/src/enrichment/slot.rs b/crates/observed/src/enrichment/slot.rs index 4b5de36c7..1ce14c0cf 100644 --- a/crates/observed/src/enrichment/slot.rs +++ b/crates/observed/src/enrichment/slot.rs @@ -68,7 +68,15 @@ impl Slot { } /// Pushes entries onto the enrichment chain and returns a guard. + /// + /// An empty layer installs no chain node and records no restoration, so the + /// storage boundary holds for callers that reach it without passing through + /// [`Sink::push_enrichment`](crate::Sink). pub(crate) fn push(&self, entries: Arc<[EnrichmentEntry]>) -> Guard { + if entries.is_empty() { + return Guard::empty(); + } + let prev = { let cell = self.0.get_or(|| RefCell::new(None)); let next = Arc::new(EnrichmentNode { @@ -100,6 +108,10 @@ pub(crate) struct Guard { } impl Guard { + pub(crate) fn empty() -> Self { + Self { slots: SmallVec::new() } + } + /// Flattens several guards into one. Each input guard is consumed and its /// `Drop` is disarmed (`mem::take` empties its slots), so the merged guard /// owns the restoration responsibility. @@ -170,21 +182,30 @@ impl EnrichmentTransfer { /// Pushes an additional enrichment node onto every captured chain. /// Broadcast within the transfer's known slots; transfers with no /// captured slots are left unchanged. + /// + /// Emptiness is tested while the entries are still an owned `Vec`, so an + /// empty layer skips the shared-slice allocation as well as the chain + /// nodes. [`push_entries`](Self::push_entries) relies on this and on the + /// same check in [`push_for`](Self::push_for) for its non-empty input. pub(crate) fn push(&mut self, additional_enrichment: impl Enrichment) { - self.push_entries(&Arc::from(additional_enrichment.into_entries())); + let entries = additional_enrichment.into_entries(); + if entries.is_empty() { + return; + } + + self.push_entries(&Arc::from(entries)); } /// Same as [`push`](Self::push), but marks every entry as targeted at /// `target`, so only that sink observes it. Mirrors the `with_target` /// mapping that `EnrichFutureExt::enrich_for` applies. pub(crate) fn push_for(&mut self, target: SinkId, additional_enrichment: impl Enrichment) { - self.push_entries( - &additional_enrichment - .into_entries() - .into_iter() - .map(|entry| entry.with_target(target)) - .collect(), - ); + let entries = additional_enrichment.into_entries(); + if entries.is_empty() { + return; + } + + self.push_entries(&entries.into_iter().map(|entry| entry.with_target(target)).collect()); } /// Layers `entries` onto every captured chain as a single new node. @@ -221,6 +242,14 @@ mod coverage_tests { } } + struct EmptyEnrichment; + + impl Enrichment for EmptyEnrichment { + fn into_entries(self) -> Vec { + Vec::new() + } + } + /// Flattens a chain into its entries, innermost node first. fn chain_entries(head: &OptEnrichmentNode) -> Vec { let mut entries = Vec::new(); @@ -255,6 +284,20 @@ mod coverage_tests { assert!(slot.current().is_none(), "dropping the guard must pop the pushed node"); } + #[test] + fn empty_push_returns_noop_guard_without_touching_the_slot() { + let slot = Slot::new(); + let empty_guard = slot.push(Arc::from(Vec::new())); + assert!(slot.current().is_none()); + + let real_guard = slot.push(Arc::from(vec![EnrichmentEntry::unclassified("k", 1_i64)])); + let merged = Guard::merge([empty_guard, real_guard]); + assert!(slot.current().is_some()); + + drop(merged); + assert!(slot.current().is_none()); + } + #[test] fn transfer_captures_a_slot_and_layers_further_enrichment_onto_it() { let slot = Slot::new(); @@ -281,6 +324,22 @@ mod coverage_tests { assert!(slot.current().is_none()); } + #[test] + fn transfer_ignores_empty_enrichment_layers() { + let slot = Slot::new(); + let mut transfer = EnrichmentTransfer::default(); + transfer.add_slot(&slot); + + transfer.push(EmptyEnrichment); + transfer.push_for(SinkId::new("target"), EmptyEnrichment); + + let guard = transfer.apply(); + assert!(slot.current().is_none()); + + drop(guard); + assert!(slot.current().is_none()); + } + #[test] fn debug_impls_and_relocate() { let mut slot = Slot::new(); diff --git a/crates/observed/src/sink/core.rs b/crates/observed/src/sink/core.rs index 6af44fcd6..71ae56f44 100644 --- a/crates/observed/src/sink/core.rs +++ b/crates/observed/src/sink/core.rs @@ -283,9 +283,16 @@ impl Sink { /// /// This is the entry point used by the `.enrich(&sink, ...)` API in /// [`EnrichFutureExt`](crate::enrichment::EnrichFutureExt) and - /// [`EnrichFnExt`](crate::enrichment::EnrichFnExt). Composites with zero children - /// return a no-op guard. + /// [`EnrichFnExt`](crate::enrichment::EnrichFnExt). + /// + /// An empty layer returns early, which spares composite fan-out and every + /// downstream slot write. The entry slice is built by the caller, so this + /// does not avoid the slice's own allocation. pub(crate) fn push_enrichment(&self, entries: Arc<[EnrichmentEntry]>) -> Guard { + if entries.is_empty() { + return Guard::empty(); + } + match &*self.inner { SinkInner::Single(state) => state.enrichment.push(entries), SinkInner::Composite { children } => Guard::merge(children.iter().map(|c| c.enrichment.push(Arc::clone(&entries)))), diff --git a/crates/observed/tests/otel_log_record.rs b/crates/observed/tests/otel_log_record.rs index ffac7029c..34f6d2738 100644 --- a/crates/observed/tests/otel_log_record.rs +++ b/crates/observed/tests/otel_log_record.rs @@ -257,10 +257,9 @@ fn metric_only_event_exports_no_log_record() { } /// The exported record name comes from the log signal rather than the event -/// name, and the emitting crate is recorded as `code.namespace`. The two names -/// differ here, so a mapping that fell back to the event name is caught. +/// name, and source attributes omit deprecated crate namespace metadata. #[test] -fn maps_log_name_and_source_crate() { +fn maps_log_name_without_deprecated_source_crate_attribute() { let (sink, provider, exporter) = otel_emitter(); emit!(sink, CacheLookup { hits: 3 }); @@ -272,10 +271,7 @@ fn maps_log_name_and_source_crate() { assert_eq!(record.event_name(), Some("cache.lookup.completed")); let attrs: Vec<_> = record.attributes_iter().map(|(k, v)| (k.clone(), v.clone())).collect(); - assert!(matches!( - find_attr(&attrs, "code.namespace"), - Some(AnyValue::String(s)) if s.as_ref() == env!("CARGO_PKG_NAME") - )); + assert!(find_attr(&attrs, "code.namespace").is_none()); } #[test] diff --git a/crates/observed_testing/src/lib.rs b/crates/observed_testing/src/lib.rs index 8ea9ffd95..837556c9c 100644 --- a/crates/observed_testing/src/lib.rs +++ b/crates/observed_testing/src/lib.rs @@ -25,9 +25,7 @@ //! emit!(sink, MyEvent { count: 42 }); //! //! let event = processor.single_event(); -//! let expected = ExpectedEvent::new("my.event", Severity::Info) -//! .dimension("count", 42i64) -//! .log(); +//! let expected = ExpectedEvent::new("my.event", Severity::Info).dimension("count", 42i64); //! assert_eq!(event, expected); //! ``` diff --git a/crates/observed_testing/src/mock_processor.rs b/crates/observed_testing/src/mock_processor.rs index 6d547f9f0..1ffd06623 100644 --- a/crates/observed_testing/src/mock_processor.rs +++ b/crates/observed_testing/src/mock_processor.rs @@ -209,9 +209,7 @@ impl CapturedEvent { /// # struct TestEvent { #[unredacted] value: i64 } /// emit!(sink, TestEvent { value: 1 }); /// -/// let expected = ExpectedEvent::new("test.event", Severity::Info) -/// .dimension("value", 1i64) -/// .log(); +/// let expected = ExpectedEvent::new("test.event", Severity::Info).dimension("value", 1i64); /// assert_eq!(processor.single_event(), expected); /// ``` #[derive(Clone)] @@ -364,14 +362,24 @@ fn passthrough_redaction_engine() -> data_privacy::RedactionEngine { } // --------------------------------------------------------------------------- -// ExpectedEvent - partial-match builder for test assertions +// ExpectedEvent - exact-match builder for test assertions // --------------------------------------------------------------------------- -/// A partial-match event specification for test assertions. +/// An exact-match event specification for test assertions. /// -/// Construct with [`ExpectedEvent::new`] and chain builder methods for the fields -/// you want to verify. Only specified fields are checked - unspecified fields are -/// ignored during comparison. +/// Construct with [`ExpectedEvent::new`] and chain builder methods for the +/// fields and signals the event must carry. Omitted body and dimensions are +/// expected to be absent, the metric signal is expected to be absent, and the +/// disabled flag is expected to be unset. +/// +/// The log signal follows the severity: [`new`](Self::new) expects one and +/// [`without_severity`](Self::without_severity) expects none, because a +/// captured event carries a severity exactly when it carries a log signal. +/// +/// Exactness covers what this type models - name, severity, body, dimensions, +/// the metric signal and the disabled flag. Source location and metric +/// instrument metadata are outside the comparison; assert those through the +/// [`CapturedEvent`] accessors. /// /// # Example /// @@ -382,7 +390,6 @@ fn passthrough_redaction_engine() -> data_privacy::RedactionEngine { /// let expected = ExpectedEvent::new("http.request", Severity::Info) /// .body("Request handled") /// .dimension("status", 200i64) -/// .log() /// .metric(); /// ``` #[derive(Debug)] @@ -391,7 +398,6 @@ pub struct ExpectedEvent { severity: Option, body: Option, sorted_dimensions: Vec<(String, Value)>, - expect_log: bool, expect_metric: bool, expect_disabled: bool, } @@ -399,9 +405,11 @@ pub struct ExpectedEvent { impl ExpectedEvent { /// Creates a new expected **log** event with the given name and severity. /// - /// All other fields are unchecked by default. Use builder methods to add - /// constraints. For an event with no log signal - a metric-only or - /// signal-less event - use [`without_severity`](Self::without_severity). + /// Omitted body and dimensions are expected to be absent, the metric signal + /// and the disabled flag are expected to be unset, and the log signal is + /// expected because a severity is. For an event with no log signal - a + /// metric-only or signal-less event - use + /// [`without_severity`](Self::without_severity). #[must_use] pub fn new(name: impl Into, severity: Severity) -> Self { Self { @@ -409,7 +417,6 @@ impl ExpectedEvent { severity: Some(severity), body: None, sorted_dimensions: Vec::new(), - expect_log: false, expect_metric: false, expect_disabled: false, } @@ -426,7 +433,6 @@ impl ExpectedEvent { severity: None, body: None, sorted_dimensions: Vec::new(), - expect_log: false, expect_metric: false, expect_disabled: false, } @@ -447,13 +453,6 @@ impl ExpectedEvent { self } - /// Expects the event to have the LOG signal. - #[must_use] - pub fn log(mut self) -> Self { - self.expect_log = true; - self - } - /// Expects the event to have the METRIC signal. #[must_use] pub fn metric(mut self) -> Self { @@ -461,7 +460,7 @@ impl ExpectedEvent { self } - /// Expects the event to be disabled by default. + /// Expects the event to carry the disabled flag. #[must_use] pub fn disabled(mut self) -> Self { self.expect_disabled = true; @@ -475,7 +474,6 @@ impl PartialEq for CapturedEvent { && self.severity == other.severity && self.body.as_deref() == other.body.as_deref() && self.sorted_dimensions == other.sorted_dimensions - && other.expect_log == self.description.is_log() && other.expect_metric == self.description.contains_metrics() && other.expect_disabled == self.description.is_disabled() } @@ -690,12 +688,16 @@ impl PartialEq for ExpectedEventDescription { #[cfg_attr(coverage_nightly, coverage(off))] #[cfg(test)] mod coverage_tests { - use observed::{Event, emit}; + use observed::{Event, SinkId, emit, event}; use super::*; use crate::events::ProbeEvent; use crate::test_emitter; + #[event("fieldless.log")] + #[info] + struct FieldlessLog; + #[test] fn mock_processor_default_debug_and_flush() { let processor = MockProcessor::default(); @@ -705,13 +707,13 @@ mod coverage_tests { #[test] fn expected_types_equal_actuals_in_reverse() { - let (sink, processor) = test_emitter(observed::SinkId::new("rev")); + let (sink, processor) = test_emitter(SinkId::new("rev")); emit!(sink, ProbeEvent::new(42)); let captured = processor.single_event(); // `ExpectedEvent == CapturedEvent` (reverse direction) agrees with the // forward direction it delegates to. - let expected_event = ExpectedEvent::new("test.probe", Severity::Info).dimension("value", 42i64).log(); + let expected_event = ExpectedEvent::new("test.probe", Severity::Info).dimension("value", 42i64); assert_eq!(expected_event == captured, captured == expected_event); // `ExpectedEnrichmentEntry == EnrichmentEntry` (reverse direction). @@ -723,4 +725,13 @@ mod coverage_tests { let expected_desc = ExpectedEventDescription::new("test.probe", Severity::Info).log(); assert_eq!(expected_desc, ProbeEvent::DESCRIPTION); } + + #[test] + fn expected_event_new_matches_fieldless_log() { + let (sink, processor) = test_emitter(SinkId::new("fieldless_log")); + + emit!(sink, FieldlessLog); + + assert_eq!(processor.single_event(), ExpectedEvent::new("fieldless.log", Severity::Info)); + } } diff --git a/crates/observed_testing/tests/basic_emission.rs b/crates/observed_testing/tests/basic_emission.rs index 0c6237145..abd198a97 100644 --- a/crates/observed_testing/tests/basic_emission.rs +++ b/crates/observed_testing/tests/basic_emission.rs @@ -38,8 +38,7 @@ fn log_event() { ExpectedEvent::new("app.warning", Severity::Warn) .body("Something went wrong") .dimension("code", "42") - .dimension("recoverable", "true") - .log(), + .dimension("recoverable", "true"), ); } @@ -80,7 +79,6 @@ fn log_and_metric_event() { .dimension("method", "GET") .dimension("retries", "3") .dimension("status", "200") - .log() .metric() ); } @@ -106,7 +104,7 @@ fn event_with_custom_field_name() { // The field `system_id` is renamed to `db.system` via #[dimension(log = "db.system")] assert_eq!( processor.single_event(), - ExpectedEvent::new("db.error", Severity::Error).dimension("db.system", "5").log(), + ExpectedEvent::new("db.error", Severity::Error).dimension("db.system", "5"), ); } @@ -119,7 +117,7 @@ fn emit_already_constructed_event() { assert_eq!( processor.single_event(), - ExpectedEvent::new("test.probe", Severity::Info).dimension("value", "204").log() + ExpectedEvent::new("test.probe", Severity::Info).dimension("value", "204") ); } @@ -133,10 +131,7 @@ fn emit_event_with_no_fields() { emit!(sink, Heartbeat); - assert_eq!( - processor.single_event(), - ExpectedEvent::new("internal.heartbeat", Severity::Trace).log(), - ); + assert_eq!(processor.single_event(), ExpectedEvent::new("internal.heartbeat", Severity::Trace)); } #[test] @@ -200,17 +195,13 @@ fn multiple_events_accumulate() { let events = processor.events(); assert_eq!(events.len(), 3); - assert_eq!( - events[0], - ExpectedEvent::new("test.probe", Severity::Info).dimension("value", "1").log() - ); + assert_eq!(events[0], ExpectedEvent::new("test.probe", Severity::Info).dimension("value", "1")); assert_eq!( events[1], ExpectedEvent::new("app.warning", Severity::Warn) .body("Something went wrong") .dimension("code", "1") .dimension("recoverable", "false") - .log() ); assert_eq!( events[2], @@ -218,7 +209,6 @@ fn multiple_events_accumulate() { .dimension("key_hash", "42") .dimension("lookup_ms", 0.5f64) .dimension("size_bytes", "1024") - .log() .metric() ); } @@ -269,7 +259,6 @@ fn dimensions_types() { .dimension("f64_field", 6.14f64) .dimension("bool_field", true) .dimension("string_field", "test") - .log() ); } @@ -302,8 +291,7 @@ fn borrowed_classified_fields() { ExpectedEvent::new("user.action", Severity::Info) .dimension("count", 3i64) .dimension("label", "click") - .dimension("name", "alice") - .log(), + .dimension("name", "alice"), ); } diff --git a/crates/observed_testing/tests/context_propagation.rs b/crates/observed_testing/tests/context_propagation.rs index 27d5a869e..24811375e 100644 --- a/crates/observed_testing/tests/context_propagation.rs +++ b/crates/observed_testing/tests/context_propagation.rs @@ -45,8 +45,7 @@ fn cross_thread_context_transfer() { processor.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("origin", "1") - .dimension("value", "99") - .log(), + .dimension("value", "99"), ); } @@ -64,8 +63,7 @@ fn context_transfer_does_not_affect_source_thread() { processor.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("origin", "42") - .dimension("value", "1") - .log(), + .dimension("value", "1"), ); } @@ -89,8 +87,7 @@ async fn async_enrichment_propagation() { processor.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("async_key", "7") - .dimension("value", "42") - .log(), + .dimension("value", "42"), ); } @@ -117,8 +114,7 @@ async fn async_enrichment_with_context_transfer() { processor.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("async_key", "99") - .dimension("value", "7") - .log(), + .dimension("value", "7"), ); } @@ -147,8 +143,7 @@ async fn async_enrichment_after_attach_stays_visible() { processor.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("async_key", "99") - .dimension("value", "7") - .log(), + .dimension("value", "7"), ); } @@ -181,14 +176,13 @@ async fn targeted_async_enrichment_after_attach_stays_visible() { // Only the addressed sink emits the targeted entry. assert_eq!( app_proc.single_event(), - ExpectedEvent::new("test.probe", Severity::Info).dimension("value", "7").log(), + ExpectedEvent::new("test.probe", Severity::Info).dimension("value", "7"), ); assert_eq!( audit_proc.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("async_key", "99") - .dimension("value", "7") - .log(), + .dimension("value", "7"), ); } @@ -237,13 +231,12 @@ async fn targeted_enrichment_carried_by_transfer_survives_generic_attach() { // have widened this entry onto `APP` as well. assert_eq!( app_proc.single_event(), - ExpectedEvent::new("test.probe", Severity::Info).dimension("value", "7").log(), + ExpectedEvent::new("test.probe", Severity::Info).dimension("value", "7"), ); assert_eq!( audit_proc.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("async_key", "99") - .dimension("value", "7") - .log(), + .dimension("value", "7"), ); } diff --git a/crates/observed_testing/tests/coverage_dyn_sink.rs b/crates/observed_testing/tests/coverage_dyn_sink.rs index 3af205b42..11826d0ac 100644 --- a/crates/observed_testing/tests/coverage_dyn_sink.rs +++ b/crates/observed_testing/tests/coverage_dyn_sink.rs @@ -296,8 +296,7 @@ fn dyn_event_body_bypasses_redaction_while_fields_do_not() { erasing.single_event(), ExpectedEvent::new("dyn.redaction", Severity::Info) .body("body-secret") - .dimension("detail", "") - .log(), + .dimension("detail", ""), ); } diff --git a/crates/observed_testing/tests/derive_enrichment.rs b/crates/observed_testing/tests/derive_enrichment.rs index a138dd0b5..9fdf9fe9a 100644 --- a/crates/observed_testing/tests/derive_enrichment.rs +++ b/crates/observed_testing/tests/derive_enrichment.rs @@ -302,8 +302,7 @@ fn hand_written_enrichment_is_accepted_by_enrich() { ExpectedEvent::new("test.probe", Severity::Info) .dimension("first", 0_i64) .dimension("second", 1_i64) - .dimension("value", "1") - .log(), + .dimension("value", "1"), ); } diff --git a/crates/observed_testing/tests/disabled_events.rs b/crates/observed_testing/tests/disabled_events.rs index 752d66bc0..97e07cc3c 100644 --- a/crates/observed_testing/tests/disabled_events.rs +++ b/crates/observed_testing/tests/disabled_events.rs @@ -34,7 +34,6 @@ fn disabled_event_captured_by_default_processor_with_flag() { processor.single_event(), ExpectedEvent::new("internal.trace_detail", Severity::Debug) .dimension("detail", "42") - .log() .disabled(), ); } @@ -49,10 +48,7 @@ fn disabled_event_filtered_by_log_and_metric_proc() { let events = processor.events(); assert_eq!(events.len(), 1); - assert_eq!( - events[0], - ExpectedEvent::new("test.probe", Severity::Info).dimension("value", "1").log() - ); + assert_eq!(events[0], ExpectedEvent::new("test.probe", Severity::Info).dimension("value", "1")); } #[test] diff --git a/crates/observed_testing/tests/enrichment.rs b/crates/observed_testing/tests/enrichment.rs index a1f789adf..41076e9a3 100644 --- a/crates/observed_testing/tests/enrichment.rs +++ b/crates/observed_testing/tests/enrichment.rs @@ -50,8 +50,7 @@ fn enrichment_appears_as_dimensions() { processor.single_event(), ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("tenant", "42") - .dimension("value", "1") - .log(), + .dimension("value", "1"), ); } @@ -83,15 +82,13 @@ fn enrichments_stack_and_unwind() { ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("request_id", "42") .dimension("service", "1") - .dimension("value", "10") - .log(), + .dimension("value", "10"), ); assert_eq!( events[1], ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("service", "1") - .dimension("value", "20") - .log(), + .dimension("value", "20"), ); } @@ -117,8 +114,7 @@ fn multiple_enrichment_entries_in_single_call() { .dimension("attempt", "1") .dimension("is_retry", "false") .dimension("request_id", "7") - .dimension("value", "1") - .log(), + .dimension("value", "1"), ); } @@ -153,16 +149,13 @@ fn targeted_enrichment_visible_to_specific_emitter() { lib_events[0], ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("lib.version", "1") - .dimension("value", "2") - .log(), + .dimension("value", "2"), ); // Targeted enrichment should NOT appear on app sink's events assert_eq!( app_events[0], - ExpectedEvent::new("test.probe", observed::Severity::Info) - .dimension("value", "1") - .log(), + ExpectedEvent::new("test.probe", observed::Severity::Info).dimension("value", "1"), ); } @@ -178,9 +171,7 @@ fn isolated_enrichment_excludes_global_context() { assert_eq!( processor.single_event(), - ExpectedEvent::new("test.probe", observed::Severity::Info) - .dimension("value", "1") - .log(), + ExpectedEvent::new("test.probe", observed::Severity::Info).dimension("value", "1"), ); } @@ -209,8 +200,7 @@ fn typed_enrichment_struct_adds_dimensions() { .dimension("request_id", "42") .dimension("attempt", "1") .dimension("is_retry", "false") - .dimension("value", "100") - .log(), + .dimension("value", "100"), ); } @@ -239,8 +229,7 @@ fn typed_enrichment_stacking() { .dimension("is_retry", "true") .dimension("request_id", "7") .dimension("tenant", "55") - .dimension("value", "200") - .log(), + .dimension("value", "200"), ); } @@ -280,8 +269,7 @@ fn enrichment_field_level_attributes() { .dimension("ctx.trace_id", "999") .dimension("logs_only_detail", "2") .dimension("metrics_only_tag", "1") - .dimension("value", "1") - .log(), + .dimension("value", "1"), ); } @@ -355,15 +343,13 @@ fn enrichment_preserved_when_future_polled() { events[0], ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("scope", "1") - .dimension("value", "1") - .log(), + .dimension("value", "1"), ); assert_eq!( events[1], ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("scope", "2") - .dimension("value", "2") - .log(), + .dimension("value", "2"), ); } @@ -392,15 +378,13 @@ fn composite_enrich_appears_on_every_child_record() { app_proc.single_event(), ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("tenant", "99") - .dimension("value", "7") - .log(), + .dimension("value", "7"), ); assert_eq!( audit_proc.single_event(), ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("tenant", "99") - .dimension("value", "7") - .log(), + .dimension("value", "7"), ); } @@ -448,32 +432,28 @@ fn composite_enrich_stacks_with_per_child_scope() { ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("request_id", "42") .dimension("service", "1") - .dimension("value", "100") - .log(), + .dimension("value", "100"), ); // audit[0]: in nested scope, but inner was pushed only on app — sees only outer. assert_eq!( audit_events[0], ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("service", "1") - .dimension("value", "200") - .log(), + .dimension("value", "200"), ); // app[1]: outer scope only — only outer. assert_eq!( app_events[1], ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("service", "1") - .dimension("value", "300") - .log(), + .dimension("value", "300"), ); // audit[1]: outer scope only — only outer. assert_eq!( audit_events[1], ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("service", "1") - .dimension("value", "300") - .log(), + .dimension("value", "300"), ); } @@ -503,17 +483,14 @@ fn composite_enrich_for_targets_one_child_only() { // AUDIT, so app's `visit_enrichments` filters it out. assert_eq!( app_proc.single_event(), - ExpectedEvent::new("test.probe", observed::Severity::Info) - .dimension("value", "1") - .log(), + ExpectedEvent::new("test.probe", observed::Severity::Info).dimension("value", "1"), ); // Audit's record: target matches its scope, so the entry is emitted. assert_eq!( audit_proc.single_event(), ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("audit_id", "7") - .dimension("value", "1") - .log(), + .dimension("value", "1"), ); } @@ -580,8 +557,7 @@ fn transfer_context_captures_every_leaf() { app_proc.single_event(), ExpectedEvent::new("test.probe", observed::Severity::Info) .dimension("tenant", "7") - .dimension("value", "3") - .log(), + .dimension("value", "3"), ); assert!(app.current_enrichments().is_empty()); } diff --git a/crates/observed_testing/tests/generics.rs b/crates/observed_testing/tests/generics.rs index 9f5a26fc9..b789d2e34 100644 --- a/crates/observed_testing/tests/generics.rs +++ b/crates/observed_testing/tests/generics.rs @@ -85,9 +85,7 @@ fn generic_event_with_unredacted_field_emits() { assert_eq!( processor.single_event(), - ExpectedEvent::new("generic.unredacted", Severity::Info) - .dimension("value", 42_i64) - .log(), + ExpectedEvent::new("generic.unredacted", Severity::Info).dimension("value", 42_i64), ); } @@ -104,9 +102,7 @@ fn generic_event_with_redacted_field_emits() { assert_eq!( processor.single_event(), - ExpectedEvent::new("generic.redacted", Severity::Info) - .dimension("value", "hello") - .log(), + ExpectedEvent::new("generic.redacted", Severity::Info).dimension("value", "hello"), ); } @@ -123,9 +119,7 @@ fn generic_event_with_data_class_field_emits() { assert_eq!( processor.single_event(), - ExpectedEvent::new("generic.classified", Severity::Info) - .dimension("value", "secret") - .log(), + ExpectedEvent::new("generic.classified", Severity::Info).dimension("value", "secret"), ); } @@ -144,16 +138,12 @@ fn generic_event_with_option_field_emits_both_arms() { let events = processor.events(); assert_eq!( events[0], - ExpectedEvent::new("generic.optional", Severity::Info) - .dimension("value", "present") - .log(), + ExpectedEvent::new("generic.optional", Severity::Info).dimension("value", "present"), ); // The default `#[if_none("n/a")]` placeholder fills the missing value. assert_eq!( events[1], - ExpectedEvent::new("generic.optional", Severity::Info) - .dimension("value", "n/a") - .log(), + ExpectedEvent::new("generic.optional", Severity::Info).dimension("value", "n/a"), ); } @@ -173,8 +163,7 @@ fn generic_event_with_two_parameters_emits() { processor.single_event(), ExpectedEvent::new("generic.mixed", Severity::Info) .dimension("raw", 7_i64) - .dimension("classified", "mixed") - .log(), + .dimension("classified", "mixed"), ); } @@ -195,8 +184,7 @@ fn generic_enrichments_reach_the_record() { ExpectedEvent::new("generic.unredacted", Severity::Info) .dimension("ctx", 5_i64) .dimension("ctx", "99") - .dimension("value", 1_i64) - .log(), + .dimension("value", 1_i64), ); } @@ -216,8 +204,7 @@ fn generic_data_class_enrichment_reaches_the_record() { processor.single_event(), ExpectedEvent::new("generic.unredacted", Severity::Info) .dimension("ctx", "tenant-7") - .dimension("value", 1_i64) - .log(), + .dimension("value", 1_i64), ); } @@ -276,6 +263,6 @@ fn a_field_routed_to_no_signal_is_absent_from_the_record() { assert_eq!( processor.single_event(), - ExpectedEvent::new("generic.excluded", Severity::Info).dimension("kept", "1").log(), + ExpectedEvent::new("generic.excluded", Severity::Info).dimension("kept", "1"), ); } diff --git a/crates/observed_testing/tests/metric_routing.rs b/crates/observed_testing/tests/metric_routing.rs index 387dfa0a6..5dc7ddf50 100644 --- a/crates/observed_testing/tests/metric_routing.rs +++ b/crates/observed_testing/tests/metric_routing.rs @@ -40,10 +40,7 @@ fn event_without_metrics_is_log_only() { emit!(sink, LogEvent { value: 1 }); let event = processor.single_event(); - assert_eq!( - event, - ExpectedEvent::new("test.probe", Severity::Info).dimension("value", 1i64).log(), - ); + assert_eq!(event, ExpectedEvent::new("test.probe", Severity::Info).dimension("value", 1i64)); assert_eq!(event.field_metrics().len(), 0); } diff --git a/crates/observed_testing/tests/processor_pipeline.rs b/crates/observed_testing/tests/processor_pipeline.rs index 9e625d531..159d7f7a0 100644 --- a/crates/observed_testing/tests/processor_pipeline.rs +++ b/crates/observed_testing/tests/processor_pipeline.rs @@ -65,13 +65,11 @@ fn severity_filter_drops_low_severity_events() { assert_eq!(events.len(), 2); assert_eq!( events[0], - ExpectedEvent::new("auth.failed", Severity::Warn).dimension("attempts", "3").log() + ExpectedEvent::new("auth.failed", Severity::Warn).dimension("attempts", "3") ); assert_eq!( events[1], - ExpectedEvent::new("system.crash", Severity::Fatal) - .dimension("exit_code", "1") - .log() + ExpectedEvent::new("system.crash", Severity::Fatal).dimension("exit_code", "1") ); } @@ -130,7 +128,7 @@ fn multiple_processors_receive_events_independently() { assert_eq!(warn_processor.len(), 1); assert_eq!( warn_processor.single_event(), - ExpectedEvent::new("auth.failed", Severity::Warn).dimension("attempts", "5").log() + ExpectedEvent::new("auth.failed", Severity::Warn).dimension("attempts", "5") ); } @@ -175,7 +173,6 @@ fn composite_fans_out_to_each_child() { ExpectedEvent::new("user.login", Severity::Info) .dimension("mfa_used", "false") .dimension("user_id", "1") - .log() ); } @@ -198,7 +195,6 @@ fn emitter_clone_shares_processors() { ExpectedEvent::new("user.login", Severity::Info) .dimension("mfa_used", "true") .dimension("user_id", "99") - .log() ); } diff --git a/crates/observed_testing/tests/redaction.rs b/crates/observed_testing/tests/redaction.rs index d2c728052..128f415d5 100644 --- a/crates/observed_testing/tests/redaction.rs +++ b/crates/observed_testing/tests/redaction.rs @@ -106,8 +106,7 @@ fn redaction_modes_on_classified_fields() { passthrough_proc.single_event(), ExpectedEvent::new("user.action", Severity::Info) .dimension("action_code", 42i64) - .dimension("user", "Alice") - .log(), + .dimension("user", "Alice"), ); // Erase: classified string becomes empty @@ -115,8 +114,7 @@ fn redaction_modes_on_classified_fields() { erase_proc.single_event(), ExpectedEvent::new("user.action", Severity::Info) .dimension("action_code", 42i64) - .dimension("user", "") - .log(), + .dimension("user", ""), ); // Replace('*'): each character replaced @@ -124,8 +122,7 @@ fn redaction_modes_on_classified_fields() { replace_proc.single_event(), ExpectedEvent::new("user.action", Severity::Info) .dimension("action_code", 42i64) - .dimension("user", "*****") - .log(), + .dimension("user", "*****"), ); } @@ -148,8 +145,7 @@ fn per_class_redaction_applies_different_rules() { ExpectedEvent::new("auth.token_used", Severity::Info) .dimension("request_id", 99i64) .dimension("token", "") - .dimension("user", "***") - .log(), + .dimension("user", "***"), ); } @@ -179,8 +175,7 @@ fn public_classified_type_with_passthrough_per_class() { processor.single_event(), ExpectedEvent::new("service.started", Severity::Info) .dimension("port", 8080i64) - .dimension("service", "my-service") - .log(), + .dimension("service", "my-service"), ); } @@ -213,8 +208,7 @@ fn enrichment_string_values_are_redacted() { processor.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("tenant", "*******") - .dimension("value", "*") - .log(), + .dimension("value", "*"), ); } @@ -243,8 +237,7 @@ fn enrichment_sensitive_values_are_redacted() { processor.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("retry_count", "") - .dimension("value", "") - .log(), + .dimension("value", ""), ); } @@ -279,8 +272,7 @@ fn enrichment_redaction_uses_per_processor_engine() { pass_proc.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("user_email", "alice@example.com") - .dimension("value", "1") - .log(), + .dimension("value", "1"), ); // Erase processor removes the enrichment value @@ -288,8 +280,7 @@ fn enrichment_redaction_uses_per_processor_engine() { erase_proc.single_event(), ExpectedEvent::new("test.probe", Severity::Info) .dimension("user_email", "") - .dimension("value", "") - .log(), + .dimension("value", ""), ); } @@ -318,8 +309,7 @@ fn insert_mode_replaces_with_custom_string() { processor.single_event(), ExpectedEvent::new("user.action", Severity::Info) .dimension("action_code", 1i64) - .dimension("user", "[REDACTED]") - .log(), + .dimension("user", "[REDACTED]"), ); } @@ -350,8 +340,7 @@ fn passthrough_for_specific_class_erase_rest() { ExpectedEvent::new("auth.token_used", Severity::Info) .dimension("request_id", 1i64) .dimension("token", "") - .dimension("user", "Alice") - .log(), + .dimension("user", "Alice"), ); } @@ -401,7 +390,6 @@ fn data_class_fields_are_redacted_without_being_cloned() { processor.single_event(), ExpectedEvent::new("data_class.borrowed", Severity::Info) .dimension("payload", "****") - .dimension("optional", "**") - .log(), + .dimension("optional", "**"), ); }