traceway/opentelemetry-symfony
最新稳定版本:v1.8.0
Composer 安装命令:
composer require traceway/opentelemetry-symfony
包简介
Pure-PHP OpenTelemetry instrumentation for Symfony — automatic HTTP, Console, HttpClient, Messenger, Doctrine DBAL, Cache, Twig tracing and Monolog log-trace correlation with response propagation, a lightweight Tracing helper, route templates, and semantic conventions. No C extension required (ext-p
关键字:
README 文档
README
OpenTelemetry Symfony Bundle
Pure-PHP OpenTelemetry instrumentation for Symfony — automatic tracing for HTTP, Console, HttpClient, Messenger, Mailer, Scheduler, Doctrine DBAL, Cache, and Twig, plus Monolog log-trace correlation, OpenTelemetry log export, and opt-in metrics for Messenger, DBAL, and HTTP server/client. No C extension required.
Works with any OpenTelemetry-compatible backend: Traceway, Jaeger, Zipkin, Datadog, Grafana Tempo, Honeycomb, and more.
- Pure PHP — no C extension required; installs on every managed Symfony host
- Production-ready — stable since v1.0, PHPStan level 10 with no baseline, supports Symfony 6.4 LTS through 8.x
- Correct under load — Messenger trace context propagates across async queue boundaries, Doctrine DBAL 3 and 4 both CI-tested, re-entrance guards prevent export-path recursion in HttpClient and the log handler
Quick Start
composer require traceway/opentelemetry-symfony
OTEL_PHP_AUTOLOAD_ENABLED=true OTEL_SERVICE_NAME=my-symfony-app OTEL_TRACES_EXPORTER=otlp OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 OTEL_EXPORTER_OTLP_PROTOCOL=http/json # Optional: OTEL_RESOURCE_ATTRIBUTES=service.version=1.0
Use
http/jsonunless you haveext-protobufinstalled — see Performance.
With Symfony Flex the bundle auto-registers; without Flex, add Traceway\OpenTelemetryBundle\OpenTelemetryBundle::class => ['all' => true] to config/bundles.php.
That's it. Every HTTP request, console command, outgoing call, Messenger job, DB query, cache operation, and Twig render is now traced.
What Gets Traced
| Component | Span Kind | What's captured |
|---|---|---|
| HTTP requests | SERVER | Route templates (GET /api/items/{id}), status codes, body sizes, client IP, exceptions, sub-requests |
| Console commands | SERVER | Command name, arguments, exit code, exceptions |
| HttpClient | CLIENT | Outgoing requests with W3C context propagation, OTLP endpoint auto-excluded, re-entrance guard |
| Messenger | PRODUCER/CONSUMER | Message class, transport, W3C context propagation across async boundaries |
| Scheduler | CONSUMER | Per scheduled-task run: schedule name, trigger expression, next-run, cancellation marker. Requires symfony/scheduler. Messenger spans for scheduled envelopes are suppressed automatically |
| Mailer | PRODUCER + CLIENT | Two-span split: PRODUCER on MailerInterface::send and CLIENT on the transport. Recipient count, message-id, X-Transport routing. Subject opt-in. Requires symfony/mailer |
| Doctrine DBAL | CLIENT | SQL queries (parameterized), transactions, db system/namespace auto-detection. DBAL 3.6+ and 4.x both CI-tested |
| Cache | INTERNAL | get (hit/miss), delete, invalidateTags with pool name. Requires symfony/cache |
| Twig | INTERNAL | Template name, nested includes. Requires twig/twig |
| Monolog | — | Inject trace_id + span_id into every log record (monolog/monolog). Opt-in OTel Logs API export with per-channel instrumentation scope (symfony/monolog-bundle, off by default) |
Also: Server-Timing response headers, full OTel semantic conventions.
Configuration
All options are optional — the bundle works out of the box with zero configuration. Create config/packages/open_telemetry.yaml to customize:
open_telemetry: traces_enabled: true tracer_name: 'opentelemetry-symfony' excluded_paths: [/health, /_profiler, /_wdt] record_client_ip: true # disable for GDPR error_status_threshold: 500 # 400-599 console_enabled: true console_excluded_commands: [cache:clear, assets:install] http_client_enabled: true http_client_excluded_hosts: [] # OTLP endpoint is auto-excluded messenger_enabled: true messenger_root_spans: false # true = standalone traces per consumed message scheduler_enabled: true # suppresses parallel Messenger spans for scheduled tasks mailer_enabled: true mailer_record_subject: false # subjects can be PII doctrine_enabled: true doctrine_record_statements: true # false = hide SQL from spans cache_enabled: true cache_excluded_pools: [cache.system, cache.validator, cache.serializer] twig_enabled: true twig_excluded_templates: ['@WebProfiler/', '@Debug/'] monolog_enabled: true # inject trace_id/span_id into log records log_export_enabled: false # OTel Logs API export (requires symfony/monolog-bundle) log_export_level: debug log_export_capture_code_attributes: false # fallback debug_backtrace when IntrospectionProcessor is absent log_export_unprefixed_attributes: false # emit context/extra as flat attributes (default flips in v2.0) metrics: # nested today; flat keys above migrate to nested in v2.0 enabled: false meter_name: 'opentelemetry-symfony' messenger: enabled: false excluded_queues: [] doctrine: enabled: false http_server: enabled: false excluded_paths: [] # same prefix-match rules as tracing excluded_paths http_client: enabled: false excluded_hosts: [] # OTLP endpoint is auto-excluded
Environment Variables
| Variable | Example | Description |
|---|---|---|
OTEL_PHP_AUTOLOAD_ENABLED |
true |
Enable SDK auto-initialization |
OTEL_SERVICE_NAME |
my-symfony-app |
Service name shown in your backend |
OTEL_TRACES_EXPORTER |
otlp |
Traces exporter (otlp, zipkin, console, none) |
OTEL_LOGS_EXPORTER |
otlp |
Logs exporter (otlp, console, none) — only used when log_export_enabled: true |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:4318 |
Collector/backend endpoint |
OTEL_EXPORTER_OTLP_PROTOCOL |
http/json |
Protocol (http/json, http/protobuf, grpc) |
See the OpenTelemetry SDK docs for all available options.
Manual Instrumentation
Inject TracingInterface for one-liner span creation:
use Traceway\OpenTelemetryBundle\TracingInterface; class OrderService { public function __construct(private readonly TracingInterface $tracing) {} public function process(int $orderId): void { $this->tracing->trace('order.validate', function () use ($orderId) { // validation logic... }); $this->tracing->trace('order.fulfill', function () { $this->tracing->trace('inventory.reserve', fn () => $this->reserve()); $this->tracing->trace('payment.charge', fn () => $this->charge()); }); } }
Mock in tests with $this->createStub(TracingInterface::class) and have trace() invoke the callback directly.
Metrics
Off by default. Enable to export OpenTelemetry metrics alongside traces, with opt-in automatic instrumentation for Messenger, Doctrine DBAL, and HTTP server/client.
open_telemetry: metrics: enabled: true meter_name: 'opentelemetry-symfony' messenger: enabled: true excluded_queues: [] doctrine: enabled: true
What Gets Measured
| Instrument | Kind | Unit | Source | Attributes |
|---|---|---|---|---|
messaging.process.duration |
Histogram | s |
Messenger consume | messaging.system, messaging.operation.name, messaging.operation.type, messaging.destination.name, error.type on failure |
messaging.client.consumed.messages |
Counter | {message} |
Messenger consume | Same as above |
messaging.client.operation.duration |
Histogram | s |
Messenger dispatch | Same shape, messaging.operation.{name,type} = send, destination derived from SentStamp::getSenderAlias() (falls back to sender FQCN) |
messaging.client.sent.messages |
Counter | {message} |
Messenger dispatch | Same as above |
db.client.operation.duration |
Histogram | s |
DBAL connection | db.system.name, db.namespace, server.address, server.port, db.operation.name, db.collection.name (when extractable), error.type on failure |
http.server.request.duration |
Histogram | s |
HTTP server | http.request.method, url.scheme, http.route if matched, http.response.status_code, server.address, server.port, error.type on failure |
http.server.active_requests |
UpDownCounter | {request} |
HTTP server | http.request.method, url.scheme, server.address, server.port |
http.server.request.body.size |
Histogram | By |
HTTP server | Same as duration (emitted when Content-Length is set) |
http.server.response.body.size |
Histogram | By |
HTTP server | Same as duration (emitted when Content-Length is set) |
Names and attributes follow OTel semantic conventions (messaging, database, HTTP). http.server.request.duration and error.type are Stable; the rest are Development.
- HTTP server — only main requests are measured; sub-requests are covered by the main duration. Service identity comes from the OTel resource (
OTEL_SERVICE_NAME,OTEL_RESOURCE_ATTRIBUTES), not from metric name prefixing. - Messenger —
excluded_queuesmatches the transport name on both sides (ReceivedStamp::getTransportName()on consume,SentStamp::getSenderAlias()on dispatch). A dispatched envelope landing on multiple transports emits one point per non-excluded transport. - DBAL — records duration for
Connection::query()/exec(), preparedStatement::execute(), and transaction control methods. SQL text is never recorded — only the leading keyword (db.operation.name) and the primary table when extractable (db.collection.name).
HTTP Client (outgoing requests):
| Instrument | Kind | Unit | Stability | Attributes |
|---|---|---|---|---|
http.client.request.duration |
Histogram | s |
Stable | http.request.method, server.address, server.port, url.scheme, http.response.status_code on response, error.type on transport failure |
http.client.request.body.size |
Histogram | By |
Development | Same as duration (emitted when Content-Length header or a string body is present) |
http.client.response.body.size |
Histogram | By |
Development | Same as duration (emitted when response Content-Length is set or the body is fully read) |
http_client.excluded_hosts skips matching hostnames; the OTLP endpoint (from OTEL_EXPORTER_OTLP_ENDPOINT) is always auto-excluded to prevent instrumentation loops.
Manual Metrics
Inject MeterRegistryInterface to record your own counters, histograms, and up/down counters without touching the MeterProvider directly:
use OpenTelemetry\API\Metrics\CounterInterface; use Traceway\OpenTelemetryBundle\Metrics\MeterRegistryInterface; final class MediaDownloader { private readonly CounterInterface $downloads; public function __construct(MeterRegistryInterface $metrics) { $this->downloads = $metrics->counter( 'media.download.count', description: 'Media downloads by outcome', ); } public function download(string $url): void { try { // ... download logic $this->downloads->add(1, ['outcome' => 'success']); } catch (\Throwable $e) { $type = $e::class; if (str_contains($type, '@anonymous')) { $type = get_parent_class($e) ?: \Throwable::class; } $this->downloads->add(1, ['outcome' => 'error', 'error.type' => $type]); throw $e; } } }
The registry caches instruments per name, so repeated ->counter('x') calls return the same instance. When the OTel SDK is not configured, the NoOp meter provider returns no-op instruments — safe to inject unconditionally. The @anonymous guard above normalises anonymous-class names to their parent; otherwise $e::class embeds a filesystem path, leaking code locations and exploding label cardinality.
Metrics Environment Variables
| Variable | Example | Description |
|---|---|---|
OTEL_METRICS_EXPORTER |
otlp |
Metrics exporter (otlp, console, none) — only used when metrics.enabled: true |
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT |
http://localhost:4318/v1/metrics |
Override the generic OTEL_EXPORTER_OTLP_ENDPOINT for metrics |
Performance
Near-zero overhead when the SDK is inactive — every component short-circuits via isEnabled(). When tracing is on, almost all cost is in span export, not instrumentation. PHP-FPM has no background thread, so BatchSpanProcessor flushes during request shutdown.
Use http/json unless you have ext-protobuf installed. PHP's native json_encode() is faster than the pure-PHP protobuf encoder, which adds significant CPU overhead under load. Switch to http/protobuf only with the C extension installed.
For high-traffic apps:
- Run a local OTel Collector at
localhost:4318(sub-ms latency) and let it forward asynchronously. - Enable head sampling:
OTEL_TRACES_SAMPLER=parentbased_traceidratio+OTEL_TRACES_SAMPLER_ARG=0.1. - Use
excluded_paths/cache_excluded_poolsto drop noisy spans.
Contributing
git clone https://github.com/tracewayapp/opentelemetry-symfony-bundle.git
cd opentelemetry-symfony-bundle
composer install
vendor/bin/phpunit
vendor/bin/phpstan analyse
License
统计信息
- 总下载量: 1.74k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 60
- 点击次数: 6
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-03-14