Skip to content

Improve the OpenTelemetry bridge - #2807

Merged
stevejgordon merged 2 commits into
mainfrom
otel-bridge-options
Sep 14, 2026
Merged

stevejgordon merged 2 commits into
mainfrom
otel-bridge-options

Conversation

@stevejgordon

Copy link
Copy Markdown
Contributor

A batch of related OpenTelemetry bridge work. The theme is that an application using the core package plus OpenTelemetry instrumentation, with no Elastic integration packages, should produce a sensible trace out of the box and that the bridge should not turn on telemetry the application did not ask for.

Incoming ASP.NET Core requests

Microsoft.AspNetCore.Hosting.HttpRequestIn was in KnownListeners.SkippedActivityNamesSet, so in an application without Elastic.Apm.AspNetCore there was no transaction for a request and every activity started while handling it became a root transaction of its own trace.

It is now skipped only while Elastic.Apm.AspNetCore or Elastic.Apm.Azure.Functions is loaded. Both create the request transaction from the hosting layer's diagnostic events, and the Azure Functions integration does so without referencing the AspNetCore assembly, so detecting only the latter would duplicate transactions for Functions users.

Aligns span naming with the ASP.NET Core instrumentation when possible and ensures that custom names or names set after the Activity starts are preserved.

The transaction also records context.request method and URL, under both the current (url.scheme, url.path, url.query, server.address, server.port) and older (http.scheme, http.target, http.host) conventions. APM Server rebuilds those fields for the transaction itself from otel.attributes, but an error copies the transaction's context as it stands when it is captured and carries no attributes of its own, so this is what gives errors in a bridge-only application a URL.

Activity source filtering

ShouldListenTo was _ => true. Two new options narrow it: OpenTelemetryBridgeAllowedActivitySources (default *) and OpenTelemetryBridgeDeniedActivitySources (default empty). Allow is a gate, deny is a veto. Filtering is applied at subscription, so an excluded source is never observed and a source which only emits while a listener is attached is never created which also means these are read once at startup and are not centrally configurable.

⚠️ Breaking: experimental runtime sources are opt in

On .NET 9 and later the runtime's Experimental.System.Net.* sources (DNS resolution, socket connect, TLS handshake, HTTP connection setup) are dormant until something subscribes but the catch-all subscription was what activated them. Transactions which opened a new connection therefore contained DNS lookup, socket connect, TLS client handshake, HTTP connection_setup and HTTP wait_for_connection spans.

The bridge no longer subscribes to Experimental.* by default. Set ELASTIC_APM_OPENTELEMETRY_BRIDGE_EXPERIMENTAL_SOURCES_ENABLED=true to restore it. .NET 8 and earlier are unaffected. When enabled, connection level activities are captured as spans within an existing trace and are never promoted to transactions: ConnectionSetup is deliberately the root of its own trace because a connection outlives the requests that share it, so promoting it produced top level transactions for application startup, background work and the agent's own transport.

Trace topology and naming

  • Skipped parents are collapsed, not severed. When an activity's in-process parent is one the bridge deliberately declined (ShouldSkipActivity, or excluded by the source filter), the walk continues up to the top-most such ancestor and the trace is continued from its ParentId and TraceStateString. An upstream sampling decision is honoured, instead of the activity starting a fresh trace.
  • Span links for re-nested parents. An activity whose declared parent was not created in this process (a messaging consumer continuing a producer's context) is nested under the ambient segment, and the declared parent is kept as a span link unless it belongs to the trace being nested into, or is already linked.
  • HasCustomName (already on Transaction, added to Span) replaces the name == OperationName heuristic, so a DisplayName set when the activity stops is adopted while a name set through the Elastic API always wins.

Attribute mapping

Current and legacy keys are read side by side for method (_OTHER resolved through http.request.method_original), scheme, path, query, host and port; InferTransactionType recognises modern server spans, which previously came out unknown; url.scheme classifies an HTTP client span the way http.scheme already did; and span.context.service.target is only emitted when it has a type or a name, which APM Server otherwise rejects.

Supporting changes

  • AbstractConfigurationReader: the near-duplicate wildcard list parsers are one ParseWildcardMatchers(...) helper, with blankIsDefault preserving each option's existing empty-value meaning.
  • HostBuilderExtensions: the framework version is read by loading Microsoft.AspNetCore by name, rather than the first loaded assembly with that prefix, which could be an out of band package such as Microsoft.AspNetCore.Mvc.Testing and made the reported version depend on load order.

@github-actions

Copy link
Copy Markdown

🤖 GitHub comments

Just comment with:

  • run docs-build : Re-trigger the docs validation. (use unformatted text in the comment!)

@github-actions

github-actions Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

✅ Elastic Docs Style Checker (Vale)

No issues found on modified lines!


The Vale linter checks documentation changes against the Elastic Docs style guide. To use Vale locally or report issues, refer to Elastic style guide for Vale.

@stevejgordon
stevejgordon marked this pull request as ready for review September 11, 2026 13:51
@stevejgordon
stevejgordon requested a review from a team September 11, 2026 15:55
@stevejgordon
stevejgordon merged commit fbc7da0 into main Sep 14, 2026
22 of 30 checks passed
@stevejgordon
stevejgordon deleted the otel-bridge-options branch September 14, 2026 07:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants