Observability

OpenTelemetry in a Laravel + Node.js Stack — A Practical Guide

The Problem with Polyglot Observability

Before OpenTelemetry, we had:

  • PHP/Laravel → custom log parser feeding Elastic
  • Node.js services → Winston logs to CloudWatch
  • Go services → nothing (it was new)

Three different systems, no correlation between a frontend request and what happened across six downstream services. Debugging a slow booking flow meant opening four browser tabs and mentally joining log lines by timestamp.


What We Wanted

A single trace that follows a user request from the Laravel API gateway, through the Node.js availability service, into the Go pricing engine — with spans, errors, and timing at every hop.

[Laravel] → [Node.js Availability] → [Go Pricing] → [MySQL]
   |              |                       |              |
 200ms          45ms                    12ms           8ms

Instrumenting Laravel

We used the open-telemetry/opentelemetry-php SDK. The key is auto-instrumentation for HTTP and PDO:

// config/otel.php
return [
    'dsn' => env('OTEL_EXPORTER_OTLP_ENDPOINT', 'http://otel-collector:4318'),
    'service_name' => env('OTEL_SERVICE_NAME', 'booking-api'),
];
// AppServiceProvider.php
use OpenTelemetry\SDK\Trace\TracerProviderFactory;

public function boot(): void
{
    $tracerProvider = (new TracerProviderFactory())->create();
    
    // Register globally — auto-instrumentation picks this up
    \OpenTelemetry\API\Globals::registerInitializer(
        fn() => $tracerProvider
    );
}

Results

MetricBeforeAfter
Mean debug time for cross-service issue45 min4 min
P95 trace coverage0%94%
Alert MTTR38 min11 min

The first week after rollout we found three latency issues we didn’t know existed. One of them had been silently degrading the checkout flow for four months.