Copy these 4 essential files from this project to your Laravel app:
bootstrap/otel.php # Core OpenTelemetry SDK setup
app/Http/Middleware/OpenTelemetryMiddleware.php # HTTP request/response tracing
app/Providers/AppServiceProvider.php # Database tracing (copy the boot() method)
config/otel.php # OpenTelemetry configuration (route filtering)
Add this line after the autoloader, before Laravel bootstrap:
// Initialize OpenTelemetry SDK
require_once __DIR__.'/../bootstrap/otel.php';In app/Http/Kernel.php, add to the $middleware array:
protected $middleware = [
// ... existing middleware
\App\Http\Middleware\OpenTelemetryMiddleware::class,
];Copy the config/otel.php file to your config/ directory to control which routes are traced:
<?php
return [
/*
|--------------------------------------------------------------------------
| OpenTelemetry Route Tracing Configuration
|--------------------------------------------------------------------------
|
| This array defines which route patterns should be traced by the
| OpenTelemetry middleware. Routes starting with these patterns
| will have tracing enabled.
|
| Examples:
| - ['api'] - Only trace routes starting with /api
| - ['api', 'admin'] - Trace routes starting with /api or /admin
| - [''] - Trace all routes (empty string matches all)
|
*/
'traced_routes' => [
'api' // Only trace /api routes by default
],
];Configuration Examples:
['api']- Only trace/api/*routes (default)['api', 'admin']- Trace/api/*and/admin/*routes['']- Trace all routes (empty string matches all)[]- Disable all tracing
Note: The config/otel.php file is required for the middleware to function properly. Make sure to copy it to your Laravel app's config/ directory.
In your existing AppServiceProvider.php boot() method, add this code:
public function boot()
{
$tracer = $GLOBALS['otel_tracer'] ?? null;
\Illuminate\Support\Facades\DB::listen(function ($query) use ($tracer) {
if (!$tracer) {
return;
}
try {
$connectionName = $query->connectionName ?? config('database.default');
$connection = config("database.connections.{$connectionName}");
$spanBuilder = $tracer->spanBuilder('db.query')
->setSpanKind(\OpenTelemetry\API\Trace\SpanKind::KIND_CLIENT)
->setAttribute(\OpenTelemetry\SemConv\TraceAttributes::DB_SYSTEM, $connection['driver'] ?? 'unknown')
->setAttribute(\OpenTelemetry\SemConv\TraceAttributes::DB_NAME, $connection['database'] ?? $connectionName)
->setAttribute('server.address', $connection['host'] ?? 'localhost')
->setAttribute('server.port', $connection['port'] ?? 3306)
->setAttribute('db.statement', $query->sql)
->setAttribute('db.query.duration_ms', $query->time);
// Add SQL parameter bindings if they exist
if (!empty($query->bindings)) {
$spanBuilder->setAttribute('db.statement.parameters', json_encode($query->bindings));
$spanBuilder->setAttribute('db.statement.parameters.count', count($query->bindings));
}
$span = $spanBuilder->startSpan();
$span->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_OK);
$span->end();
} catch (\Throwable $e) {
// Silently fail
}
});
}Add to your .env file:
OTEL_SERVICE_NAME=your-app-name
OTEL_SERVICE_VERSION=1.0.0
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://your-collector-endpoint/v1/traces
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic your-auth-token"
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobufAdd to your composer.json and run composer install:
{
"require": {
"open-telemetry/sdk": "^1.0",
"open-telemetry/contrib-otlp": "^1.0",
"open-telemetry/sem-conv": "^1.0"
}
}- β
HTTP Request Spans - Configurable route-based tracing (only
/apiroutes by default) - β Database Spans - All Eloquent ORM and raw DB queries traced with SQL parameter binding
- β
SQL Parameter Binding - Capture actual parameter values in
db.statement.parametersattribute - β
External HTTP Call Tracing - Use helper functions
traced_curl_exec()andtraced_guzzle_request() - β Proper Span Relationships - Database spans are children of HTTP request spans
- β Performance Optimized - Zero regex parsing, minimal overhead, non-traced routes skip all instrumentation
For tracing external HTTP calls, use these helper functions (included in bootstrap/otel.php):
// Instead of curl_exec($ch)
$result = traced_curl_exec($ch);
// Instead of $client->request($method, $url, $options)
$response = traced_guzzle_request($client, $method, $url, $options);After integration, test that tracing is working:
You can add these test routes to verify everything is working:
// Test basic functionality
Route::get('/api/test-otel', function () {
// This will create HTTP span automatically via middleware
// Test database span with parameter binding
$users = \Illuminate\Support\Facades\DB::select('SELECT COUNT(*) as count FROM users WHERE id > ?', [0]);
// Test Eloquent with parameter binding
$user = \App\User::find(1);
// Test external HTTP call (if needed)
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://httpbin.org/get');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = traced_curl_exec($ch);
curl_close($ch);
return response()->json([
'message' => 'OpenTelemetry test completed',
'user_count' => $users[0]->count ?? 0,
'specific_user' => $user ? $user->name : null,
'external_call' => 'success'
]);
});- HTTP request span appears in your tracing backend
- Database query spans appear as children of HTTP span
- SQL parameter binding values appear in
db.statement.parametersattribute - Parameter count appears in
db.statement.parameters.countattribute - External HTTP call spans appear (if using helper functions)
- All spans contain proper semantic attributes
- No application errors or performance degradation
{
"db.statement": "select * from users where id = ? limit 1",
"db.statement.parameters": "[1]",
"db.statement.parameters.count": 1,
"db.sql.table": "users",
"db.system": "mysql",
"db.name": "laravel_app",
"db.query.duration_ms": 2.5
}-
No spans appearing
- Check environment variables are set correctly
- Verify collector endpoint is reachable
- Check Laravel logs for any errors
-
Database spans missing
- Ensure
AppServiceProvider.phpboot method includes the DB::listen code - Verify database queries are actually executing
- Ensure
-
HTTP spans missing
- Confirm middleware is registered in
Kernel.php - Check middleware order (should be early in the stack)
- Ensure
config/otel.phpexists and contains proper route patterns
- Confirm middleware is registered in
-
Route filtering not working
- Verify
config/otel.phpis copied to your Laravel app - Check that
traced_routesarray matches your desired route patterns - Run
php artisan config:cacheafter modifying the config file
- Verify
-
Performance issues
- This implementation is optimized for minimal overhead
- Monitor your application performance before/after
- Adjust batch processor settings in
bootstrap/otel.phpif needed
- OpenTelemetry PHP Documentation
- OpenTelemetry Semantic Conventions
- Laravel Service Providers
- Laravel Middleware
That's it! Your Laravel app will now have comprehensive OpenTelemetry tracing with HTTP, database, and external call monitoring.