Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Opentelemetry Bundle Laravel Package

friendsofopentelemetry/opentelemetry-bundle

Symfony bundle integrating OpenTelemetry PHP for distributed tracing, metrics, and logs with sensible defaults, configurable exporters, and easy instrumentation of requests, console commands, and services.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the framing. This is a Symfony bundle (friendsofopentelemetry/opentelemetry-bundle), not a Laravel package. It ships no service provider, no config/opentelemetry.php, and it decorates Symfony services (HttpClientInterface, DBAL connections, Messenger buses, kernel.request). You cannot composer require it into a Laravel app and expect anything to happen. What you can leverage is everything underneath it: the bundle is essentially a curated wiring of open-telemetry/sdk, open-telemetry/exporter-otlp, and open-telemetry/sem-conv. Read it as a reference implementation, then port the wiring into a Laravel service provider.

Version note. The current release is v1.0.0-beta.7 — still pre-1.0, so pin it exactly ("friendsofopentelemetry/opentelemetry-bundle": "1.0.0-beta.7") if you run it in Symfony, and read the changelog between betas rather than assuming semver guarantees. Beta.7 itself is mostly dependency hygiene (Dependabot action bumps, PHPUnit upgraded to ^13 to clear security advisories) plus one substantive feature: Messenger worker and middleware instrumentation. That feature is the most interesting thing in this release for a Laravel developer, because it is a concrete blueprint for instrumenting queue workers.

If you work in Symfony too, the path is short:

  1. composer require friendsofopentelemetry/opentelemetry-bundle:1.0.0-beta.7 (note the spelling: friendsofopentelemetry, no plural "friends" — verify the vendor name carefully before installing).
  2. Register the bundle in config/bundles.php (Flex usually does it).
  3. Add config/packages/open_telemetry.yaml with a resource block (service.name, service.version, deployment.environment) and an OTLP exporter pointing at a collector.
  4. Run a local collector + backend: docker run --rm -p 4317:4317 -p 16686:16686 otel/opentelemetry-collector-contrib alongside Jaeger, and set the sampler to always_on while you're learning.
  5. Hit a route that does a DB query and an outbound HTTP call, then confirm you see three spans: request, DB, HTTP client.
  6. If you use Messenger, start a consumer (messenger:consume) and confirm you now get a worker span plus one span per handled message, correctly parented to whatever dispatched it.

Where to look first, in this order:

  • DependencyInjection/Configuration.php — the authoritative config tree. The README will drift; this file won't.
  • Resources/config/*.php — the service definitions. This is the map of what actually gets instrumented, and therefore the map of what you must reimplement in Laravel.
  • The decorator classes themselves (TraceableHttpClient-style wrappers, the DBAL middleware, the Messenger middleware, the kernel event subscriber). Each one is 50–150 lines and is the best documentation of "what a span should look like."
  • Instrumentation/Messenger/ — new in beta.7: separate worker and middleware instrumentation. Read the middleware for the per-message span shape and the worker class for the long-running-process lifecycle (start, consume, flush, terminate). This is the closest analogue to Laravel's queue:work.

Best first use case for a Laravel dev: a single request span plus one outbound HTTP span on a controller you already know well. Prove the trace lands in your backend before touching queues, Doctrine analogues, or metrics. Traces are the signal with the highest day-to-day debugging payoff and the least config risk. Once that works, the second use case is a single queued job — because context propagation across the queue boundary is where most hand-rolled Laravel instrumentation quietly falls apart.

Implementation Patterns

Decorator-first, not annotation-first. The bundle's model is: don't touch application code, wrap the framework's seams. In Laravel the corresponding seams are:

Symfony bundle hook Laravel equivalent
kernel.request subscriber Global middleware extracting traceparent, starting a root span
TraceableHttpClient Guzzle/Http::withMiddleware() middleware that injects context
Doctrine DBAL middleware DB::listen() or a connection decorator for query spans
Messenger middleware (per-message span) Queue JobProcessing/JobProcessed listeners
Messenger worker (consumer lifecycle) queue:work loop wrapper, WorkerStarting/WorkerStopping, Looping
Console instrumentation Artisan CommandStarting/CommandFinished events

Port them one at a time and verify each in isolation. Do not wire all five in a single PR.

Model queue instrumentation as two spans, not one. Beta.7's split between Messenger middleware and worker instrumentation is worth copying verbatim in Laravel:

  • The producer span lives in the dispatching request, with a PRODUCER kind, and is where you inject traceparent into the job payload.
  • The consumer span lives in the worker, one per job, with a CONSUMER kind, parented via a link (not a child) to the producer when the message may be retried or fanned out. Link, don't parent, if a job can be processed more than once — otherwise retries look like a suspiciously deep single trace.
  • Attributes worth setting: queue name, connection name, job class (or a low-cardinality alias), attempt number, and messaging.message.id.

Use a scoped helper instead of raw span boilerplate. The SDK's SpanBuilderInterface ceremony (start, set attributes, catch, record exception, set status, end) is noisy and easy to get wrong in the finally. Wrap it:

app(Tracer::class)->span('invoice.generate', function (Span $span) use ($invoice) {
    $span->setAttribute('invoice.id', $invoice->id);
    return $this->generator->run($invoice);
});

The helper must end the span in finally, call recordException() on throw, and mark the status ERROR. Every unbounded $span->end() you forget is a leak in a long-running worker.

Propagate context in both directions. Incoming: extract traceparent/tracestate from the request header, Context::attach() for the request lifetime, detach in a terminating callback. Outgoing: inject into Guzzle headers and into queued job payloads. For queued jobs, serialize the traceparent as a job property and re-attach in the worker — this is the single highest-value thing the bundle does for Symfony Messenger (and the thing beta.7 finally instruments end-to-end), and it is the thing people forget in Laravel.

Resource attributes should be config-driven, not hardcoded. Map service.name to config('app.name'), deployment.environment to APP_ENV, service.version to a git SHA baked at deploy time. This is what makes traces searchable across staging and prod without guessing.

Exporter topology: app → OTLP → collector → backend. Never point the app directly at Jaeger/Tempo/Datadog. The collector gives you tail sampling, batching, retries, and vendor swaps without redeploying PHP. Use grpc on 4317 or http/protobuf on 4318 — pick one and match the collector config.

Processor choice by environment. simple processor (synchronous) in tests and local debugging; batch processor in production so export never sits in the request path. Tune maxQueueSize and scheduleDelayMillis if you're generating hundreds of spans per request — the default queue will drop spans silently under load.

Test with an in-memory exporter. Assert on span names and a few key attributes rather than the whole tree. Keep OTEL_SDK_DISABLED=true (or a null tracer binding) in phpunit.xml so the suite doesn't block on network calls. The bundle's own suite moved to PHPUnit ^13 in this release — if you crib their test patterns for span assertions, note the newer assertion API and attribute syntax; don't copy snippets that assume PHPUnit 10 idioms.

Gotchas and Tips

Symfony DI dependency creep. composer require-ing this into Laravel pulls symfony/dependency-injection, symfony/http-kernel, and friends. It won't boot. Either port the wiring or use a Laravel-native package; don't try to bridge it.

Two sources of truth for config. The OTel PHP SDK can be configured via OTEL_* environment variables (the SDK's autoloader reads them), and via the bundle's config tree. If both are present you will spend an afternoon wondering why your sampler change did nothing. Pick one — env vars for infrastructure-ish settings (endpoint, headers, sampler ratio), bundle/provider config for resource attributes.

PHP extension auto-instrumentation collides with library instrumentation. If the opentelemetry PECL extension plus open-telemetry/opentelemetry-auto-* packages are installed, you'll get duplicate DB and curl spans on top of whatever the bundle/provider registers. Choose one mechanism per signal.

Sampler semantics. always_on is a dev-only luxury. In prod use parent-based + trace-id-ratio, and remember head sampling means you've already thrown spans away before the collector sees them — tail sampling in the collector is how you keep all errors and slow traces. The bundle exposes sampler configuration; check whether parent_based is actually composing with the ratio sampler as you expect, because a misconfigured parent-based sampler with no root sampler silently samples 100%.

Long-running processes (queue workers, Octane, Messenger consumers). This deserves extra attention now that beta.7 instruments Messenger workers directly: the bundle hooks kernel/console terminate to flush, and a worker that never terminates never flushes. Flush or force-flush per job (the worker instrumentation is the place to see how), watch queue-depth metrics on the batch processor, and be aware that Context detach bugs manifest as memory growth over hours, not seconds. If you copy the Messenger worker pattern into a Laravel queue:work wrapper, make sure you detach context in a finally around each job and handle SIGTERM — queue:restart mid-job otherwise drops spans.

Retries and links. If your consumer span is a child of the producer span, a job retried three times produces three identical-looking child spans and a misleading trace. Use links for messages that may be redelivered; the Messenger worker instrumentation is a good reference for the decision.

No traces arriving — the debugging ladder. In order: (1) is OTEL_SDK_DISABLED set anywhere in the env? (2) endpoint port/protocol mismatch (4317 gRPC vs 4318 HTTP is the classic); (3) TLS/insecure flag; (4) collector logs — did the collector receive anything; (5) sampler returning DROP; (6) did the span actually get end()ed (unended spans never export); (7) batch processor queue overflow dropping spans. Turn on the console exporter for one request to bisect SDK vs. transport.

Attribute cardinality. Trace attributes tolerate high cardinality; metric attributes do not. Don't put user IDs, raw SQL, or ID-bearing URLs on metrics — you'll blow up the backend's time-series count. Keep raw SQL on the DB span only, and truncate it. The same applies to job payloads: log the class and ID, never the serialized payload.

Semantic convention churn. sem-conv renamed a lot between versions (http.method → http.request.method, db.system → db.system.name), and messaging conventions have been in flux too. Pin open-telemetry/sem-conv and check what your bundle version expects; a mismatch means your dashboards query attributes that no longer exist. Beta.7's worker instrumentation may introduce attribute names that shift in beta.8 — centralise them in one constants class in your port so a rename is a one-file change.

Beta means beta. v1.0.0-beta.x to beta.x+1 can move service definitions or config keys without a major-version bump, and this release already touched the test suite and added a new instrumentation area. Treat upgrades as code review, not composer update. The dev-only changes in beta.7 (PHPUnit 13, action bumps) carry no runtime risk, so there's no urgency to upgrade unless you want the Messenger worker instrumentation.

Extension points worth knowing. The bundle lets you plug custom services: a SpanProcessor for redaction, a Sampler for business-aware sampling, a ResourceDetector for container/host metadata, and exporter service IDs for a custom OTLP target. If your config tree exposes processors/samplers/exporters as service references, decorating them is cleaner than forking the bundle. In your Laravel port, replicate that: bind interfaces (TracerProviderInterface, MeterProviderInterface) in the container so tests and future changes can swap implementations.

Still no dependents to speak of. Treat this as a codebase to read rather than a dependency to trust for critical-path instrumentation — especially with a single-contributor feature landing in a beta. That's fine; its real value to a Laravel developer is as a well-organized, MIT-licensed blueprint for how to wire OpenTelemetry into a PHP framework properly, and beta.7 improves that blueprint materially by adding the worker/consumer half of the queue story.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels