Skip to content

[opentelemetry-php-contrib] Symfony: a throwing kernel.view listener splits one request into two traces and loses http.route #2020

Description

@kreciszd

Package

open-telemetry/opentelemetry-auto-symfony 1.4.0 (latest release)

Environment

  • Symfony 5.4.45, PHP 8.2, php-fpm
  • open-telemetry/sdk 1.15.0, open-telemetry/api 1.10.0, ext-opentelemetry
  • Exporter: OTLP http/protobuf → Grafana Alloy → Tempo

What happens

When a request fails with a 500 raised from a kernel.view listener, one HTTP request is
exported as two error spans in two different traces, and neither is complete:

span kind name http.route http.response.status_code events
A SERVER DELETE absent absent exception
B INTERNAL DELETE api_customer_records_delete_item present 500 none

The two spans carry different trace_ids.

Counts match failures 1:1 — over 24h on two clusters: 2 failing requests produced 2 A-spans
and 2 B-spans; 1 failing request produced 1 and 1. Successful requests are unaffected: a
single SERVER span, correctly named {method} {route}.

Why it matters

  • Span-derived RED metrics (traces_spanmetrics_calls_total) bucket every 5xx under the
    bare HTTP method, so server errors cannot be attributed to an endpoint.
  • The span holding the exception is not the span holding the status code, and they are not
    in the same trace, so no single trace shows a complete picture of the failure.

Where it appears to come from

In SymfonyInstrumentation::register() a scope is attached in the handle pre hook for
every request type, but detached in only two places:

  • handle post — returns early without detaching when $exception === null
    (the normal path, including a request that produced a 500 response);
  • terminate post — pops exactly one scope, and that is where http.route and the
    status code are stamped.

When the error is rendered through a sub-request, a second scope is attached and the stack
ends up unbalanced, so terminate appears to stamp the route and status onto the
sub-request span rather than the main SERVER span.

Caveat

The table above is measured; the explanation is inferred from reading
SymfonyInstrumentation.php — I have not stepped through the hook ordering to confirm it.
Happy to put together a minimal reproducer if that would help.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedThis issue is looking for someone to work on it

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions