Skip to main content

Laravel

Implement OpenTelemetry instrumentation for Laravel applications to enable comprehensive application performance monitoring (APM), distributed tracing, and observability. This guide shows you how to auto-instrument your Laravel application to collect traces and metrics from HTTP requests, database queries, background jobs, cache operations, and custom business logic using the OpenTelemetry PHP SDK.

Laravel applications benefit from automatic instrumentation of popular frameworks and libraries including Eloquent ORM, HTTP client requests (Guzzle), Redis, queue workers, and dozens of commonly used packages. With OpenTelemetry, you can monitor production performance, debug slow requests, trace distributed transactions across microservices, and identify database bottlenecks without significant code changes.

Whether you're implementing observability for the first time, migrating from commercial APM solutions, or troubleshooting performance issues in production, this guide provides production-ready configurations and best practices for Laravel OpenTelemetry instrumentation.

Note: This guide provides a practical Laravel-focused overview based on the official OpenTelemetry documentation. For complete PHP language information, please consult the official OpenTelemetry PHP documentation.

Who This Guide Is For

This documentation is designed for:

  • Laravel developers: implementing observability and distributed tracing for the first time
  • DevOps engineers: deploying Laravel applications with production monitoring requirements
  • Engineering teams: migrating from DataDog, New Relic, or other commercial APM solutions
  • Developers: debugging performance issues, slow database queries, or N+1 problems in Laravel applications
  • Platform teams: standardizing observability across multiple Laravel services

Overview

This comprehensive guide demonstrates how to:

  • Install and configure OpenTelemetry SDK and PHP extension for Laravel applications
  • Set up automatic instrumentation for HTTP requests, database queries, and popular packages
  • Configure production-ready telemetry export to Scout Collector
  • Implement custom instrumentation for business-critical operations
  • Collect and analyze traces, metrics, and performance data
  • Deploy instrumented Laravel applications to development, staging, and production environments
  • Troubleshoot common instrumentation issues and optimize performance
  • Secure sensitive data in telemetry exports

Prerequisites

Before starting, ensure you have:

  • PHP 8.0 or later (PHP 8.1+ recommended for best performance and compatibility)
    • For production deployments, PHP 8.2+ is recommended
    • JIT support in PHP 8.0+ improves instrumentation performance
  • Laravel 8.0 or later installed
    • Laravel 10.x or 11.x is recommended for optimal OpenTelemetry support
    • Laravel 8.x and 9.x are supported but may require additional configuration
  • Composer 2.0+ for dependency management
  • Scout Collector configured and accessible
    • See Docker Compose Setup for local development
    • Production deployments should use a dedicated Scout Collector instance
  • Build tools for compiling the OpenTelemetry PHP extension (gcc, make, autoconf)
  • Basic understanding of OpenTelemetry concepts (traces, spans, attributes)

Compatibility Matrix

ComponentMinimum VersionRecommended Version
PHP8.0.08.2.0+
Laravel8.0.011.0.0+
Composer2.0.02.7.0+
OpenTelemetry PHP Extension1.0.0Latest stable
OpenTelemetry SDK1.0.01.6.0+

Installation

Step 1: Install OpenTelemetry PHP Extension

The OpenTelemetry PHP extension provides automatic instrumentation capabilities.

Install Build Dependencies

# Ubuntu/Debian
sudo apt-get install gcc make autoconf

# Alpine Linux (Docker)
apk add --no-cache autoconf build-base

# macOS
xcode-select --install

Install Extension via PECL

pecl install opentelemetry

Enable Extension in php.ini

Add the extension to your php.ini file:

php.ini
[opentelemetry]
extension=opentelemetry.so

Verify Installation

php -m | grep opentelemetry

Expected output:

opentelemetry

Step 2: Install Required Packages

Install the necessary OpenTelemetry packages via Composer:

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

Optional packages for additional functionality:

# For PSR-18 HTTP client instrumentation (Guzzle, etc.)
composer require open-telemetry/opentelemetry-auto-psr18

# For complete auto-instrumentation (includes all available instrumentations)
composer require open-telemetry/opentelemetry-auto-slim

Configuration

OpenTelemetry Laravel instrumentation can be configured using multiple approaches depending on your deployment requirements and preferences. Choose the method that best fits your application architecture.

The recommended approach for Laravel is using environment variables. This provides flexibility and keeps configuration separate from your application code.

Configure .env File

.env
# OpenTelemetry Configuration
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_SERVICE_NAME=laravel-app
OTEL_SERVICE_VERSION=1.0.0
OTEL_TRACES_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
OTEL_PROPAGATORS=baggage,tracecontext

This configuration automatically instruments all supported Laravel components including:

  • Laravel Core: HTTP routing, middleware, controllers, views
  • Eloquent ORM: Database queries, model events
  • HTTP Clients: Guzzle, PSR-18 clients
  • Databases: MySQL, PostgreSQL, SQLite, SQL Server
  • Caching: Redis, Memcached, File cache
  • Queue Workers: Redis, Database, SQS queues
  • External APIs: HTTP requests with distributed trace propagation

Scout Collector Integration

When using Scout Collector, configure your Laravel application to send telemetry data to the Scout Collector endpoint with OAuth2 authentication:

.env
# Scout Collector Configuration
OTEL_EXPORTER_OTLP_ENDPOINT=https://your-tenant.base14.io/v1/traces
SCOUT_CLIENT_ID=your_client_id
SCOUT_CLIENT_SECRET=your_client_secret
SCOUT_TOKEN_URL=https://your-tenant.base14.io/oauth/token

# Service Configuration
OTEL_SERVICE_NAME=laravel-app
OTEL_SERVICE_VERSION=1.0.0
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_TRACES_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

Scout Dashboard Integration: After configuration, your traces will appear in the Scout Dashboard. Navigate to the Traces section to view request flows, identify performance bottlenecks, and analyze distributed transactions across your Laravel services.

Production Configuration

Production deployments require additional configuration for optimal performance, reliability, and resource utilization. This section covers production-specific settings and best practices.

Production Environment Variables

Create a production-optimized environment configuration:

.env.production
# Application Settings
APP_NAME=laravel-app-production
APP_ENV=production
APP_DEBUG=false

# OpenTelemetry Service Configuration
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_SERVICE_NAME=laravel-app
OTEL_SERVICE_VERSION=2.1.3
OTEL_SERVICE_NAMESPACE=production

# Scout Collector Endpoint
OTEL_EXPORTER_OTLP_ENDPOINT=https://scout-collector.example.com/v1/traces
SCOUT_CLIENT_ID=prod_client_id
SCOUT_CLIENT_SECRET=prod_secret_key
SCOUT_TOKEN_URL=https://scout-collector.example.com/oauth/token

# Exporter Settings
OTEL_TRACES_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_COMPRESSION=gzip
OTEL_EXPORTER_OTLP_TIMEOUT=10

# Propagators
OTEL_PROPAGATORS=baggage,tracecontext,b3

# Batch Span Processor Settings (Production Optimized)
OTEL_BSP_MAX_QUEUE_SIZE=2048
OTEL_BSP_SCHEDULE_DELAY_MILLIS=5000
OTEL_BSP_EXPORT_TIMEOUT_MILLIS=30000
OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512

# Resource Attributes
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,host.name=${HOSTNAME},cloud.provider=aws,cloud.region=us-east-1

Benefits of production configuration:

  • GZIP compression reduces network bandwidth by 70-80%
  • Batch processing minimizes network requests by 95%
  • Resource attributes enable filtering by environment and infrastructure
  • Configurable timeouts prevent hanging exports

Docker Production Configuration

For containerized Laravel applications, configure OpenTelemetry in your Docker setup:

Dockerfile
FROM php:8.2-fpm-alpine

# Install system dependencies
RUN apk add --no-cache \
autoconf \
build-base \
postgresql-dev \
libzip-dev \
zip \
unzip

# Install PHP extensions
RUN docker-php-ext-install pdo pdo_pgsql zip opcache

# Install OpenTelemetry extension
RUN pecl install opentelemetry && \
docker-php-ext-enable opentelemetry

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www/html

# Copy application files
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --no-scripts

COPY . .

# Generate optimized autoloader
RUN composer dump-autoload --optimize

# Set production environment
ENV APP_ENV=production
ENV OTEL_PHP_AUTOLOAD_ENABLED=true
ENV OTEL_SERVICE_NAME=laravel-app
ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://scout-collector:4318

# Optimize Laravel for production
RUN php artisan config:cache && \
php artisan route:cache && \
php artisan view:cache

EXPOSE 8000

CMD ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]
docker-compose.prod.yml
version: '3.8'

services:
laravel-app:
build: .
environment:
APP_NAME: laravel-app
APP_ENV: production
APP_DEBUG: "false"

# OpenTelemetry Configuration
OTEL_PHP_AUTOLOAD_ENABLED: "true"
OTEL_SERVICE_NAME: laravel-app
OTEL_SERVICE_VERSION: "${APP_VERSION:-1.0.0}"
OTEL_EXPORTER_OTLP_ENDPOINT: http://scout-collector:4318
OTEL_EXPORTER_OTLP_COMPRESSION: gzip

# Database
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: laravel_production
DB_USERNAME: laravel
DB_PASSWORD: "${DB_PASSWORD}"

# Resource Attributes
OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=production,service.instance.id=${HOSTNAME}"
depends_on:
- postgres
- scout-collector
ports:
- "8000:8000"
networks:
- app-network

scout-collector:
image: base14/scout-collector:latest
ports:
- "4318:4318"
networks:
- app-network

postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: laravel_production
POSTGRES_USER: laravel
POSTGRES_PASSWORD: "${DB_PASSWORD}"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- app-network

networks:
app-network:
driver: bridge

volumes:
postgres-data:

Resource Attributes Configuration

Add rich context to all telemetry data with resource attributes:

.env
OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,service.namespace=ecommerce,service.instance.id=${HOSTNAME},host.name=${HOSTNAME},host.type=container,cloud.provider=aws,cloud.region=us-east-1,k8s.pod.name=${K8S_POD_NAME},k8s.namespace.name=${K8S_NAMESPACE}"

These attributes help you:

  • Filter traces by environment, region, or instance
  • Correlate issues with specific deployments
  • Analyze performance across different infrastructure
  • Debug production incidents faster

Health Check Implementation

Create health check endpoints to verify telemetry export:

routes/api.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HealthController;

Route::get('/health', [HealthController::class, 'check']);
Route::get('/health/telemetry', [HealthController::class, 'telemetry']);
app/Http/Controllers/HealthController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;

class HealthController extends Controller
{
public function check(): JsonResponse
{
return response()->json([
'status' => 'ok',
'timestamp' => now()->toIso8601String(),
'environment' => config('app.env'),
]);
}

public function telemetry(): JsonResponse
{
$otelEnabled = extension_loaded('opentelemetry');

return response()->json([
'status' => 'ok',
'telemetry' => [
'extension_loaded' => $otelEnabled,
'service_name' => env('OTEL_SERVICE_NAME'),
'service_version' => env('OTEL_SERVICE_VERSION'),
'exporter_endpoint' => env('OTEL_EXPORTER_OTLP_ENDPOINT'),
'php_version' => PHP_VERSION,
'laravel_version' => app()->version(),
],
]);
}
}

Eloquent Database Monitoring

OpenTelemetry automatically instruments Eloquent ORM to provide comprehensive database query monitoring and performance insights.

Automatic Query Tracing

Once configured, all Eloquent queries are automatically traced with detailed information:

// This query is automatically instrumented
$users = User::where('active', true)
->with('posts')
->limit(10)
->get();

// The trace will show:
// - SQL query statement
// - Database name and operation
// - Query duration
// - Bindings (obfuscated for security)

Query Builder and Raw SQL

All database interactions are automatically traced:

// Query Builder (automatically traced)
$articles = DB::table('articles')
->join('users', 'articles.user_id', '=', 'users.id')
->where('articles.published', true)
->orderBy('articles.created_at', 'desc')
->get();

// Raw SQL queries (automatically traced)
$results = DB::select('SELECT * FROM users WHERE active = ?', [true]);

// Transactions (automatically traced with span hierarchy)
DB::transaction(function () {
$order = Order::create([...]);
$order->items()->createMany([...]);
});

Span attributes include:

  • db.system - Database type (mysql, pgsql, sqlite)
  • db.name - Database name
  • db.statement - SQL query (obfuscated)
  • db.operation - Operation type (SELECT, INSERT, UPDATE, DELETE)
  • db.sql.table - Table name

Detecting N+1 Queries

Use OpenTelemetry traces to identify and fix N+1 query problems:

// Bad: N+1 query pattern (visible in traces as multiple DB spans)
$posts = Post::limit(10)->get();
foreach ($posts as $post) {
echo $post->author->name; // Triggers 10 additional queries
}

// Good: Optimized with eager loading (single query in trace)
$posts = Post::with('author')->limit(10)->get();
foreach ($posts as $post) {
echo $post->author->name; // No additional queries
}

In Scout Dashboard, N+1 queries will appear as:

  • Multiple identical database spans within a single request trace
  • High span count for simple operations
  • Repeated query patterns with different parameters

Custom Manual Instrumentation

While automatic instrumentation covers most Laravel components, you can add custom instrumentation for business logic, external API calls, or performance-critical code paths.

Creating Custom Spans

Create custom spans for important business operations:

app/Http/Controllers/OrderController.php
<?php

namespace App\Http\Controllers;

use App\Models\Order;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;

class OrderController extends Controller
{
public function store(Request $request): JsonResponse
{
$tracer = Globals::tracerProvider()->getTracer('orders-controller', '1.0.0');

return $tracer->spanBuilder('create_order')
->setSpanKind(SpanKind::KIND_SERVER)
->setAttribute('user.id', $request->user()->id)
->setAttribute('order.items_count', count($request->items))
->startSpan()
->activate(function ($span) use ($request) {
try {
$span->addEvent('Validating order data');

$order = Order::create([
'user_id' => $request->user()->id,
'items' => $request->items,
'total_amount' => $request->total_amount,
]);

$span->addEvent('Order saved successfully', [
'order.id' => $order->id,
'order.total' => $order->total_amount,
]);

// Process payment in nested span
$this->processPayment($order);

// Send confirmation email in nested span
$this->sendConfirmation($order);

$span->setStatus(StatusCode::STATUS_OK);

return response()->json($order, 201);
} catch (\Exception $e) {
$span->recordException($e);
$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());

return response()->json(['error' => $e->getMessage()], 422);
}
});
}

private function processPayment(Order $order): void
{
$tracer = Globals::tracerProvider()->getTracer('orders-controller', '1.0.0');

$tracer->spanBuilder('process_payment')
->setSpanKind(SpanKind::KIND_INTERNAL)
->startSpan()
->activate(function ($span) use ($order) {
// Payment processing logic
$span->setAttribute('payment.amount', $order->total_amount);
$span->setAttribute('payment.status', 'completed');
});
}

private function sendConfirmation(Order $order): void
{
$tracer = Globals::tracerProvider()->getTracer('orders-controller', '1.0.0');

$tracer->spanBuilder('send_confirmation_email')
->setSpanKind(SpanKind::KIND_INTERNAL)
->startSpan()
->activate(function ($span) use ($order) {
// Email sending logic
$span->addEvent('Email queued', [
'email.to' => $order->user->email,
]);
});
}
}

Adding Middleware for Request Context

Enrich all requests with user context:

app/Http/Middleware/AddTraceContext.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use OpenTelemetry\API\Trace\Span;

class AddTraceContext
{
public function handle(Request $request, Closure $next)
{
$span = Span::getCurrent();

if ($span->isRecording()) {
// Add request attributes
$span->setAttribute('http.route', $request->route()?->getName());
$span->setAttribute('http.request_id', $request->header('X-Request-ID'));

// Add user context if authenticated
if ($request->user()) {
$span->setAttribute('user.id', $request->user()->id);
$span->setAttribute('user.email', $request->user()->email);
$span->setAttribute('user.authenticated', true);
}
}

return $next($request);
}
}

Register the middleware in app/Http/Kernel.php:

app/Http/Kernel.php
protected $middleware = [
// ... other middleware
\App\Http\Middleware\AddTraceContext::class,
];

Instrumenting External API Calls

Add custom instrumentation for external API calls:

app/Services/ExternalApiClient.php
<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;

class ExternalApiClient
{
public function fetchData(string $endpoint): array
{
$tracer = Globals::tracerProvider()->getTracer('external-api-client', '1.0.0');

return $tracer->spanBuilder('external_api_call')
->setSpanKind(SpanKind::KIND_CLIENT)
->setAttribute('http.url', $endpoint)
->setAttribute('http.method', 'GET')
->startSpan()
->activate(function ($span) use ($endpoint) {
try {
$response = Http::get($endpoint);

$span->setAttribute('http.status_code', $response->status());
$span->setAttribute('http.response_size', strlen($response->body()));

if ($response->successful()) {
$span->setStatus(StatusCode::STATUS_OK);
return $response->json();
}

$span->setStatus(StatusCode::STATUS_ERROR, "HTTP {$response->status()}");
throw new \Exception("API request failed with status {$response->status()}");

} catch (\Exception $e) {
$span->recordException($e);
$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());
throw $e;
}
});
}
}

Using Semantic Conventions

Follow OpenTelemetry semantic conventions for consistent attribute naming:

// HTTP semantic conventions
$span->setAttribute('http.method', 'POST');
$span->setAttribute('http.url', 'https://api.example.com/users');
$span->setAttribute('http.status_code', 201);
$span->setAttribute('http.request.header.content_type', 'application/json');

// Database semantic conventions
$span->setAttribute('db.system', 'postgresql');
$span->setAttribute('db.name', 'production');
$span->setAttribute('db.statement', 'SELECT * FROM users WHERE active = ?');
$span->setAttribute('db.operation', 'SELECT');

// Messaging/Queue semantic conventions
$span->setAttribute('messaging.system', 'redis');
$span->setAttribute('messaging.destination', 'emails');
$span->setAttribute('messaging.operation', 'process');

Running Your Instrumented Application

Development Mode

For local development, verify instrumentation is working:

# Set environment variables
export OTEL_PHP_AUTOLOAD_ENABLED=true
export OTEL_SERVICE_NAME=laravel-app-dev
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_LOG_LEVEL=debug

# Start Laravel development server
php artisan serve

Visit http://localhost:8000/api/health/telemetry to verify configuration.

Production Mode

For production deployments, ensure the Scout Collector endpoint is properly configured:

# Set production environment variables
export APP_ENV=production
export OTEL_SERVICE_NAME=laravel-app-production
export OTEL_SERVICE_VERSION=2.1.0
export OTEL_EXPORTER_OTLP_ENDPOINT=https://scout-collector.example.com/v1/traces
export SCOUT_CLIENT_ID=your_client_id
export SCOUT_CLIENT_SECRET=your_client_secret

# Optimize Laravel for production
php artisan config:cache
php artisan route:cache
php artisan view:cache

# Start production server (use PHP-FPM + Nginx in production)
php artisan serve --host=0.0.0.0 --port=8000

Docker Deployment

Run your instrumented Laravel application in Docker:

# Build the image
docker build -t laravel-app:latest .

# Run with Scout Collector
docker run -d \
--name laravel-app \
-e OTEL_PHP_AUTOLOAD_ENABLED=true \
-e OTEL_SERVICE_NAME=laravel-app \
-e OTEL_EXPORTER_OTLP_ENDPOINT=http://scout-collector:4318 \
-e DB_CONNECTION=pgsql \
-e DB_HOST=postgres \
-p 8000:8000 \
laravel-app:latest

Or use Docker Compose (see Production Configuration section for complete example).

Troubleshooting

Verifying OpenTelemetry Installation

Check if the OpenTelemetry extension is loaded:

# Verify extension is loaded
php -m | grep opentelemetry

# Check extension version
php -r "echo phpversion('opentelemetry');"

# Verify configuration
php -i | grep -i otel

Expected output:

opentelemetry
1.0.0
OTEL_PHP_AUTOLOAD_ENABLED => true
OTEL_SERVICE_NAME => laravel-app

Testing Instrumentation in Tinker

Test your OpenTelemetry configuration using Laravel Tinker:

php artisan tinker

// Check if extension is loaded
>>> extension_loaded('opentelemetry');
=> true

// Verify environment variables
>>> env('OTEL_SERVICE_NAME');
=> "laravel-app"

>>> env('OTEL_EXPORTER_OTLP_ENDPOINT');
=> "http://localhost:4318"

Debug Mode

Enable debug logging to troubleshoot instrumentation issues:

export OTEL_LOG_LEVEL=debug
export OTEL_PHP_INTERNAL_METRICS_ENABLED=true
php artisan serve

Check Laravel logs for OpenTelemetry debug information:

tail -f storage/logs/laravel.log

Common Issues

Issue: No traces appearing in Scout Dashboard

Solutions:

  1. Verify Scout Collector endpoint is reachable:

    curl -v http://scout-collector:4318/v1/traces
  2. Check environment variables are set:

    php artisan tinker
    >>> env('OTEL_EXPORTER_OTLP_ENDPOINT');
    >>> env('OTEL_SERVICE_NAME');
  3. Enable debug logging and check for export errors:

    export OTEL_LOG_LEVEL=debug
    php artisan serve
  4. Verify network connectivity between Laravel app and Scout Collector

Issue: OpenTelemetry extension not loaded

Solutions:

  1. Verify extension installation:

    pecl list | grep opentelemetry
  2. Check php.ini includes extension directive:

    php --ini | head -1
    cat /path/to/php.ini | grep opentelemetry
  3. Ensure extension file exists:

    find /usr -name "opentelemetry.so" 2>/dev/null
  4. Restart PHP-FPM if using FastCGI:

    sudo systemctl restart php8.2-fpm

Issue: Missing database query spans

Solutions:

  1. Ensure auto-instrumentation is enabled:

    echo $OTEL_PHP_AUTOLOAD_ENABLED  # Should be "true"
  2. Verify database connection is active:

    php artisan tinker
    >>> DB::connection()->getPdo();
  3. Check that Laravel instrumentation package is installed:

    composer show | grep opentelemetry-auto-laravel

Issue: High memory usage

Solutions:

  1. Reduce batch queue size:

    export OTEL_BSP_MAX_QUEUE_SIZE=1024
  2. Increase export frequency:

    export OTEL_BSP_SCHEDULE_DELAY_MILLIS=2000
  3. Monitor PHP memory limit:

    php -i | grep memory_limit

Issue: Performance degradation

Solutions:

  1. Verify batch span processor is being used (not simple processor)

  2. Skip health check endpoints by configuring routes:

    // Don't trace health check endpoints
    Route::get('/health', function () {
    return response()->json(['status' => 'ok']);
    })->withoutMiddleware([\App\Http\Middleware\AddTraceContext::class]);
  3. Use selective instrumentation if full auto-instrumentation is too heavy

Security Considerations

Protecting Sensitive Data

Avoid adding sensitive information to span attributes:

// Bad - exposes sensitive data
$span->setAttribute('user.password', $user->password); // Never!
$span->setAttribute('credit_card.number', $request->cc_number); // Never!
$span->setAttribute('user.ssn', $user->social_security_number); // Never!

// Good - uses safe identifiers
$span->setAttribute('user.id', $user->id);
$span->setAttribute('user.role', $user->role);
$span->setAttribute('payment.provider', 'stripe');
$span->setAttribute('payment.status', 'completed');

SQL Query Obfuscation

OpenTelemetry PHP automatically obfuscates SQL parameter values in database spans:

Before obfuscation (never sent):

SELECT * FROM users WHERE email = 'user@example.com' AND password = 'secret123'

After obfuscation (what gets sent):

SELECT * FROM users WHERE email = ? AND password = ?

Filtering Sensitive HTTP Headers

Configure which HTTP headers are captured in spans:

.env
# Only capture safe headers
OTEL_HTTP_HEADERS_ALLOWED=content-type,accept,user-agent
OTEL_HTTP_HEADERS_BLOCKED=authorization,cookie,x-api-key

Compliance Considerations

For applications handling regulated data (GDPR, HIPAA, PCI-DSS):

  • Never include personally identifiable information (PII) in spans
  • Use hashed or anonymized user identifiers
  • Implement data retention policies in Scout Dashboard
  • SQL obfuscation is enabled by default for database queries
  • Audit span attributes regularly for sensitive data leaks
  • Configure allowed/blocked HTTP headers appropriately

Performance Considerations

Expected Performance Impact

OpenTelemetry instrumentation adds minimal overhead to Laravel applications:

  • Average latency increase: 2-4ms per request
  • CPU overhead: Less than 3% in production with batch processing
  • Memory overhead: ~80-120MB depending on queue size and traffic

Impact varies based on:

  • Number of enabled instrumentations
  • Application request volume
  • Complexity of database queries
  • Number of external API calls

Optimization Best Practices

1. Use Batch Span Processing

# Production settings (low overhead)
OTEL_BSP_MAX_QUEUE_SIZE=2048
OTEL_BSP_SCHEDULE_DELAY_MILLIS=5000
OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512

2. Enable OPcache

php.ini
[opcache]
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0 ; Disable in production

3. Skip Non-Critical Endpoints

Configure routes that don't need tracing:

routes/web.php
// Health checks don't need full tracing
Route::get('/health', function () {
return response()->json(['status' => 'ok']);
})->withoutMiddleware();

Route::get('/metrics', function () {
return response()->json([/* metrics */]);
})->withoutMiddleware();

4. Use Redis for Queue Monitoring

For high-throughput queue processing, ensure efficient monitoring:

.env
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis # Faster than predis

5. Enable GZIP Compression

OTEL_EXPORTER_OTLP_COMPRESSION=gzip

Reduces network bandwidth by 70-80%.

Frequently Asked Questions

Does OpenTelemetry impact Laravel performance?

OpenTelemetry adds approximately 2-4ms of latency per request in typical Laravel applications. With proper configuration (batch processing, GZIP compression), the performance impact is minimal and acceptable for most production workloads.

Which Laravel versions are supported?

OpenTelemetry supports Laravel 8.0+ with PHP 8.0+. Laravel 10.x or 11.x with PHP 8.2+ is recommended for optimal compatibility and performance. See the Prerequisites section for detailed version compatibility.

Can I use OpenTelemetry with Laravel queues and job workers?

Yes! The opentelemetry-auto-laravel package includes automatic instrumentation for Laravel queue workers. Background jobs are traced automatically, and you can see the complete trace from HTTP request through asynchronous job processing in Scout Dashboard.

Is OpenTelemetry compatible with Laravel middleware?

Yes, OpenTelemetry instruments at the HTTP request level, making it compatible with all Laravel middleware. Custom middleware will appear in traces automatically.

Can I use OpenTelemetry alongside other APM tools?

Yes, OpenTelemetry can run alongside tools like New Relic or DataDog during migration periods. However, running multiple APM agents simultaneously will multiply the performance overhead, so plan your migration carefully.

How do I handle multi-tenant Laravel applications?

Add tenant context to spans using middleware:

$span = Span::getCurrent();
$span->setAttribute('tenant.id', $request->tenant->id);
$span->setAttribute('tenant.name', $request->tenant->name);

Then filter traces by tenant in Scout Dashboard.

What's the difference between traces and metrics?

Traces show the complete request flow through your application with timing details for each operation. Use traces to debug slow requests and understand distributed transactions.

Metrics provide aggregated statistics over time (request rate, error rate, latency percentiles). Use metrics for monitoring overall application health and setting alerts.

How do I monitor Eloquent N+1 queries?

OpenTelemetry traces automatically expose N+1 queries as multiple database spans within a single request trace. In Scout Dashboard, look for repeated query patterns or high span counts for simple operations.

Can I use OpenTelemetry with Laravel Octane?

Yes! OpenTelemetry is compatible with Laravel Octane (Swoole/RoadRunner). Ensure the extension is loaded in your Octane worker process by checking php -m in the Octane container.

How do I instrument Laravel scheduled tasks (cron jobs)?

Scheduled tasks are automatically instrumented when using auto-instrumentation. Each scheduled command execution creates a trace you can view in Scout Dashboard.

Does OpenTelemetry work with Laravel Livewire?

Yes, Laravel Livewire HTTP requests are automatically traced. Component lifecycle events (mount, render, etc.) appear as spans in your traces.

Can I customize which database queries are traced?

Yes, you can create custom middleware or database event listeners to selectively skip tracing certain queries. However, we recommend tracing all queries for complete observability.

What's Next?

Now that your Laravel application is instrumented with OpenTelemetry, explore these resources to maximize your observability:

Advanced Topics

Scout Platform Features

  • Creating Alerts - Set up intelligent alerts for error rates, latency thresholds, and custom metrics
  • Dashboard Creation - Build custom dashboards combining traces, metrics, and business KPIs

Deployment and Operations

Complete Example

Here's a complete working example of a Laravel application with OpenTelemetry instrumentation.

Example Project 1: Laravel 11 + PHP 8.2 + PostgreSQL

Based on our GitHub example repository.

composer.json

composer.json
{
"name": "laravel/laravel",
"type": "project",
"require": {
"php": "^8.2",
"laravel/framework": "^11.0",
"open-telemetry/sdk": "^1.6",
"open-telemetry/exporter-otlp": "^1.3",
"open-telemetry/opentelemetry-auto-laravel": "^1.2"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
}
}

Environment Configuration

.env
APP_NAME=laravel-otel-example
APP_ENV=production
APP_DEBUG=false

# Database
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=secret

# OpenTelemetry Configuration
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_SERVICE_NAME=laravel-app
OTEL_SERVICE_VERSION=1.0.0
OTEL_TRACES_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=http://scout-collector:4318
OTEL_PROPAGATORS=baggage,tracecontext

# Scout Collector (for production)
SCOUT_ENDPOINT=https://your-tenant.base14.io/v1/traces
SCOUT_CLIENT_ID=your_client_id
SCOUT_CLIENT_SECRET=your_client_secret
SCOUT_TOKEN_URL=https://your-tenant.base14.io/oauth/token

Dockerfile

Dockerfile
FROM php:8.2-fpm-alpine

# Install dependencies
RUN apk add --no-cache \
autoconf \
build-base \
postgresql-dev \
libzip-dev

# Install PHP extensions
RUN docker-php-ext-install pdo pdo_pgsql zip opcache

# Install OpenTelemetry extension
RUN pecl install opentelemetry && \
docker-php-ext-enable opentelemetry

WORKDIR /var/www/html

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader

COPY . .

ENV OTEL_PHP_AUTOLOAD_ENABLED=true
ENV OTEL_SERVICE_NAME=laravel-app

EXPOSE 8000

CMD ["php", "artisan", "serve", "--host=0.0.0.0"]

Example Project 2: Laravel 8 + SQLite (Legacy)

For legacy applications still using Laravel 8 (see GitHub example):

⚠️ Security Warning: Laravel 8 reached end-of-life in July 2023. This example is provided for reference only and should not be used in production.

.env
APP_NAME=laravel-8-legacy
DB_CONNECTION=sqlite

OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_SERVICE_NAME=laravel-8-legacy
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318

Complete working examples are available in our GitHub examples repository.

References

Was this page helpful?