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

Otel Logging Laravel Package

changole/otel-logging

OpenTelemetry logging for Laravel. Add an OTLP log channel to Laravel’s logging stack so application logs flow to OpenTelemetry collectors and compatible backends, with structured context and trace correlation for easier observability.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up

composer require changole/otel-logging

The package is a Monolog handler, so the integration surface is Laravel's logging config, not a facade. Expect the flow to be:

  1. Publish the config (check README for the exact tag — commonly --tag=otel-logging-config or --tag=otel-config).
  2. Add a channel to config/logging.php using the package's driver name (likely otel), e.g.:
'channels' => [
    'otel' => [
        'driver'  => 'otel',
        'level'   => env('LOG_LEVEL', 'info'),
        // endpoint/service name usually read from OTEL_* env vars
    ],
    'stack' => [
        'driver'   => 'stack',
        'channels' => ['single', 'otel'],
    ],
],
  1. Set the standard OTel env vars, which well-behaved OTel SDK wrappers honor automatically:
OTEL_SERVICE_NAME=my-app
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer ..."

Where to look first

  • src/ — the Monolog handler class and the service provider that registers the driver via Log::extend(). Read those two files before anything else; this package is small enough that the source is the documentation.
  • config/ — the publishable config file tells you which knobs exist (endpoint, headers, resource attributes, level, batching).
  • composer.json — critical here. Check the required open-telemetry/* versions and the illuminate/* constraints, since the package has only 5 stars and no dependents; you are the de facto QA.

First use case

The highest-value adoption path is not replacing your file logs. It is adding the otel channel to your stack alongside single/daily, so local debugging stays intact while logs ship to the collector and get correlated with traces in Grafana Tempo, Jaeger, or SigNoz. Prove it end-to-end with one Log::info('hello', ['user_id' => 1]) and confirm it lands in the backend before touching production config.

Implementation Patterns

Stack, don't swap. Keep single first in the stack for local/tail -f ergonomics, and let otel be additive. Developers lose the ability to tail -f storage/logs/laravel.log the moment you replace the channel outright.

Trace correlation is the whole point. The value of OTLP logs over plain JSON logs is that a log record carries trace_id/span_id and the backend can jump from a slow trace to its log lines. That only works if the OpenTelemetry PHP SDK is bootstrapped and a span is active when the log is written:

$tracer = Globals::tracerProvider()->getTracer('app');
$span = $tracer->spanBuilder('checkout')->startSpan();
$scope = $span->activate();

Log::info('charging card', ['amount' => $total]); // carries trace_id
$scope->detach();
$span->end();

If your app isn't instrumented with the SDK auto-instrumentation or manual spans, this package degrades into "HTTP log shipper" — still useful, but you're paying the latency cost for less benefit. Plan to adopt open-telemetry/sdk + open-telemetry/exporter-otlp and OTel's Laravel auto-instrumentation in the same initiative.

Route exceptions and queue failures explicitly. Laravel's exception handler logs to the default channel; if LOG_CHANNEL=single you won't get exceptions in OTLP. Either set LOG_CHANNEL=stack, or override report() in bootstrap/app.php (Laravel 11+) / App\Exceptions\Handler to Log::channel('otel')->error(...). Same for failed_jobs and Horizon — they have their own logging paths.

Attach resource attributes via the collector, not the app. Service name, environment, and version belong on the OTel Resource. If the package exposes resource config, set it; otherwise let the collector's resource processor add deployment.environment and service.version so you don't hardcode env-specific values in PHP config.

Use structured context deliberately. Pass scalar, low-cardinality keys:

Log::channel('otel')->info('order.created', [
    'order_id' => $order->id,
    'tenant'   => tenant()->slug,
    'amount'   => $order->total_cents,
]);

Backends index log attributes; high-cardinality values (raw request bodies, full stack traces as attributes) blow up storage and query cost.

Run an OTel Collector between app and backend. Point OTEL_EXPORTER_OTLP_ENDPOINT at a local sidecar/daemonset collector, not directly at your SaaS vendor. The collector gives you retries, batching, sampling, PII scrubbing, and the ability to fan out to two backends — all without redeploying the app. It also insulates you from this package's limitations.

Gate it per-environment. Ship OTLP logs from staging and production only. In local dev and CI, keep the otel channel out of the stack (or set OTEL_SDK_DISABLED=true) so tests don't attempt network calls and so test suites don't slow down.

Gotchas and Tips

License is NOASSERTION and there are zero dependents. This is the single biggest risk. Before adopting, open LICENSE in the repo — "NOASSERTION" from GitHub's detector usually means a custom, missing, or malformed license file. If there's no clear permissive license, get legal sign-off, or accept that you may need to vendor/fork it. Given the handler is likely 100–200 lines, the fallback is cheap: a custom Monolog handler wrapping open-telemetry/exporter-otlp's LoggerProvider is a few hours of work and removes the supply-chain unknown entirely. Budget for that option.

Version-skew with the OTel SDK. The PHP OTel SDK was in beta for a long time and made breaking changes across 1.0/1.1. If composer.json allows a wide range or pins an old version, you may collide with the auto-instrumentation package. Run composer why open-telemetry/sdk after install and pin explicitly in your root composer.json.

Logs silently dropped at request end. Batch processors buffer in memory and flush on shutdown. Under PHP-FPM, if the process is killed or the shutdown handler isn't registered (common with Octane, or when another extension short-circuits shutdown), you lose the tail of every request — exactly the logs you care about for errors. Verify by triggering an error and checking the backend immediately; set OTEL_BSP_SCHEDULE_DELAY low (e.g. 500) in staging. If the package exposes a flush/shutdown method, call it from a terminating middleware.

Octane and long-lived workers. In Octane/RoadRunner/Swoole, static state persists between requests. Confirm the handler doesn't accumulate context or hold a stale span across requests — a leaked active span means every subsequent log is attributed to the wrong trace. Test with two sequential requests and compare trace_ids.

Blocking network I/O in the hot path. A synchronous OTLP HTTP export on every Log::info will add milliseconds to every request and can cascade failures if the collector is down. Prefer batch/async export, set short timeouts (OTEL_EXPORTER_OTLP_TIMEOUT=2), and make sure exporter failures are swallowed — a log shipper must never break a user request.

bubble semantics when stacking. Monolog handlers bubble by default, meaning a record passes to the next handler. If the otel handler sets bubble => false, records stop there and your single file log goes empty. Symptom: "logs stopped appearing in laravel.log after installing the package." Check the handler constructor's $bubble argument first.

Debugging the pipeline. Work outward from the app:

  1. LOG_CHANNEL=otel php artisan tinker → Log::info('ping') — no exception means the handler ran.
  2. Run the collector with --debug (or log exporter) and confirm the LogsData payload arrives.
  3. Only then look at the backend, and check its signal filters — many vendors require you to enable the logs signal separately from traces.

Context serialization surprises. Nested arrays and objects typically get JSON-encoded into a single string attribute, which breaks structured queries. Flatten before logging, and never pass an Exception object in context — it stringifies to a stack trace and inflates payload size. Use report($e) or the exception key in Laravel's usual shape.

Extension points to keep in your pocket. If the package's handler is registered via Log::extend('otel', fn() => new Handler(...)), you can override the binding in a service provider to inject your own Monolog Processor — useful for adding request_id, tenant, or user_id to every record without touching call sites. Write the processor once:

class RequestContextProcessor {
    public function __invoke(array $record): array {
        $record['extra'] += [
            'request_id' => app()->bound('request_id') ? app('request_id') : null,
            'user_id'    => auth()->id(),
        ];
        return $record;
    }
}

Cost control. OTLP log ingestion is priced per GB at most vendors. LOG_LEVEL=debug in production plus a verbose stack trace per exception is the classic way to get a surprise invoice. Set the otel channel's own level to warning while leaving the file channel at debug — you get full local detail and only actionable signal in the backend.

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.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json