|Docs

Tracing for PHP

PHP apps export traces to Railway with the OpenTelemetry PHP SDK and its auto-instrumentation packages. The opentelemetry PHP extension hooks into function calls, and framework packages use those hooks to trace requests without code changes.

Before you start, enable tracing for the service. Railway adds the OTEL_* variables on the next deploy, and the SDK reads them.

Install the extension

Auto-instrumentation needs the opentelemetry PHP extension. Install it in your Dockerfile with PECL, or with the install-php-extensions helper from the official PHP images:

Dockerfile
FROM php:8.3-cli

RUN pecl install opentelemetry \
    && docker-php-ext-enable opentelemetry

Check that the extension loads with php -m | grep opentelemetry during the build.

Install the packages

Add the SDK, the OTLP exporter, and the auto-instrumentation package for your framework:

composer require open-telemetry/sdk \
  open-telemetry/exporter-otlp \
  open-telemetry/opentelemetry-auto-laravel

Use open-telemetry/opentelemetry-auto-symfony for Symfony or open-telemetry/opentelemetry-auto-slim for Slim. Add open-telemetry/opentelemetry-auto-psr18 to trace outgoing HTTP calls made through a PSR-18 client, and open-telemetry/opentelemetry-auto-pdo for database queries.

Configure the SDK

The SDK configures itself from environment variables when autoloading is enabled. Set these on the service alongside the variables Railway provides:

OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_TRACES_EXPORTER=otlp

OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_HEADERS, and OTEL_SERVICE_NAME come from Railway. The SDK exports traces only unless you enable other exporters.

Laravel

Each incoming request produces a server span named after the HTTP method and the matched route, with http.route and http.response.status_code attributes. The instrumentation reads the traceparent header the edge sends, so the span joins the edge's trace. Middleware, controller actions, Eloquent queries, and queued jobs appear as child spans.

PHP exports spans at the end of each request. In a long-running process such as Laravel Octane or a queue worker, the SDK batches spans and exports them periodically.

Add a custom span

use OpenTelemetry\API\Globals;

$tracer = Globals::tracerProvider()->getTracer('checkout');

function calculateTotal(Cart $cart, $tracer): int
{
    $span = $tracer->spanBuilder('calculate-total')->startSpan();
    $scope = $span->activate();
    try {
        $span->setAttribute('cart.items', count($cart->items));
        return priceItems($cart);
    } catch (\Throwable $e) {
        $span->recordException($e);
        $span->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_ERROR);
        throw $e;
    } finally {
        $scope->detach();
        $span->end();
    }
}

The span becomes a child of the request span that's active when the function runs.

Verify

  1. Deploy the service and send it a few requests.
  2. Open the Traces page. In the Tracing setup panel, the App indicator for the service turns green when its first span arrives.
  3. Open a trace. The request's server span and any child spans appear under the edge and proxy spans.

If spans from the app don't arrive, see Troubleshooting. Set OTEL_LOG_LEVEL=debug on the service to have the SDK log its configuration and export attempts.