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

Tracing Laravel Laravel Package

vinelab/tracing-laravel

Laravel integration for distributed tracing with Vinelab Tracing. Automatically traces incoming HTTP requests and application flow, helping you add request-scoped spans and propagate trace context across services with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up

composer require vinelab/tracing-laravel
php artisan vendor:publish --provider="Vinelab\Tracing\TracingServiceProvider"

The package is a thin OpenTracing façade with Jaeger and Zipkin drivers. There is almost nothing to "learn" — the surface area is the Tracing facade, one middleware, and a config file — so read config/tracing.php first, then src/Tracing.php. That is the entire library.

Minimum viable config (.env):

TRACING_ENABLED=true
TRACING_DRIVER=jaeger
TRACING_SERVICE_NAME="${APP_NAME}"
TRACING_JAEGER_AGENT_HOST=127.0.0.1
TRACING_JAEGER_AGENT_PORT=6831
TRACING_JAEGER_SAMPLER_TYPE=const
TRACING_JAEGER_SAMPLER_PARAM=1

For Zipkin instead, switch TRACING_DRIVER=zipkin and set the collector endpoint (default http://localhost:9411/api/v2/spans).

First use case: trace every HTTP request. Register the package middleware globally in app/Http/Kernel.php:

protected $middleware = [
    \Vinelab\Tracing\Http\Middleware\Tracing::class,
    // ...
];

This opens a span for the request, tags it with method/URL/status, and finishes it in a terminate()/finally path. Start there and confirm the span lands in your Jaeger UI before writing a single line of manual instrumentation.

Then add one manual span to prove the API out:

use Vinelab\Tracing\Facades\Tracing;

Tracing::startSpan('user.register');
Tracing::addTag('user.id', $user->id);
Tracing::finishSpan();

If you see two spans, you're done getting started.

Implementation Patterns

Manual span around a unit of work. The façade tracks a single active span; every startSpan() becomes the current parent and finishSpan() pops it. Keep start/finish in a try/finally so exceptions don't leak an unfinished span:

Tracing::startSpan('billing.charge');
try {
    Tracing::addTag('billing.amount_cents', $cents);
    $result = $gateway->charge($cents);
    Tracing::addTag('billing.charge_id', $result->id);
} catch (\Throwable $e) {
    Tracing::addTag('error', true);
    Tracing::addTag('error.kind', get_class($e));
    Tracing::log(['event' => 'error', 'message' => $e->getMessage()]);
    throw $e;
} finally {
    Tracing::finishSpan();
}

Nesting is implicit. Inside a controller that is already traced by the middleware, calling startSpan() creates a child of the request span. Don't try to pass spans around manually — the active-span stack is the whole model. This is also its main constraint: anything running outside the synchronous request (queue workers, Swoole/Octane coroutines, long-running daemons) shares one process-global active span and will mis-parent unless you reset it.

Propagation to outbound HTTP calls. This is the feature that actually justifies the package. Use the context-injection helper before calling a downstream service, and mirror the headers on the receiving side:

$headers = [];
Tracing::startSpan('http.outbound:inventory');
Tracing::addTag('http.url', $url);
try {
    $response = Http::withHeaders($headers)->get($url); // headers populated by inject helper
    Tracing::addTag('http.status_code', $response->status());
} finally {
    Tracing::finishSpan();
}

Check the README for the exact method name (startSpanAndInjectContext / an injectContext(&$headers)-style call) — it's one of the few places where the API is not obvious and has changed between minor versions.

Queues and jobs. The middleware only covers the HTTP lifecycle. To carry a trace into a job, capture the current context when dispatching, serialize it onto the job, and re-open a span with that context as parent inside handle(). The package gives you the Tracer (Tracing::getTracer()) and the low-level OpenTracing SpanContext — do the extract/inject yourself in a small trait; there's no built-in queue integration.

Disable in tests and local dev. Set TRACING_ENABLED=false (or point the driver at the no-op implementation if one is bound) in phpunit.xml. Instrumented code should never need if (app()->environment('testing')) guards — if the facade isn't a no-op when disabled, wrap it in your own tiny helper class so tests don't pay for span allocation.

Centralise instrumentation. The façade is static and called inline, so it spreads fast. Put spans in service-layer methods or in middleware/job middleware rather than in controllers, and adopt a small set of tag names (http.status_code, db.statement, error.kind) early — an inconsistent tag vocabulary makes the Jaeger UI useless.

Gotchas and Tips

  • This is OpenTracing, not OpenTelemetry. OpenTracing was deprecated in favour of OTel, and opentracing/opentracing-php is abandoned. Anything you instrument here will need re-instrumenting when you move to open-telemetry/opentelemetry-php. Treat this package as a stopgap, not a foundation.
  • Check version constraints before committing. Last release was Nov 2023 and the package is small (81 stars, low activity). Verify its composer.json accepts your Laravel/PHP versions and that jonahgeorge/jaeger-client-php / openzipkin/zipkin-php resolve cleanly — dependency conflicts here are the most common install failure.
  • Unfinished spans silently vanish. Jaeger/Zipkin only report spans that were finished. If a span never appears, the first thing to check is a missing finishSpan() on an exception path — not your agent host. This is the #1 debugging time-sink.
  • Middleware ordering matters. Put the tracing middleware at the very top of the global stack so it wraps everything, including exception handlers and other middleware. If it lands after StartSession/auth, those spans disappear from the waterfall.
  • Jaeger agent is UDP. Port 6831 (compact thrift over UDP) drops packets silently when nothing is listening — you get no error, just no traces. Point at the agent container and check docker logs on it; don't expect the app to complain. Zipkin's HTTP reporter is easier to debug for this reason.
  • Sampling. TRACING_JAEGER_SAMPLER_TYPE=const with param 1 traces everything — fine locally, expensive in production. Switch to probabilistic with a small param, or remote if you're running the Jaeger collector's adaptive sampler.
  • Config is cached. After changing config/tracing.php or env vars in a deployed environment, run php artisan config:cache again or the old driver/sampler sticks around.
  • Extension point: swap the tracer via Tracing::setTracer(...) (or rebind the container binding) to inject a test double or a custom-propagating tracer without forking the package. The façade is a thin proxy, so this is genuinely the intended escape hatch.
  • Don't over-tag. Tags on every loop iteration produce enormous spans that Jaeger truncates or rejects. Tag the aggregate (row counts, byte sizes), not the individual items, and prefer log() for point-in-time events.
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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core