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

Zipkin Instrumentation Symfony Laravel Package

jcchavezs/zipkin-instrumentation-symfony

A Symfony bundle for Zipkin distributed tracing that instruments HTTP requests and application spans, helping you collect and report timing data to a Zipkin server for performance monitoring and debugging across services.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Verify installability before you write any code. Release 3.1.0 raises the tested matrix to PHP 7.4–8.1 on Symfony 4.4/5.x, so PHP 8.1 is now a supported target. Start with a dependency check, not a require:

composer require jcchavezs/zipkin-instrumentation-symfony --dry-run

Expect it to resolve cleanly on Symfony 4.4/5.x and PHP 7.4–8.1. On Symfony 6/7 or PHP 8.2+ you will still hit constraint conflicts — 3.1.0 added no new Symfony support, only PHP. Decide up front whether you are forking the bundle or pinning an older Symfony. Don't discover this in a sprint.

2. Register the bundle. This is the Symfony equivalent of adding a service provider to config/app.php. In config/bundles.php:

return [
    // ...
    Zipkin\Instrumentation\Symfony\Bundle\ZipkinBundle::class => ['all' => true],
];

3. Point it at a collector. Run one locally first:

docker run -d -p 9411:9411 openzipkin/zipkin

Then config/packages/zipkin.yaml:

zipkin:
    service_name: my-app
    reporter:
        endpoint: '%env(ZIPKIN_ENDPOINT)%'   # http://localhost:9411/api/v2/spans
    sampler:
        type: percentage
        rate: 0.1

4. Where to look first. Open vendor/jcchavezs/zipkin-instrumentation-symfony/src and read DependencyInjection/Configuration.php, ZipkinBundle.php, and the event listeners. The bundle is a thin wiring layer over openzipkin/zipkin (v3.0 as of this release); almost everything you will want to customise is a service definition. Run bin/console debug:container zipkin and debug:container --tag=kernel.event_listener | grep -i zipkin to see exactly what got registered — this is the fastest way to confirm the bundle actually booted.

5. First useful outcome. Hit any controller and open http://localhost:9411, select your service name, and you should see one server span per request. If you see nothing, the sampler or reporter is the culprit (see Gotchas), not your controllers.

Implementation Patterns

Let the kernel listener do the boring 90%. The bundle instruments kernel.request/kernel.response/kernel.exception, so every request already has a span with method, path, status code, and duration. Don't hand-roll root spans in controllers; add child spans instead.

Manual spans in a service. Inject Zipkin\Tracing (the bundle's main service) and use the tracer:

public function __construct(private \Zipkin\Tracing $tracing) {}

public function priceCart(Cart $cart): Money
{
    $tracer = $this->tracing->getTracer();
    $span = $tracer->nextSpan()->setName('cart.price');
    $span->start();
    $scope = $tracer->openScope($span);

    try {
        $result = $this->doWork($cart);
        $span->tag('cart.items', (string) $cart->count());
        return $result;
    } catch (\Throwable $e) {
        $span->tag('error', $e->getMessage());
        throw $e;
    } finally {
        $scope->close();
        $span->finish();
    }
}

The Scope wrapper is the key habit: it makes the span the current span so nested work (DB calls, HTTP calls) attaches to it rather than starting a new trace. Always close it in finally.

Tag and annotate for searchability. $span->tag('user.id', $id), $span->tag('cache.hit', 'true'), $span->annotate('payment.authorized'). Tags are indexed in the Zipkin UI and are what you'll actually query during an incident; timestamps-only spans are nearly useless.

Propagate B3 to downstream services. For outbound calls you must inject the X-B3-TraceId/SpanId/ParentSpanId/Sampled headers, otherwise each hop becomes a separate orphan trace. Use the propagation API from the parent library:

$injector = $this->tracing->getPropagation()->getInjector(new \Zipkin\Propagation\Map());
$headers = [];
$injector($span->getContext(), $headers);

Then pass $headers to your HTTP client. If the bundle ships an HTTP-client decorator, prefer it — it's less error-prone than hand-wiring every call site. Do the extraction (getExtractor) on the server side if you sit behind a gateway, or rely on the kernel listener which already reads inbound B3 headers.

Instrument DB and HTTP edges. Enable the bundled Doctrine DBAL middleware (or wrap the connection) so SQL appears as child spans. That is usually where the actual latency lives, and it's the single highest-value instrumentation after the root span.

Long-running workers (Messenger, custom daemons). A kernel.request span does not exist in a worker, so wrap each message handler in a root span and, critically, call the reporter's flush() periodically. A CurlReporter/HTTP reporter buffers spans; in a process that never ends, they may never ship. Either flush on a timer or after each batch.

Test and dev environments. Decorate zipkin.tracing with a no-op, or swap the reporter for a log/console reporter, so you don't pollute your collector — and more importantly so CI doesn't attempt network calls. In Symfony this is a services_test.yaml override:

services:
    zipkin.tracing:
        class: Zipkin\NoopTracing

Gotchas and Tips

  • PHP 8.2+/Symfony 6+ is still unsupported. 3.1.0 is explicitly a compatibility chore release: it adds PHP 8.0/8.1 and pins openzipkin/zipkin 3.0 for PHP 7.4/8.0/8.1. It does not address Symfony 6's EventDispatcherInterface signature changes or PHP 8.2 deprecations. On a modern stack you still need a fork or a patched listener. Budget a day for this, or pick a maintained alternative.
  • The Zipkin 3.0 bump is the real upgrade risk, not the bundle. 3.0.1 → 3.1.0 is small in the bundle itself, but your transitive openzipkin/zipkin version will jump. Diff the parent library's changelog, re-run your span-creating code paths, and confirm getTracer()/getPropagation()/openScope() behave as before if you jump from a 2.x lockfile. Lock the parent lib explicitly if you have hand-written instrumentation.
  • Silent no-ops. Tracing is built lazily and the default sampler may be "never" or misconfigured. If debug:container shows the service but no spans arrive, check the sampler first, then the reporter endpoint (including the /api/v2/spans path — a missing path returns 404 and it's easy to miss in logs).
  • Sampling in dev vs prod. type: always in dev is fine; leaving it in prod floods your collector. Use percentage with a low rate in production, and remember sampling decisions propagate — a 10% sample means 10% of full traces, not 10% of spans.
  • Set service_name explicitly. Without it, spans land under an unhelpful default and you can't tell environments apart. Prefer my-app-production style names, or tag with deployment.environment.
  • Reporter must not block the request. Ensure the reporter is asynchronous (curl multi / non-blocking handler). If a collector outage adds latency to every request, you've made your app depend on your tracing infra — a classic self-inflicted outage.
  • Span naming at kernel.request is generic. The root span fires before routing, so its name may be the HTTP method or bundle name, not the controller. Rename it in a kernel.controller listener if you want GET /orders/{id}-style names in the UI.
  • Context leakage. In PHP-FPM each request is a fresh process, so leaks are rare; in workers they are guaranteed. If you see spans from job A parented under job B, you've failed to close a Scope — search for missing close()/finish() in try blocks without finally.
  • 128-bit trace IDs. If your platform's load balancer or gateway generates 128-bit IDs, enable the corresponding config flag (roughly trace_id_128bit: true) or propagation will silently drop/reject inbound contexts.
  • Extension points. The clean places to customise are: your own Zipkin\Sampler, your own Reporter, a decorator over zipkin.tracing, and custom event listeners ordered before/after the bundle's. Avoid patching vendor — these hooks cover almost everything.
  • Debugging tip. When in doubt, register a reporter that writes spans to the Symfony logger and watch var/log/dev.log. You'll see immediately whether spans are created (instrumentation problem) or created-but-not-sent (reporter/sampler problem). Those are two very different bugs.
  • Release cadence is slow — plan for it. 3.1.0 is a narrow maintenance release from a project with sporadic activity. Pin your versions exactly, read the changelog on every bump, and don't assume a future release will land before your next Symfony/PHP upgrade.
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