Skip to content

Commit 5f0abc8

Browse files
committed
feat(flow-php/symfony-telemetry-bundle): honour upstream trace sampling decision
- default sampler is now parent_based, matching the OTel SDK - configurable parent_based root sampler - new http_kernel require_trace_context flag, HTTP-scoped - messenger sync transport no longer lifts request suppression
1 parent 1c13ee1 commit 5f0abc8

19 files changed

Lines changed: 858 additions & 97 deletions

File tree

documentation/components/bridges/symfony-telemetry-bundle.md

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,12 @@ flow_telemetry:
378378
tracer_provider:
379379
error_handler: default # name from error_handlers; defaults to "default"
380380
sampler:
381-
type: always_on # always_on|always_off|trace_id_ratio|parent_based|attribute_matching|service
381+
type: parent_based # always_on|always_off|trace_id_ratio|parent_based|attribute_matching|service (default)
382382
ratio: 1.0 # Sampling ratio (0.0-1.0, only for trace_id_ratio)
383383
service_id: null # Custom sampler service (only for type: service)
384+
root:
385+
type: always_on # parent_based only: always_on|always_off|trace_id_ratio (default)
386+
ratio: 1.0 # root.type: trace_id_ratio only
384387
processor:
385388
type: batching # composite|memory|batching|passthrough|void|attribute_filtering|service
386389
batch_size: 512
@@ -392,18 +395,50 @@ flow_telemetry:
392395

393396
| Type | Description |
394397
|----------------------|---------------------------------------------------------------------------------------------------|
395-
| `always_on` | Sample all traces (default) |
398+
| `always_on` | Sample all traces |
396399
| `always_off` | Sample no traces |
397400
| `trace_id_ratio` | Sample based on trace ID ratio |
398-
| `parent_based` | Respect parent span's sampling decision |
401+
| `parent_based` | Respect the parent span's sampling decision; root spans fall back to `root` (**default**) |
399402
| `attribute_matching` | Drop spans matching an attribute matcher (start-time attrs); defer the rest to a delegate sampler |
400403
| `service` | Custom sampler service |
401404

402-
**`attribute_matching`** is the OTel-idiomatic way to drop spans by attribute — the decision is made at span start, so a
405+
### `parent_based` root sampler
406+
407+
`root` selects the sampler used for spans that have no parent — that is, requests arriving with no
408+
`traceparent` header, or with a malformed one. It accepts `always_on` (default), `always_off`, and
409+
`trace_id_ratio` (with `ratio`).
410+
411+
Setting `root.type: always_off` produces OpenTelemetry's `parentbased_always_off` behaviour: a trace is
412+
recorded only when an upstream service passes a sampled `traceparent`. This is the configuration to use
413+
when sampling is decided by a proxy such as nginx:
414+
415+
```yaml
416+
flow_telemetry:
417+
tracer_provider:
418+
sampler:
419+
type: parent_based
420+
root:
421+
type: always_off
422+
```
423+
424+
Note that the sampler is global. Under `root.type: always_off`, console commands stop producing traces
425+
entirely, and so do messenger worker spans — even when the request that dispatched the message was
426+
sampled, because a worker-consumed span deliberately starts its own trace linked to the producer rather
427+
than continuing the producer's trace.
428+
429+
If you want that behaviour for HTTP only, leave `root.type` at `always_on` and use
430+
`instrumentation.http_kernel.require_trace_context` instead.
431+
432+
### `attribute_matching` sampler
433+
434+
`attribute_matching` is the OTel-idiomatic way to drop spans by attribute — the decision is made at span start, so a
403435
matched span never records or reaches a processor. It only sees attributes available at span start (not end-state values
404436
like status codes). It reuses the same `matcher` / `exclude` / `sources` / `cache_dir` options as the
405437
[`attribute_filtering`](#attribute_filtering) processor, plus a `delegate` sampler for spans that don't match:
406438

439+
`delegate` only accepts leaf sampler types (`always_on` | `always_off` | `trace_id_ratio`), not `parent_based` — so an
440+
`attribute_matching` sampler cannot honour upstream sampling decisions for spans that don't match its matcher.
441+
407442
```yaml
408443
flow_telemetry:
409444
tracer_provider:
@@ -1073,6 +1108,10 @@ flow_telemetry:
10731108
The schema replaced the `type: <name>` discriminator with a sub-block whose key matches the implementation. There is no
10741109
BC shim.
10751110

1111+
**Breaking change:** the default `sampler.type` changed from `always_on` to `parent_based` (see
1112+
[TracerProvider](#tracerprovider) above). Spans whose parent carries an unsampled `traceparent` are now dropped by
1113+
default; pass `sampler.type: always_on` explicitly to restore the previous behaviour.
1114+
10761115
**Before (legacy schema)**
10771116

10781117
```yaml
@@ -1128,6 +1167,7 @@ flow_telemetry:
11281167
enabled: true
11291168
context_propagation: true # Extract context from incoming headers
11301169
context_propagation_query: false # Also extract from the URL query string (off by default; see note below)
1170+
require_trace_context: false # Suppress requests without a trace context (off by default; see note below)
11311171
route_naming: path # Span name / http.route source: 'path' (template, e.g. /orders/{id}; default) or 'name'
11321172
trace_controller: true # Controller body span (default ON)
11331173
trace_controller_resolution: false # controller.get_callable span (default OFF)
@@ -1178,6 +1218,18 @@ exists, so disabling `http_kernel` or excluding the path produces none.
11781218
The resolution and argument toggles install service decorators only when enabled, so they add zero overhead
11791219
when off.
11801220

1221+
`require_trace_context` suppresses any request that arrives without a trace context, including its DBAL
1222+
and cache spans. It is scoped to HTTP: console commands and messenger workers are unaffected. It requires
1223+
`context_propagation: true` and the `flow-php/symfony-http-foundation-telemetry-bridge` package; without
1224+
either, the flag is silently ignored and requests are traced as if it were unset.
1225+
1226+
Use `require_trace_context: true` when a proxy such as nginx decides sampling for inbound traffic but you
1227+
still want cron jobs and workers traced. Use `sampler.root.type: always_off` when you want the
1228+
require-an-upstream-decision rule applied to every entry point in the process. The flag only checks
1229+
whether a trace context is *present*, not whether it was sampled, so it does not honour an upstream
1230+
`sampled=0` decision on its own — that comes from the `parent_based` sampler's default. Pairing it with
1231+
`sampler.type: always_on` reintroduces traces for requests nginx explicitly declined to sample.
1232+
11811233
#### Security
11821234

11831235
Decorates the request span with the authenticated user. Requires `symfony/security-core` (and
@@ -1950,8 +2002,10 @@ flow_telemetry:
19502002
19512003
tracer_provider:
19522004
sampler:
1953-
type: trace_id_ratio
1954-
ratio: 0.1 # Sample 10% of traces in production
2005+
type: parent_based
2006+
root:
2007+
type: trace_id_ratio
2008+
ratio: 0.1 # sample 10% of traces that originate here; upstream decisions are always honoured
19552009
processor:
19562010
type: batching
19572011
batch_size: 512

documentation/components/core/telemetry.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,12 +377,16 @@ pass_through_log_processor($exporter)
377377
For high-volume pipelines, consider sampling to reduce telemetry volume:
378378

379379
```php
380-
use Flow\Telemetry\Tracer\Sampler\AlwaysOnSampler;
380+
use function Flow\Telemetry\DSL\{parent_based_sampler, trace_id_ratio_based_sampler};
381381

382-
// Always sample (default)
383-
tracer_provider($processor, $clock, $contextStorage, new AlwaysOnSampler())
382+
// Honor the parent's decision; sample 10% of traces that originate here
383+
tracer_provider($processor, $clock, $contextStorage, parent_based_sampler(trace_id_ratio_based_sampler(0.1)))
384384
```
385385

386+
See the [breaking change note](../libs/telemetry.md#sampling) in the telemetry library docs: the default sampler
387+
changed from `AlwaysOnSampler` to `ParentBasedSampler(AlwaysOnSampler)`, so spans whose parent is unsampled are now
388+
dropped.
389+
386390
### Resource Attributes
387391

388392
Include meaningful resource attributes to identify your service:

0 commit comments

Comments
 (0)