Symfony Telemetry Bundle
Flow Symfony Telemetry Bundle provides automatic telemetry integration for Symfony applications, including HTTP request/response tracing, console command instrumentation, and configurable exporters through OpenTelemetry-compatible backends.
- Installation
- Overview
- Configuration Reference
- Resource Configuration
- Clock Configuration
- Context Storage
- Propagator
- Error Handlers
- Exporters (named definitions)
- TracerProvider
- MeterProvider
- LoggerProvider
- Processor Configuration
- Exporter Definitions
- OTLP Transport Configuration
- Multiple OTLP backends
- Migrating from older config
- Instrumentation
- Named Instruments
- Main Logger
- Pattern Matching
- Usage
- Complete Production Example
Installation
For detailed installation instructions, see the installation page.
Overview
This bundle is built on top of flow-php/telemetry — see that page for the underlying Telemetry, tracer/meter/logger API, and processor/exporter primitives. For exporting to OTLP-compatible backends, it also uses the Telemetry OTLP Bridge.
This bundle integrates Flow PHP's Telemetry library with Symfony applications. It provides:
- Automatic resource detection - Detects service name, version, environment, OS, host, and process information
- 8 auto-instrumentation options - HTTP kernel, console, messenger, Twig, HTTP client, PSR-18 client, Doctrine DBAL, and cache
- Context propagation - W3C TraceContext and Baggage support for distributed tracing
- Configurable exporters - Console, memory, void, or OTLP with multiple transport options
- Full Symfony configuration - Configure everything through Symfony's config system
Configuration Reference
Any
error_handler:field that appears under a provider, processor, orotlpexporter references a name from the top-levelerror_handlers:map (see Error Handlers). When omitted it defaults todefault, which is auto-created as{ type: error_log }if the user does not declare one.
Resource Configuration
The resource node configures OpenTelemetry Resource attributes that identify your service.
flow_telemetry:
resource:
detectors:
enabled: true # Enable resource detectors (default: true)
static:
cache:
enabled: true # Cache static attributes (default: true)
path: null # Cache file path (default: sys_get_temp_dir()/flow_telemetry_resource.cache)
os:
enabled: true # Detect os.type, os.name, os.version, os.description
host:
enabled: true # Detect host.name, host.arch, host.id
service:
enabled: true # Detect service.name, service.version from composer.json
deployment:
enabled: true # Detect deployment.environment.name from kernel environment
environment:
enabled: true # Read OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES
dynamic:
process:
enabled: true # Detect process.pid, process.runtime.*, process.executable.*
custom:
service.name: 'my-app'
service.version: '1.0.0'
deployment.environment.name: 'production'
Detector types:
| Type | Class | Category | Attributes Detected |
|---|---|---|---|
os |
OsDetector |
static | os.type, os.name, os.version, os.description |
host |
HostDetector |
static | host.name, host.arch, host.id |
service |
ComposerDetector |
static | service.name, service.version (from composer.json) |
deployment |
SymfonyDeploymentDetector |
static | deployment.environment.name (from kernel environment) |
environment |
EnvironmentDetector |
static | Reads OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES env vars |
process |
ProcessDetector |
dynamic | process.pid, process.runtime.*, process.executable.* |
Static detectors are cached by default. Dynamic detectors run on every request/command.
The cache file lives outside Symfony's cache lifecycle on purpose: building the Symfony cache
(via cache:warmup) at image build time would otherwise freeze runtime-dependent attributes
such as host.name or process.pid from the build container. Defaulting to
sys_get_temp_dir() keeps the cache per-runtime and avoids that pitfall. To invalidate it,
delete the cache file or restart the process; cache:clear does not touch it.
Custom attributes override auto-detected values.
Clock Configuration
- type:
string|null - default:
null
Custom PSR-20 clock service ID. If not provided, uses the built-in SystemClock.
flow_telemetry:
clock_service_id: 'app.clock'
Context Storage
- type:
enum - default:
memory
Context storage for maintaining trace context across async operations.
flow_telemetry:
context_storage:
type: memory # memory|service
service_id: null # Custom service ID (only for type: service)
Propagator
- type:
enum - default:
w3c
Context propagator for distributed tracing. Determines how trace context is injected/extracted from HTTP headers.
flow_telemetry:
propagator:
type: w3c # w3c|tracecontext|baggage|service
service_id: null # Custom service ID (only for type: service)
| Type | Description |
|---|---|
w3c |
W3C TraceContext + Baggage (recommended) |
tracecontext |
W3C TraceContext only |
baggage |
W3C Baggage only |
service |
Custom propagator service |
Error Handlers
Per the OpenTelemetry spec, the SDK MUST NOT throw to user code at runtime. Errors raised by exporters/processors are
caught and forwarded to a configured error handler. The bundle exposes a named map under error_handlers: that is
referenced from provider, processor, and OTLP exporter blocks via error_handler: fields.
flow_telemetry:
error_handlers:
default:
type: error_log # default if omitted
to_file:
type: stream
destination: '%kernel.logs_dir%/flow-telemetry-errors.log'
to_syslog:
type: syslog
facility: local0
severity: warning
fanout:
type: composite
handlers: [default, to_file, to_syslog]
silent:
type: noop
custom:
type: service
service_id: app.my_error_handler
If error_handlers: is omitted (or default is missing inside it), the bundle injects
error_handlers.default = { type: error_log } automatically so every error_handler: reference always resolves.
Service IDs registered by the bundle (predictable for decorates:):
flow.telemetry.error_handler.<name>— e.g.flow.telemetry.error_handler.default
error_log (default)
Writes formatted Throwables via PHP's error_log() — stderr in CLI by default, or the error_log ini setting otherwise.
Matches the OTEL spec recommendation to log to standard error output.
| Option | Type | Default | Description |
|---|---|---|---|
message_type |
enum | operating_system |
operating_system (0), email (1), file (3), sapi (4) |
expand_newlines |
boolean | false |
Emit one error_log() call per line of the formatted message |
message_prefix |
string | [flow-telemetry] |
Prefix prepended to every message |
stream
Appends formatted Throwables (one per line) to a file path or php:// stream wrapper. The handle is opened lazily on
the first call and reused.
| Option | Type | Default | Description |
|---|---|---|---|
destination |
string | - | File path or php://stdout/php://stderr/etc. (required) |
file_permissions |
integer | 0644 |
Permissions for newly created files (ignored for php://) |
create_directories |
boolean | true |
Create parent directories of the destination if missing |
message_prefix |
string | [flow-telemetry] |
Prefix prepended to every line |
syslog
Writes via openlog/syslog/closelog.
| Option | Type | Default | Description |
|---|---|---|---|
ident |
string | flow-telemetry |
Syslog identity tag |
facility |
enum | user |
RFC 5424 facility (see table below) |
log_opts |
integer | LOG_PID |
Bitmask of LOG_* flags passed to openlog() |
severity |
enum | error |
RFC 5424 severity (see table below) |
udp_syslog
Sends RFC 5424 syslog frames over UDP.
| Option | Type | Default | Description |
|---|---|---|---|
host |
string | - | Remote syslog host (required) |
port |
integer | 514 |
Remote syslog port |
ident |
string | flow-telemetry |
Syslog identity tag |
facility |
enum | user |
RFC 5424 facility |
severity |
enum | error |
RFC 5424 severity |
composite
Fans an error out to multiple named handlers. Each child invocation is wrapped so a misbehaving handler cannot prevent siblings from running.
| Option | Type | Default | Description |
|---|---|---|---|
handlers |
list of strings | - | Names of other entries in error_handlers: (required) |
fanout:
type: composite
handlers: [default, to_file]
noop
Discards every Throwable. Intended for tests or explicit silence.
silent: { type: noop }
service
Aliases an existing service that implements Flow\Telemetry\ErrorHandler\ErrorHandler.
| Option | Type | Default | Description |
|---|---|---|---|
service_id |
string | - | Service id of the user-provided handler (required) |
custom:
type: service
service_id: app.my_error_handler
Facility values
| Value | Constant |
|---|---|
kernel |
LOG_KERN |
user |
LOG_USER |
mail |
LOG_MAIL |
daemon |
LOG_DAEMON |
auth |
LOG_AUTH |
syslog |
LOG_SYSLOG |
lpr |
LOG_LPR |
news |
LOG_NEWS |
uucp |
LOG_UUCP |
cron |
LOG_CRON |
local0..local7 |
LOG_LOCAL0..LOG_LOCAL7 |
Severity values
| Value | Constant |
|---|---|
emergency |
LOG_EMERG |
alert |
LOG_ALERT |
critical |
LOG_CRIT |
error |
LOG_ERR |
warning |
LOG_WARNING |
notice |
LOG_NOTICE |
info |
LOG_INFO |
debug |
LOG_DEBUG |
Exporters (named definitions)
The bundle exposes a top-level named map of exporters. The implementation is selected by the sub-block name
under each entry — there is no separate type: key. Each exporter must declare exactly one of the supported
sub-blocks: otlp, service, console, memory, void. Per-signal processors reference exporters by name.
flow_telemetry:
exporters:
otlp: # exporter name
otlp: # sub-block selects implementation
transport:
type: curl
endpoint: 'http://otel-collector:4318'
encoding: protobuf
| Sub-block | Options | Description |
|---|---|---|
otlp |
transport: { ... } |
Sends batches over OTLP curl/grpc/service |
service |
id: <service_id> |
Aliases an existing user-provided service |
console |
none (use ~ / null / {}) |
Pretty-prints to console |
memory |
none | Stores batches in memory (testing) |
void |
none | Discards everything (no-op) |
Service IDs registered by the bundle (predictable for decorates:):
flow.telemetry.exporter.<name>— e.g.flow.telemetry.exporter.otlpflow.telemetry.exporter.<name>.transport— only when the exporter uses theotlpsub-block
TracerProvider
Configures the tracer provider for distributed tracing.
flow_telemetry:
tracer_provider:
error_handler: default # name from error_handlers; defaults to "default"
sampler:
type: always_on # always_on|always_off|trace_id_ratio|parent_based|service
ratio: 1.0 # Sampling ratio (0.0-1.0, only for trace_id_ratio)
service_id: null # Custom sampler service (only for type: service)
processor:
type: batching # composite|memory|batching|passthrough|void|service
batch_size: 512
exporter: otlp # name of a top-level exporter
error_handler: default
Sampler types:
| Type | Description |
|---|---|
always_on |
Sample all traces (default) |
always_off |
Sample no traces |
trace_id_ratio |
Sample based on trace ID ratio |
parent_based |
Respect parent span's sampling decision |
service |
Custom sampler service |
MeterProvider
flow_telemetry:
meter_provider:
error_handler: default
temporality: cumulative # cumulative|delta
processor:
type: batching
batch_size: 512
exporter: otlp
error_handler: default
LoggerProvider
flow_telemetry:
logger_provider:
error_handler: default
processor:
type: batching # composite|memory|batching|passthrough|void|severity_filtering|service
batch_size: 512
exporter: otlp
error_handler: default
Severity filtering (logs only):
flow_telemetry:
logger_provider:
processor:
type: severity_filtering
minimum_severity: warn # trace|debug|info|warn|error|fatal
inner_processor:
type: batching
exporter: otlp
batch_size: 200
Processor Configuration
Processor types available for tracer_provider, meter_provider, and logger_provider.
void (default)
Discards all data. No additional options.
processor:
type: void
passthrough
Immediately exports each item via the referenced exporter.
processor:
type: passthrough
exporter: console
memory
Stores in memory (for testing). Still requires a backing exporter for flush() to call.
processor:
type: memory
exporter: memory
batching
Batches items before export.
| Option | Type | Default | Description |
|---|---|---|---|
batch_size |
integer | 512 |
Number of items per batch |
exporter |
string | - | Name of a top-level exporter (required) |
processor:
type: batching
batch_size: 512
exporter: otlp
composite
Combines multiple processors. Each child references its own exporter by name.
processor:
type: composite
processors:
- { type: batching, exporter: otlp, batch_size: 512 }
- { type: memory, exporter: memory }
service
Custom processor service.
processor:
type: service
service_id: 'app.custom_processor'
severity_filtering (logger_provider only)
Filters logs by minimum severity level. The wrapped inner_processor is built with the same set of types as a top-level
processor (except composite/severity_filtering).
processor:
type: severity_filtering
minimum_severity: warn
inner_processor:
type: batching
exporter: otlp
batch_size: 200
Exporter Definitions
Exporters are declared once at the top level under exporters: and referenced from processor blocks by name. Each
exporter declares exactly one sub-block; the sub-block name selects the implementation.
void
Discards all data.
exporters:
drop: { void: ~ }
memory
In-memory store for testing. Service exposes allLogs(), allMetrics(), allSpans() accessors via
Flow\Telemetry\Provider\Memory\MemoryExporter.
exporters:
capture: { memory: ~ }
console
Pretty-prints logs, metrics, and spans to the console. Useful for development.
exporters:
debug: { console: ~ }
otlp
Sends batches over an embedded transport. The single OTLP exporter handles all three signals.
exporters:
otlp:
otlp:
error_handler: default # name from error_handlers; defaults to "default"
transport:
type: curl
endpoint: 'http://otel-collector:4318'
encoding: protobuf
service
Aliases an existing user-defined service implementing Flow\Telemetry\Exporter\Exporter. This is the escape hatch for
APM-specific exporters (Datadog, New Relic, custom) that don't go through OTLP — define your exporter as a Symfony
service in your own services.yaml and reference it by id.
exporters:
datadog:
service:
id: 'app.datadog_telemetry_exporter'
OTLP Transport Configuration
Inside exporters.<name>.otlp.transport. Required for the otlp sub-block.
Timeouts
Both curl and gRPC default to aggressive, local-collector-friendly per-request timeouts with a separate, looser budget for graceful drain at shutdown:
| Setting | Default | Applies to | Bounds |
|---|---|---|---|
timeout_ms |
250 | curl, grpc | Per-request deadline (curl: total request; grpc: per-call) |
connect_timeout_ms |
250 | curl only | TCP/TLS connect; gRPC has no separate bound |
shutdown_timeout_ms |
5000 | curl, grpc | Wall-clock budget for draining pending requests at shutdown |
The defaults assume the recommended deployment: an OpenTelemetry Collector running close to the application (loopback,
UDS, or sidecar). shutdown_timeout_ms is independent of timeout_ms — keep the per-request value tight to surface
collector slowness during steady-state, while still giving graceful exit a longer window to drain. For a remote
collector across regions, raise both values. See the
OTLP bridge Timeouts section for the rationale.
Failover Transport
Both curl and grpc transports accept an optional nested failover: block. When primary delivery fails, the prior
batch is forwarded to the failover transport and a FailoverTransportException is raised so the operator is informed
even when failover absorbs the data. Common pattern: gRPC primary → stream (JSONL on disk) failover so a downed
collector still leaves recoverable data the operator can replay later.
exporters:
otlp:
otlp:
transport:
type: grpc
endpoint: 'otel-collector:4317'
timeout_ms: 250
failover:
type: stream
endpoint: '%kernel.logs_dir%/otel-failed.jsonl'
Constraints
- The
failover:block accepts the same fields as the parent transport, except it cannot itself declare a nestedfailover:(single-level depth). - Allowed only on
curlandgrpcprimaries.failoverunder astreamorserviceprimary is rejected at config-validation time. - The bundle registers
flow.telemetry.exporter.<name>.failover.transportfor the failover service id.
For the underlying behavior — when a forwarded batch is treated as absorbed vs. lost, the shape of
FailoverTransportException, and the cascade-shutdown contract — see the
OTLP bridge Failover Transport section.
curl (default)
| Option | Type | Default | Description |
|---|---|---|---|
endpoint |
string | - | OTLP base URL (required) |
timeout_ms |
integer | 250 |
Total per-request deadline in milliseconds |
connect_timeout_ms |
integer | 250 |
TCP/TLS connect deadline in milliseconds |
shutdown_timeout_ms |
integer | 5000 |
Wall-clock budget in milliseconds for draining pending requests at shutdown |
compression |
boolean | false |
Enable compression |
follow_redirects |
boolean | true |
Follow HTTP redirects |
max_redirects |
integer | 3 |
Maximum redirects to follow |
proxy |
string | null |
Proxy URL |
ssl_verify_peer |
boolean | true |
Verify SSL peer |
ssl_verify_host |
boolean | true |
Verify SSL host |
ssl_cert_path |
string | null |
SSL certificate path |
ssl_key_path |
string | null |
SSL key path |
ca_info_path |
string | null |
CA info path |
headers |
object | {} |
Additional HTTP headers |
encoding |
enum | json |
OTLP/HTTP wire encoding: json or protobuf |
failover |
object | null |
Optional failover transport |
See Timeouts for guidance on the millisecond defaults.
exporters:
otlp:
otlp:
transport:
type: curl
endpoint: 'http://otel-collector:4318'
timeout_ms: 250
connect_timeout_ms: 250
compression: true
headers:
Authorization: 'Bearer token'
encoding: protobuf
grpc
gRPC transport. encoding is rejected by validation (OTLP/gRPC mandates Protobuf, built internally), and
connect_timeout_ms is rejected because gRPC has no separate connect bound — timeout_ms is the per-call deadline
covering DNS, connect, send and receive together.
| Option | Type | Default | Description |
|---|---|---|---|
endpoint |
string | - | gRPC endpoint (required) |
timeout_ms |
integer | 250 |
Per-call deadline in milliseconds |
shutdown_timeout_ms |
integer | 5000 |
Wall-clock budget in milliseconds for draining pending calls at shutdown |
insecure |
boolean | true |
Allow insecure connections |
headers |
object | {} |
gRPC metadata |
failover |
object | null |
Optional failover transport |
exporters:
otlp_grpc:
otlp:
transport:
type: grpc
endpoint: 'otel-collector:4317'
timeout_ms: 250
insecure: false
stream
OTLP File Exporter (spec). Writes one JSON Line
per batch to the configured destination — either an absolute file path or a php:// stream wrapper — with
LOCK_EX around each fwrite. Only JSON encoding is supported per the spec; encoding and HTTP-specific options
(timeout, ssl_*, headers, etc.) are rejected at config time.
| Option | Type | Default | Description |
|---|---|---|---|
endpoint |
string | - | File path or php:// stream wrapper URI (required) |
file_permissions |
integer | 0644 |
File mode applied when creating new files; ignored for php:// destinations |
create_directories |
boolean | true |
Create the destination's parent directories if missing; ignored for php:// destinations |
exporters:
otlp_logs_file:
otlp:
transport:
type: stream
endpoint: '%kernel.project_dir%/var/otel/logs.jsonl'
file_permissions: 0640
create_directories: true
otlp_logs_stdout:
otlp:
transport:
type: stream
endpoint: 'php://stdout'
To write all three signal types to one file, point three exporters at the same destination — the OpenTelemetry
Collector's otlpjsonfile receiver handles mixed resourceLogs / resourceMetrics / resourceSpans lines.
service
Aliases an existing transport service ID inside the OTLP exporter.
exporters:
otlp:
otlp:
transport:
type: service
service_id: 'app.custom_transport'
Multiple OTLP backends
Each signal can target its own collector by declaring multiple named exporters and referencing them per provider.
flow_telemetry:
exporters:
otlp_traces:
otlp:
transport:
type: grpc
endpoint: 'http://traces:4317'
insecure: false
otlp_metrics:
otlp:
transport:
type: curl
endpoint: 'http://metrics:4318'
encoding: protobuf
otlp_logs:
otlp:
transport:
type: curl
endpoint: 'http://logs:4318'
encoding: json
tracer_provider:
processor: { type: batching, exporter: otlp_traces, batch_size: 1024 }
meter_provider:
processor: { type: batching, exporter: otlp_metrics, batch_size: 256 }
logger_provider:
processor: { type: batching, exporter: otlp_logs, batch_size: 100 }
Migrating from older config
The schema replaced the type: <name> discriminator with a sub-block whose key matches the implementation. There is no
BC shim.
Before (legacy schema)
flow_telemetry:
exporters:
otlp:
type: otlp
transport:
type: curl
endpoint: 'http://otel-collector:4318'
encoding: protobuf
custom:
type: service
service_id: 'app.x'
debug: { type: console }
After
flow_telemetry:
exporters:
otlp:
otlp:
transport:
type: curl
endpoint: 'http://otel-collector:4318'
encoding: protobuf
custom:
service:
id: 'app.x'
debug: { console: ~ }
tracer_provider:
processor: { type: batching, exporter: otlp }
Instrumentation
Configure automatic instrumentation for various Symfony components.
All instrumentation is disabled by default. You must explicitly set enabled: true for each component you want to
instrument.
HTTP Kernel
Traces HTTP requests and responses.
flow_telemetry:
instrumentation:
http_kernel:
enabled: true
context_propagation: true # Extract context from incoming headers
exclude_paths:
- path: '/_profiler'
- path: '/_wdt'
- path: '/health'
method: GET
- path: '/^\/api\/internal\/.*/' # Regex pattern
Console
Traces console commands.
flow_telemetry:
instrumentation:
console:
enabled: true
exclude_commands:
- 'cache:clear'
- 'assets:install'
- '/^debug:.*/' # Regex: exclude all debug commands
Messenger
Traces Symfony Messenger messages with context propagation across message boundaries.
flow_telemetry:
instrumentation:
messenger:
enabled: true
context_propagation: true # Propagate context across message boundaries
Twig
Traces Twig template rendering.
flow_telemetry:
instrumentation:
twig:
enabled: true
trace_templates: true # Trace template rendering
trace_blocks: false # Trace block rendering
trace_macros: false # Trace macro execution
exclude_templates:
- '@WebProfiler'
- '/^@Debug\/.*/'
HTTP Client
Traces Symfony HTTP Client requests.
flow_telemetry:
instrumentation:
http_client:
enabled: true
exclude_clients:
- 'internal.client'
- '/^debug\..*/'
PSR-18 Client
Traces PSR-18 HTTP client requests.
flow_telemetry:
instrumentation:
psr18_client:
enabled: true
exclude_clients:
- 'app.internal_client'
Doctrine DBAL
Traces database queries.
flow_telemetry:
instrumentation:
dbal:
enabled: true
log_sql: true # Include SQL in span attributes
max_sql_length: 1000 # Max SQL length (0 = no limit)
exclude_connections:
- 'legacy'
- '/^test_.*/'
Cache
Traces Symfony Cache operations.
flow_telemetry:
instrumentation:
cache:
enabled: true
exclude_pools:
- 'cache.system'
- '/^cache\.validator.*/'
Named Instruments
Configure named tracers, meters, and loggers with custom instrumentation scope attributes.
Options:
| Option | Type | Default | Description |
|---|---|---|---|
version |
string | 'unknown' |
Instrumentation scope version |
schema_url |
string | null |
Schema URL for semantic conventions |
attributes |
object | {} |
Additional scope attributes |
flow_telemetry:
tracers:
my_tracer:
version: '1.0.0' # default: 'unknown'
schema_url: 'https://opentelemetry.io/schemas/1.21.0'
attributes:
custom.attribute: 'value'
meters:
my_meter:
version: '1.0.0' # default: 'unknown'
schema_url: null
attributes: { }
loggers:
my_logger:
version: '1.0.0' # default: 'unknown'
Main Logger
The bundle depends on PSR-3 Telemetry Bridge and registers a PSR-3 wrapper service for every named Telemetry logger at flow.telemetry.<name>.logger.psr3. This makes Flow Telemetry loggers usable as Symfony's logger service, removing the need for Monolog when telemetry is the only logging destination.
In addition, the bundle always registers a default logger, meter, and tracer — flow.telemetry.default.logger, flow.telemetry.default.logger.psr3, flow.telemetry.default.meter, flow.telemetry.default.tracer — regardless of what is configured under loggers/meters/tracers. Defining your own default entry under those keys is allowed and will override the auto-default.
Options:
| Option | Type | Default | Description |
|---|---|---|---|
framework_logger |
string | null |
null |
Name of a logger configured under loggers (or the always-available default) to alias as Symfony logger |
Behavior:
- When
framework_loggeris set, the bundle aliases the Symfonyloggerservice toflow.telemetry.<framework_logger>.logger.psr3. If no logger with that name exists, container compilation fails with a clear error. - When
framework_loggerisnulland Symfony'sloggerservice is the defaultSymfony\Component\HttpKernel\Log\Logger, the bundle automatically aliasesloggertoflow.telemetry.default.logger.psr3. - When
framework_loggerisnullandloggeris provided by another bundle (Monolog, custom alias, etc.), the bundle leavesloggeralone.
flow_telemetry:
loggers:
app:
version: '1.0.0'
framework_logger: app # Symfony "logger" service -> flow.telemetry.app.logger.psr3
Pattern Matching
Several configuration options support pattern matching for exclusion lists (paths, commands, templates, etc.).
Exact String Matching
Patterns without regex delimiters match exactly:
exclude_paths:
- path: '/_profiler' # Matches exactly /_profiler
- path: '/health' # Matches exactly /health
Regex Matching
Patterns enclosed in / delimiters are treated as regular expressions:
exclude_paths:
- path: '/^\/api\/internal\/.*/' # Regex: matches /api/internal/*
exclude_commands:
- '/^debug:.*/' # Regex: matches debug:* commands
exclude_templates:
- '/^@Debug\/.*/' # Regex: matches @Debug/* templates
Usage
Accessing Telemetry in Services
Inject the Telemetry service to create custom spans, metrics, and logs:
<?php
namespace App\Service;
use Flow\Telemetry\Telemetry;
final class OrderService
{
public function __construct(
private readonly Telemetry $telemetry,
) {
}
public function processOrder(int $orderId): void
{
$tracer = $this->telemetry->tracer('order-service');
$tracer->trace('process_order', function () use ($orderId) {
// Your order processing logic
}, [
'order.id' => $orderId,
]);
}
}
Creating Custom Spans in Controllers
<?php
namespace App\Controller;
use Flow\Telemetry\Telemetry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class CheckoutController extends AbstractController
{
public function __construct(
private readonly Telemetry $telemetry,
) {
}
#[Route('/checkout', name: 'checkout')]
public function checkout(): Response
{
$tracer = $this->telemetry->tracer('checkout');
return $tracer->trace('checkout_page', function () {
// Nested span for cart validation
return $this->telemetry->tracer('checkout')->trace(
'validate_cart',
fn () => $this->render('checkout/index.html.twig')
);
});
}
}
Recording Metrics
<?php
$meter = $this->telemetry->meter('business-metrics');
// Counter
$meter->counter('orders_processed')
->add(1, ['status' => 'completed']);
// Histogram
$meter->histogram('order_value')
->record(99.99, ['currency' => 'USD']);
// Gauge
$meter->gauge('active_users')
->record(42);
Logging with Telemetry
<?php
$logger = $this->telemetry->logger('app');
$logger->info('Order processed', [
'order_id' => 12345,
'amount' => 99.99,
]);
Complete Production Example
# config/packages/flow_telemetry.yaml
flow_telemetry:
resource:
custom:
service.name: 'my-app'
service.version: '%env(APP_VERSION)%'
propagator:
type: w3c
exporters:
otlp_traces:
otlp:
transport:
type: curl
endpoint: '%env(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)%'
timeout_ms: 250
connect_timeout_ms: 250
headers:
Authorization: 'Bearer %env(OTEL_AUTH_TOKEN)%'
encoding: protobuf
failover:
type: stream
endpoint: '%kernel.logs_dir%/otel-traces-failed.jsonl'
otlp_metrics:
otlp:
transport:
type: curl
endpoint: '%env(OTEL_EXPORTER_OTLP_METRICS_ENDPOINT)%'
encoding: protobuf
otlp_logs:
otlp:
transport:
type: curl
endpoint: '%env(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT)%'
encoding: protobuf
tracer_provider:
sampler:
type: trace_id_ratio
ratio: 0.1 # Sample 10% of traces in production
processor:
type: batching
batch_size: 512
exporter: otlp_traces
meter_provider:
temporality: cumulative
processor:
type: batching
exporter: otlp_metrics
logger_provider:
processor:
type: severity_filtering
minimum_severity: info
inner_processor:
type: batching
exporter: otlp_logs
loggers:
app:
version: '%env(APP_VERSION)%'
audit:
version: '%env(APP_VERSION)%'
attributes:
channel: audit
meters:
business:
version: '%env(APP_VERSION)%'
tracers:
checkout:
version: '%env(APP_VERSION)%'
framework_logger: app # Symfony "logger" service -> flow.telemetry.app.logger.psr3
instrumentation:
http_kernel:
enabled: true
exclude_paths:
- path: '/_profiler'
- path: '/_wdt'
- path: '/health'
method: GET
console:
enabled: true
exclude_commands:
- 'cache:clear'
- 'cache:warmup'
- '/^debug:.*/'
messenger:
enabled: true
context_propagation: true
dbal:
enabled: true
log_sql: true
max_sql_length: 500
cache:
enabled: true
exclude_pools:
- 'cache.system'
Found a typo or an outdated section? Edit this page on GitHub