open-telemetry/exporter-zipkin
OpenTelemetry Zipkin Exporter for PHP. Sends OpenTelemetry traces to a Zipkin collector for storage and visualization. Use with the OpenTelemetry PHP SDK to export spans over HTTP, enabling distributed tracing and diagnostics in your applications.
composer require open-telemetry/exporter-zipkin
AppServiceProvider):
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\Contrib\Zipkin\Exporter\ZipkinExporter;
public function boot()
{
$zipkinExporter = new ZipkinExporter('http://your-zipkin-server:9411/api/v2/spans');
$spanProcessor = new BatchSpanProcessor($zipkinExporter);
\OpenTelemetry\API\Common\GlobalRegistry::getTraceProvider()->addSpanProcessor($spanProcessor);
}
use OpenTelemetry\API\Trace\TracerInterface;
public function handle(TracerInterface $tracer)
{
$span = $tracer->spanBuilder('process-job')->startSpan();
try {
// Your job logic here
$span->setAttribute('job.status', 'success');
} finally {
$span->end();
}
}
Add middleware to auto-instrument HTTP requests:
use OpenTelemetry\API\Trace\TracerInterface;
class TraceMiddleware
{
public function handle($request, Closure $next, TracerInterface $tracer)
{
$span = $tracer->spanBuilder('http.request')->startSpan();
$span->setAttribute('http.method', $request->method());
$span->setAttribute('http.path', $request->path());
try {
$response = $next($request);
$span->setAttribute('http.status', $response->status());
return $response;
} finally {
$span->end();
}
}
}
Instrument Entry Points:
$tracer = \OpenTelemetry\API\Common\GlobalRegistry::getTraceProvider()->getTracer('my-app');
$span = $tracer->spanBuilder('event.processed')->startSpan();
Propagate Context:
W3C Trace Context headers).use OpenTelemetry\API\Trace\Propagation\TextMapPropagator;
$propagator = new TextMapPropagator();
$carrier = [];
$propagator->inject($carrier, $span->getSpanContext());
Http::withHeaders($carrier)->get('https://external-api.com');
Batch Exports:
Configure the BatchSpanProcessor for optimal performance:
$exporter = new ZipkinExporter('http://zipkin:9411/api/v2/spans', [
'batch_size' => 50, // Flush after 50 spans
'flush_interval' => 5000, // Flush every 5 seconds
'timeout' => 1000, // Timeout in milliseconds
]);
Laravel-Specific Attributes: Enrich spans with Laravel metadata:
$span->setAttribute('laravel.route', route()->currentRouteName());
$span->setAttribute('laravel.user.id', auth()->id());
handle() method of jobs to wrap execution in spans.
public function handle()
{
$tracer = app(TracerInterface::class);
$span = $tracer->spanBuilder('job.process')->startSpan();
try {
// Job logic
} finally {
$span->end();
}
}
query() hooks.
\DB::listen(function ($query) {
$tracer = app(TracerInterface::class);
$span = $tracer->spanBuilder('db.query')->startSpan();
$span->setAttribute('db.query', $query->sql);
// ... end span after query completes
});
Http::macro() to auto-instrument Guzzle requests.
Http::macro('withTrace', function ($url, $span) {
$propagator = new TextMapPropagator();
$carrier = [];
$propagator->inject($carrier, $span->getSpanContext());
return Http::withHeaders($carrier)->get($url);
});
Non-String Attributes:
Zipkin expects all tags/attributes to be strings. Non-string values (e.g., null, int, array) may cause serialization errors.
Fix: Cast attributes explicitly:
$span->setAttribute('user.id', (string) auth()->id());
Timeouts and Retries:
The exporter uses HTTP under the hood. If your Zipkin server is unreachable, spans may accumulate in memory.
Fix: Configure a reasonable timeout and flush_interval:
$exporter = new ZipkinExporter('http://zipkin:9411/api/v2/spans', [
'timeout' => 2000, // 2 seconds
'flush_interval' => 2000,
]);
PHP Version Mismatch:
The package drops support for PHP <8.1. Ensure your Laravel app is upgraded.
Fix: Update composer.json to require PHP 8.1+:
"require": {
"php": "^8.1"
}
Span Context Propagation: If traces appear disconnected across services, check:
TextMapPropagator is used consistently.
Debug: Log the traceparent header:\Log::debug('Trace Context:', ['traceparent' => $carrier['traceparent'] ?? 'missing']);
Zipkin Server Compatibility: Ensure your Zipkin server supports the v2 API (used by this exporter). Older servers may reject requests. Fix: Use a modern collector like Jaeger or Grafana Tempo.
http://your-zipkin-server:9411/
class LoggingZipkinExporter implements Exporter
{
private $exporter;
public function __construct(ZipkinExporter $exporter)
{
$this->exporter = $exporter;
}
public function export(array $spans)
{
try {
return $this->exporter->export($spans);
} catch (\Exception $e) {
\Log::error('Zipkin export failed', ['error' => $e->getMessage()]);
throw $e;
}
}
}
SpanProcessor to validate spans before export:
$processor = new class implements SpanProcessor {
public function onStart(Span $span) {}
public function onEnd(Span $span) {
if (empty($span->getName())) {
\Log::warning('Span without name', ['span_id' => $span->getSpanContext()->getSpanId()]);
}
}
};
Custom Span Attributes: Add Laravel-specific attributes via a trait or helper:
trait Traceable
{
protected function startSpan(string $name): Span
{
$span = app(TracerInterface::class)->spanBuilder($name)->startSpan();
$span->setAttribute('laravel.instance', config('app.name'));
return $span;
}
}
Dynamic Endpoint Configuration:
Use Laravel’s config() to manage the Zipkin endpoint:
$exporter = new ZipkinExporter(config('telemetry.zipkin.endpoint'));
Sampling: Implement custom sampling logic to reduce trace volume:
$provider = new TraceProvider([
'sampler' => new AlwaysOnSampler(), // or custom sampler
'span_processor' => new BatchSpanProcessor($exporter),
]);
Async Transport: For high-throughput apps, use an async HTTP client (e.g., `php-http/async-client-implementation
How can I help you explore Laravel packages today?