micro-module/observability-bundle
Observability bundle for Laravel/PHP applications that centralizes logging, metrics, and tracing configuration. Provides opinionated defaults and simple setup so teams can monitor requests, errors, and performance without wiring multiple packages.
First, confirm the fit. This is a Symfony bundle — it ships a Bundle class, a DependencyInjection\Extension with a Configuration tree, and compiler passes. Laravel will not auto-discover it, will not read its config tree, and will not dispatch its Symfony event subscribers. You are not consuming this package so much as reading it and porting its loading model. Budget for that before installing anything.
Minimal evaluation path:
composer require micro-module/observability-bundle in a scratch Symfony skeleton (not your Laravel app) just to see the config surface. Read, in this order: the bundle class, DependencyInjection/*Extension.php, DependencyInjection/Configuration.php, then the compiler passes — that is where the "conditional subsystem loading" actually lives.open-telemetry/opentelemetry-php plus open-telemetry/exporter-otlp and run a local collector (OTel Collector → Jaeger/Tempo). Do not point at a hosted backend until you see spans locally.service.name and deployment.environment as resource attributes. Leave metrics and logs disabled until traces are clean — that is the same discipline the bundle's conditional loading is trying to enforce.If the port looks like more than a day of work, fall back to open-telemetry/opentelemetry-php directly and treat this repo as a reference architecture, not a dependency. With zero stars and zero dependents you have no upstream safety net either way.
Mirror the conditional-loading idea with a Laravel config file. The bundle's core value is that a disabled subsystem costs nothing at container build time. Reproduce that with config/observability.php (tracing.enabled, metrics.enabled, logs.enabled) and a service provider that only binds the tracer/exporter/middleware when the flag is true. Resolve everything as singletons so one TracerProvider and one MeterProvider exist per process.
HTTP server spans — middleware wrapped around the request, span started on handle(), ended and status set in a finally, attributes for route, method, status code after the response is built. Use the route pattern (users/{id}), not the resolved URL, or you get unbounded cardinality.
Queue jobs — this is the Symfony Messenger-to-Laravel-Queue translation. Hook JobProcessing / JobProcessed / JobFailed and create a consumer span per job, with the trace context serialized onto the payload when dispatching so the producer and consumer spans link. Without the propagation step you get two disconnected traces and the feature looks broken.
Database and outbound HTTP — DB::listen for query spans (statement template only, never bound values), and Http::globalRequestMiddleware / a Guzzle middleware for client spans with W3C traceparent injected. Keep both behind their own config flags; on a write-heavy app these are the two hooks that will actually cost you throughput.
Console — wrap Artisan via Artisan::starting so scheduled commands produce spans; otherwise your cron noise is invisible and you'll misattribute latency.
Export pipeline — BatchSpanProcessor, OTLP over HTTP/protobuf unless you already run gRPC infra. Register a flush on app()->terminating() so short-lived requests and queue workers don't drop the final batch.
Propagation — extract inbound traceparent in the HTTP middleware, inject outbound on HTTP clients and queue dispatches. Set W3C TraceContext + Baggage as the propagator explicitly; the defaults across SDK versions have moved around.
extra.laravel.providers, no php artisan vendor:publish, no Laravel-shaped config. Any blog post or README example for this bundle will be Symfony YAML that does not translate. Expect to rewrite the wiring, not configure it.php artisan about won't help — add a temporary app()->bound(...) assertion).semconv constants rather than string keys like http.status_code; the HTTP attribute names were reorganized in recent SDK releases and string literals will silently land in the wrong place.stderr but not in your backend, it's an exporter/auth/endpoint problem, not an instrumentation problem — that distinction saves the most time.SpanProcessor for redaction or enrichment, ResourceDetector for host/container attributes, and a custom sampler if tail-based sampling isn't available. All three exist in the SDK and are where the bundle's opinionated bits are least likely to be worth porting.How can I help you explore Laravel packages today?