Deprecating Span Events API
OpenTelemetry is finally addressing one of its more awkward API overlaps by deprecating the Span Event API. If you've been instrumenting services with OTel, you've probably noticed the confusion: should you call span.AddEvent() or use the logging API with span context? The project is consolidating on log-based events, and while the transition will take time, it's the right architectural move.
The core problem is simple duplication. Span events and log-based events both let you record timestamped structured data within a trace context. The span event API feels natural when you're already holding a span reference—just call AddEvent() with a name and attributes. But the logging API does essentially the same thing, automatically correlating logs with the active span through context propagation. Having both creates decision paralysis during instrumentation and forces backend systems to handle two different data models that represent the same concept.
From a data model perspective, span events are tightly coupled to their parent span. They live in the span's lifecycle and get exported as part of the span payload. Log-based events are independent records that happen to carry span and trace IDs as attributes. This distinction matters when you're building observability pipelines. With span events, you can't query or sample them independently—they're always attached to their span. If you want to analyze events across multiple spans or services without pulling full trace data, you're out of luck. Log-based events give you that flexibility since they're first-class citizens in your logging pipeline.
The practical implications depend on how you've instrumented your code. If you're using span.AddEvent() extensively, nothing breaks immediately. The deprecation is a signal to shift new instrumentation toward the logging API with proper context propagation. Your existing span event data will continue to flow through the pipeline and display correctly in trace visualizations. Backend vendors and the OTel collector already handle both formats, and that won't change during the transition period.
What does change is your instrumentation strategy going forward. Instead of calling span.AddEvent("cache_miss", attributes), you'll use your language's logging API with structured fields and ensure the span context is properly propagated. In Go, that means using the context-aware logger. In Java, it's leveraging the log4j2 or logback appenders that extract span context. Python developers will use the logging integration that automatically injects trace IDs.
The win here is conceptual clarity and operational flexibility. You get one way to emit events, one set of sampling controls, and one query interface. Your logging pipeline can apply consistent filtering, enrichment, and routing rules whether events originated from application logs or trace instrumentation. You can sample high-volume events independently from their parent spans, which is crucial when debugging chatty code paths that would otherwise explode your trace storage costs.
The migration path is straightforward but not instant. Review your instrumentation libraries and SDK versions to ensure they support log-based events with span correlation. Update your runbooks and instrumentation guidelines to prefer logging APIs over span.AddEvent(). Test that your observability backend correctly correlates logs with traces—most vendors handle this already, but verify your specific setup. Then gradually refactor hot paths and new code to use the logging approach.
This deprecation eliminates a long-standing source of confusion in OTel's API surface. One less decision to make during instrumentation is a genuine improvement.