OpenTelemetry Integration for Symfony: Full Guide
OpenTelemetry integration with Symfony applications provides comprehensive observability through distributed tracing, metrics, and logging. This guide shows you how to implement OpenTelemetry in Symfony using auto-instrumentation and custom spans for production-ready monitoring.
What is OpenTelemetry?
OpenTelemetry is an open-source observability framework that provides a standardized way to collect, process, and export telemetry data from applications and infrastructure. It combines metrics, logs, and distributed traces into a unified toolkit that helps developers understand how their systems are performing. For Symfony applications, it offers:
- Auto-instrumentation: Automatic tracing of Symfony framework components (HTTP kernel, Doctrine, Twig)
- Distributed tracing: End-to-end request tracking across services
- Performance monitoring: Database queries, HTTP requests, and message queue processing
- Custom metrics: Business-specific measurements and KPIs
For general PHP instrumentation details, see the OpenTelemetry PHP guide.
Why Use OpenTelemetry with Symfony?
Compared to other Symfony monitoring solutions:
| Solution | Setup | Cost | Distributed Tracing | Vendor Lock-in |
|---|---|---|---|---|
| OpenTelemetry | Medium | Free + Backend | Excellent | None |
| Symfony Profiler | Easy | Free | None | None |
| Blackfire | Easy | Paid | Limited | Yes |
| New Relic | Easy | Expensive | Excellent | Yes |
OpenTelemetry provides industry-standard observability without vendor lock-in, making it ideal for distributed Symfony applications. Unlike the built-in Symfony Profiler (which only works in development), OpenTelemetry is designed for production monitoring across multiple services.
Requirements
Before implementing OpenTelemetry in Symfony applications:
- PHP 8.1+ (required for auto-instrumentation)
- Symfony 5.4+ / 6.x / 7.x (full support across LTS and current releases)
- OpenTelemetry PHP Extension
- Composer for dependency management
Quick Start: For fastest setup without code changes, see the PHP zero-code instrumentation guide.
Step 1: Install OpenTelemetry Extension
The extension enables auto-instrumentation for Symfony applications. First, set up the development environment:
sudo apt-get install gcc make autoconf php-dev
Then install the extension using one of these methods:
pecl install opentelemetry
Add the extension to your php.ini file (run php --ini to find the file location):
[opentelemetry]
extension=opentelemetry.so
Verify the installation works:
php --ri opentelemetry
# Should display extension information and version
Step 2: Install Symfony Packages
Install the required packages for Symfony OpenTelemetry integration:
# Core Symfony auto-instrumentation package
composer require open-telemetry/opentelemetry-auto-symfony
# OpenTelemetry SDK and exporter
composer require open-telemetry/sdk open-telemetry/exporter-otlp
# For Uptrace integration (optional)
composer require uptrace/uptrace
Step 3: Configure OpenTelemetry
Add OpenTelemetry initialization to public/index.php right after the autoloader:
<?php
use App\Kernel;
use Uptrace\Distro;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
/*
|--------------------------------------------------------------------------
| Configure OpenTelemetry
|--------------------------------------------------------------------------
| This runs before the runtime closure so the SDK is ready
| when Symfony boots the kernel.
*/
$uptrace = Distro::builder()
->setDsn($_ENV['UPTRACE_DSN'] ?? 'your-project-dsn')
->setServiceName($_ENV['OTEL_SERVICE_NAME'] ?? 'symfony-app')
->setServiceVersion($_ENV['OTEL_SERVICE_VERSION'] ?? '1.0.0')
->buildAndRegisterGlobal();
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
Set up environment variables in your .env file using the standard OpenTelemetry environment variables:
# Service identification
OTEL_SERVICE_NAME=symfony-ecommerce
OTEL_SERVICE_VERSION=1.0.0
# Uptrace configuration
UPTRACE_DSN=https://<secret>@api.uptrace.dev/project-id
# Enable auto-instrumentation
OTEL_PHP_AUTOLOAD_ENABLED=true
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
# OTLP protocol settings
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.uptrace.dev:4318
# Sampling (adjust based on traffic)
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=1.0
# Context propagation
OTEL_PROPAGATORS=tracecontext,baggage
Step 4: Test the Integration
Start your Symfony application and OpenTelemetry will automatically instrument it:
symfony server:start
# or
php -S localhost:8000 -t public/
Make some requests to generate traces:
curl http://localhost:8000/
curl http://localhost:8000/api/products
You should see traces appearing in your Uptrace project dashboard.
Custom Instrumentation Examples
While auto-instrumentation covers most Symfony components (HTTP kernel, Doctrine ORM, Twig templates), you can add custom instrumentation for business logic.
Controller with Custom Spans
This example shows how to add business context to your Symfony controllers:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\StatusCode;
use App\Entity\Order;
use Doctrine\ORM\EntityManagerInterface;
class OrderController extends AbstractController
{
public function __construct(
private EntityManagerInterface $entityManager,
) {}
#[Route('/api/orders', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
$tracer = Globals::tracerProvider()->getTracer('order-service');
$span = $tracer->spanBuilder('create-order')
->setAttribute('user.id', $this->getUser()?->getUserIdentifier())
->startSpan();
$scope = $span->activate();
try {
$data = json_decode($request->getContent(), true);
$order = new Order();
$order->setCustomerEmail($data['customer_email']);
$order->setTotal($this->calculateTotal($data['items']));
$order->setStatus('pending');
// Doctrine queries are automatically traced
$this->entityManager->persist($order);
$this->entityManager->flush();
$span->setAttribute('order.id', $order->getId());
$span->setAttribute('order.items.count', count($data['items']));
$span->setStatus(StatusCode::STATUS_OK);
return $this->json(['order_id' => $order->getId()], 201);
} catch (\Exception $e) {
$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());
$span->recordException($e);
throw $e;
} finally {
$scope->detach();
$span->end();
}
}
private function calculateTotal(array $items): float
{
return array_sum(array_column($items, 'price'));
}
}
Custom Metrics with Event Subscriber
Track business metrics using a Symfony event subscriber:
<?php
namespace App\EventSubscriber;
use OpenTelemetry\API\Globals;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class MetricsSubscriber implements EventSubscriberInterface
{
private $requestCounter;
private $responseDuration;
private $errorCounter;
public function __construct()
{
$meter = Globals::meterProvider()->getMeter('symfony-metrics');
$this->requestCounter = $meter->createCounter(
'http_requests_total',
'count',
'Total number of HTTP requests'
);
$this->responseDuration = $meter->createHistogram(
'http_response_duration',
'ms',
'HTTP response duration in milliseconds'
);
$this->errorCounter = $meter->createCounter(
'http_errors_total',
'count',
'Total number of HTTP errors'
);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => 'onRequest',
KernelEvents::RESPONSE => 'onResponse',
KernelEvents::EXCEPTION => 'onException',
];
}
public function onRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$event->getRequest()->attributes->set(
'_otel_start_time',
hrtime(true)
);
$this->requestCounter->add(1, [
'http.method' => $event->getRequest()->getMethod(),
'http.route' => $event->getRequest()->attributes->get('_route', 'unknown'),
]);
}
public function onResponse(ResponseEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$startTime = $event->getRequest()->attributes->get('_otel_start_time');
if ($startTime) {
$duration = (hrtime(true) - $startTime) / 1_000_000; // Convert to ms
$this->responseDuration->record($duration, [
'http.method' => $event->getRequest()->getMethod(),
'http.status_code' => $event->getResponse()->getStatusCode(),
]);
}
}
public function onException(ExceptionEvent $event): void
{
$this->errorCounter->add(1, [
'http.method' => $event->getRequest()->getMethod(),
'exception.type' => get_class($event->getThrowable()),
]);
}
}
Register the subscriber in config/services.yaml (if not using autoconfigure):
services:
App\EventSubscriber\MetricsSubscriber:
tags: ['kernel.event_subscriber']
Production Configuration
Environment-Specific Settings
Configure different sampling rates for different environments:
Development:
# Full sampling for debugging
OTEL_TRACES_SAMPLER_ARG=1.0
Production:
# Reduced sampling for performance
OTEL_TRACES_SAMPLER_ARG=0.1
OTEL_BSP_SCHEDULE_DELAY=5000
OTEL_BSP_MAX_EXPORT_BATCH_SIZE=512
Docker Configuration
For containerized Symfony applications:
FROM php:8.2-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git unzip libzip-dev \
&& docker-php-ext-install zip
# Install OpenTelemetry extension
RUN pecl install opentelemetry \
&& echo "extension=opentelemetry" > /usr/local/etc/php/conf.d/opentelemetry.ini
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
COPY . /var/www/html
WORKDIR /var/www/html
RUN composer install --optimize-autoloader --no-dev
ENV OTEL_PHP_AUTOLOAD_ENABLED=true
ENV OTEL_SERVICE_NAME=symfony-app
EXPOSE 9000
CMD ["php-fpm"]
Common Issues
No Traces Appearing
Check if the OpenTelemetry extension is properly loaded:
# Verify extension is installed
php -m | grep opentelemetry
# Check if autoloading is enabled
echo $OTEL_PHP_AUTOLOAD_ENABLED
# Test with console exporter to see traces in terminal
OTEL_TRACES_EXPORTER=console php -S localhost:8000 -t public/
Performance Impact
If you notice performance degradation, optimize the configuration:
# Reduce sampling rate
OTEL_TRACES_SAMPLER_ARG=0.05
# Increase batch processing interval
OTEL_BSP_SCHEDULE_DELAY=10000
OTEL_BSP_MAX_EXPORT_BATCH_SIZE=1024
# Disable specific instrumentation if not needed
OTEL_PHP_DISABLED_INSTRUMENTATIONS=psr3,psr16
Missing Doctrine Traces
Ensure database instrumentation packages are installed and not disabled:
# Install Doctrine auto-instrumentation if not present
composer require open-telemetry/opentelemetry-auto-pdo
# Verify OTEL_PHP_DISABLED_INSTRUMENTATIONS does not contain 'pdo'
Symfony Cache Interference
If traces are missing after cache warmup, clear and rebuild:
php bin/console cache:clear
php bin/console cache:warmup
Conclusion
OpenTelemetry provides comprehensive observability for Symfony applications with minimal setup effort. The auto-instrumentation package handles most common scenarios automatically, while custom instrumentation allows you to add business-specific context where needed.
Key benefits:
- Zero-code auto-instrumentation for Symfony HTTP kernel, Doctrine, and Twig
- Industry-standard telemetry compatible with any OpenTelemetry APM backend
- Production-ready performance with configurable sampling
- Extensible with custom metrics, spans, and event subscribers
For alternative PHP frameworks, explore Laravel for rapid development with auto-instrumentation or Slim for lightweight microservices.
Ready to start monitoring your Symfony application? Sign up for Uptrace and get comprehensive observability in minutes.