OpenTelemetry PHP distro for Uptrace
This guide shows you how to configure the OpenTelemetry PHP SDK to export spans, logs, and metrics to Uptrace using the OTLP/HTTP protocol.
For detailed API documentation, see:
Overview
OpenTelemetry provides observability for your PHP applications by collecting telemetry data (traces, metrics, and logs) and exporting it to monitoring systems like Uptrace.
Installation Options
Option 1: Using Uptrace PHP Distribution (Recommended)
uptrace-php is a thin wrapper around opentelemetry-php that pre-configures the OpenTelemetry SDK for Uptrace. It provides convenience without adding new functionality.
Prerequisites:
Install Composer following the official installation guide.
Installation:
composer require uptrace/uptrace
Option 2: Direct OTLP Exporter
Skip the wrapper and use the OTLP exporter directly if you prefer more control over the configuration. See the Direct OTLP Configuration section below.
Quick Start Guide
Follow these steps to get your first trace running in 5 minutes:
Step 1: Create an Uptrace Project
- Create an Uptrace project to obtain your DSN (Data Source Name)
- Your DSN will look like:
https://<secret>@api.uptrace.dev?grpc=4317
Step 2: Install the Package
composer require uptrace/uptrace
Step 3: Basic Configuration
Create a PHP file with the following configuration:
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use OpenTelemetry\API\Common\Instrumentation\Globals;
use OpenTelemetry\API\Trace\SpanKind;
// Configure Uptrace
$uptrace = Uptrace\Distro::builder()
->setDsn('YOUR_UPTRACE_DSN_HERE') // Replace with your actual DSN
->setServiceName('myservice')
->setServiceVersion('1.0.0')
->setResourceAttributes(['deployment.environment' => 'production'])
->buildAndRegisterGlobal();
// Create a tracer instance
$tracer = Globals::tracerProvider()->getTracer('app_or_package_name');
Step 4: Create Your First Trace
// Create a root span to measure an operation
$main = $tracer->spanBuilder('main-operation')->startSpan();
$mainScope = $main->activate();
// Create a child span for an HTTP request
$httpSpan = $tracer->spanBuilder('GET /posts/:id')
->setSpanKind(SpanKind::KIND_SERVER)
->startSpan();
$httpScope = $httpSpan->activate();
// Add HTTP-specific attributes
$httpSpan->setAttribute('http.method', 'GET');
$httpSpan->setAttribute('http.route', '/posts/:id');
$httpSpan->setAttribute('http.url', 'http://localhost:8080/posts/123');
$httpSpan->setAttribute('http.status_code', 200);
// Handle errors within spans
try {
throw new \Exception('Database connection failed');
} catch (\Exception $exc) {
$httpSpan->setStatus('error', $exc->getMessage());
$httpSpan->recordException($exc);
}
$httpScope->detach();
$httpSpan->end();
// Create a database operation span
$dbSpan = $tracer->spanBuilder('database-query')->startSpan();
$dbScope = $dbSpan->activate();
$dbSpan->setAttributes([
'db.system' => 'mysql',
'db.statement' => 'SELECT * FROM posts LIMIT 100',
'db.name' => 'blog_db'
]);
$dbScope->detach();
$dbSpan->end();
// Clean up the main span
$mainScope->detach();
$main->end();
// Get the trace URL for debugging
echo "View trace: " . $uptrace->traceUrl($main) . PHP_EOL;
Step 5: Run Your Application
UPTRACE_DSN="your_dsn_here" php your_script.php
You should see output like:
View trace: https://app.uptrace.dev/traces/<trace_id>
Configuration Options
Environment Variables
You can configure Uptrace using environment variables instead of hardcoding values:
export UPTRACE_DSN="https://<secret>@api.uptrace.dev?grpc=4317"
export OTEL_SERVICE_NAME="myservice"
export OTEL_SERVICE_VERSION="1.0.0"
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production"
Then use in your code:
$uptrace = Uptrace\Distro::builder()
// DSN will be read from UPTRACE_DSN environment variable
->setServiceName(getenv('OTEL_SERVICE_NAME') ?: 'myservice')
->setServiceVersion(getenv('OTEL_SERVICE_VERSION') ?: '1.0.0')
->buildAndRegisterGlobal();
Resource Attributes
Resource attributes provide metadata about your service:
$uptrace = Uptrace\Distro::builder()
->setDsn($dsn)
->setServiceName('user-service')
->setServiceVersion('2.1.0')
->setResourceAttributes([
'deployment.environment' => 'production',
'service.namespace' => 'backend',
'service.instance.id' => gethostname(),
'cloud.provider' => 'aws',
'cloud.region' => 'us-west-2'
])
->buildAndRegisterGlobal();
Sampling Configuration
For production environments with high traffic, configure sampling to reduce overhead:
$uptrace = Uptrace\Distro::builder()
->setDsn($dsn)
->setServiceName('high-traffic-service')
->setSampling(0.1) // Sample 10% of traces
->buildAndRegisterGlobal();
Direct OTLP Configuration
If you prefer to configure OpenTelemetry directly without the Uptrace wrapper:
Dependencies
First, install the required packages:
composer require \
open-telemetry/opentelemetry \
open-telemetry/exporter-otlp \
open-telemetry/transport-grpc \
open-telemetry/auto-instrumentation
Trace Export Configuration
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\SDK\Sdk;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
$dsn = getenv('UPTRACE_DSN');
if (!$dsn) {
exit('UPTRACE_DSN environment variable is required');
}
// Configure service resource information
$resource = ResourceInfoFactory::emptyResource()->merge(
ResourceInfo::create(Attributes::create([
"service.name" => "my-php-service",
"service.version" => "1.0.0",
])),
ResourceInfoFactory::defaultResource()
);
// Set up OTLP HTTP transport
$transportFactory = new OtlpHttpTransportFactory();
$transport = $transportFactory->create(
'https://api.uptrace.dev/v1/traces',
'application/json',
['uptrace-dsn' => $dsn],
TransportFactoryInterface::COMPRESSION_GZIP,
);
// Create span exporter and processor
$spanExporter = new SpanExporter($transport);
$spanProcessor = new BatchSpanProcessor(
$spanExporter,
ClockFactory::getDefault(),
BatchSpanProcessor::DEFAULT_MAX_QUEUE_SIZE,
BatchSpanProcessor::DEFAULT_SCHEDULE_DELAY,
BatchSpanProcessor::DEFAULT_EXPORT_TIMEOUT,
BatchSpanProcessor::DEFAULT_MAX_EXPORT_BATCH_SIZE,
true,
);
// Build and register the tracer provider
$tracerProvider = TracerProvider::builder()
->addSpanProcessor($spanProcessor)
->setResource($resource)
->setSampler(new ParentBased(new AlwaysOnSampler()))
->build();
Sdk::builder()
->setTracerProvider($tracerProvider)
->setPropagator(TraceContextPropagator::getInstance())
->setAutoShutdown(true)
->buildAndRegisterGlobal();
// Now you can use the tracer
$tracer = \OpenTelemetry\API\Globals::tracerProvider()->getTracer('app_or_package_name');
Metrics Export Configuration
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\Contrib\Otlp\MetricExporter;
$dsn = getenv('UPTRACE_DSN');
if (!$dsn) {
exit('UPTRACE_DSN environment variable is required');
}
// Configure resource information
$resource = ResourceInfoFactory::emptyResource()->merge(
ResourceInfo::create(Attributes::create([
"service.name" => "my-php-service",
"service.version" => "1.0.0",
])),
ResourceInfoFactory::defaultResource()
);
// Set up metrics export
$transportFactory = new OtlpHttpTransportFactory();
$reader = new ExportingReader(
new MetricExporter(
$transportFactory->create(
'https://api.uptrace.dev/v1/metrics',
'application/json',
['uptrace-dsn' => $dsn],
TransportFactoryInterface::COMPRESSION_GZIP,
)
),
ClockFactory::getDefault()
);
$meterProvider = MeterProvider::builder()
->setResource($resource)
->addReader($reader)
->build();
// Create and use metrics
$meter = $meterProvider->getMeter('app_or_package_name');
$counter = $meter->createCounter('http_requests_total', 'requests', 'Number of HTTP requests');
$histogram = $meter->createHistogram('http_request_duration', 'seconds', 'HTTP request duration');
// Example usage
$counter->add(1, ['method' => 'GET', 'status' => '200']);
$histogram->record(0.150, ['method' => 'GET', 'route' => '/api/users']);
Logs Export Configuration
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use OpenTelemetry\API\Logs\EventLogger;
use OpenTelemetry\API\Logs\LogRecord;
use OpenTelemetry\SDK\Sdk;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Time\ClockFactory;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;
use OpenTelemetry\SDK\Logs\LoggerProvider;
use OpenTelemetry\SDK\Logs\Processor\BatchLogRecordProcessor;
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\Contrib\Otlp\LogsExporter;
$dsn = getenv('UPTRACE_DSN');
if (!$dsn) {
exit('UPTRACE_DSN environment variable is required');
}
// Configure resource information
$resource = ResourceInfoFactory::emptyResource()->merge(
ResourceInfo::create(Attributes::create([
"service.name" => "my-php-service",
"service.version" => "1.0.0",
])),
ResourceInfoFactory::defaultResource()
);
// Set up log export
$transportFactory = new OtlpHttpTransportFactory();
$transport = $transportFactory->create(
'https://api.uptrace.dev/v1/logs',
'application/json',
['uptrace-dsn' => $dsn],
TransportFactoryInterface::COMPRESSION_GZIP,
);
$exporter = new LogsExporter($transport);
$processor = new BatchLogRecordProcessor($exporter, ClockFactory::getDefault());
$loggerProvider = LoggerProvider::builder()
->setResource($resource)
->addLogRecordProcessor($processor)
->build();
Sdk::builder()
->setLoggerProvider($loggerProvider)
->setAutoShutdown(true)
->buildAndRegisterGlobal();
// Create and use loggers
$logger = $loggerProvider->getLogger('my-app', '1.0.0');
$eventLogger = new EventLogger($logger, 'user-actions');
// Example usage
$record = (new LogRecord('User login successful'))
->setSeverityText('INFO')
->setAttributes([
'user.id' => '12345',
'user.email' => 'user@example.com',
'request.ip' => '192.168.1.100'
]);
$eventLogger->logEvent('user.login', $record);
Auto-Instrumentation
OpenTelemetry PHP provides automatic instrumentation for popular libraries and frameworks. Enable it by installing the auto-instrumentation package:
composer require open-telemetry/auto-instrumentation
Supported Libraries
Auto-instrumentation is available for:
- HTTP clients: Guzzle, cURL
- Databases: PDO, MySQLi, Doctrine DBAL
- Frameworks: Symfony, Laravel, Slim
- Template engines: Twig
- Message queues: RabbitMQ, Redis
Enabling Auto-Instrumentation
Set the following environment variables:
export OTEL_PHP_AUTOLOAD_ENABLED=true
export OTEL_TRACES_EXPORTER=otlp
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
Or configure it programmatically:
use OpenTelemetry\Auto\Instrumentation\Instrumentations;
// Enable all available instrumentations
Instrumentations::registerAll();
// Or enable specific instrumentations
Instrumentations::register([
'guzzle',
'pdo',
'symfony'
]);
Framework Integration
Symfony Integration
For Symfony applications, add the OpenTelemetry bundle:
composer require open-telemetry/opentelemetry-bundle-symfony
Configure in config/packages/opentelemetry.yaml
:
opentelemetry:
resource:
service_name: 'my-symfony-app'
service_version: '1.0.0'
tracing:
enabled: true
sampler: 'always_on'
metrics:
enabled: true
For detailed integration, see the OpenTelemetry Symfony Guide.
Laravel Integration
For Laravel applications, install the OpenTelemetry package:
composer require open-telemetry/opentelemetry-laravel
Publish and configure the config file:
php artisan vendor:publish --provider="OpenTelemetry\Laravel\OpenTelemetryServiceProvider"
For detailed integration, see the OpenTelemetry Laravel Guide.
Slim Framework Integration
For Slim applications, add middleware:
use OpenTelemetry\Contrib\Slim\SlimMiddleware;
use Slim\Factory\AppFactory;
$app = AppFactory::create();
// Add OpenTelemetry middleware
$app->add(new SlimMiddleware());
// Your routes...
$app->get('/', function ($request, $response) {
$response->getBody()->write('Hello World!');
return $response;
});
For detailed integration, see the OpenTelemetry Slim Guide.
Querying Data in Uptrace
Viewing Traces
Once your application is sending traces to Uptrace, you can:
- Browse traces in the Uptrace UI at
https://app.uptrace.dev/traces
- Filter traces by service name, operation, duration, or custom attributes
- Analyze performance using the trace timeline and span details
- Set up alerts for error rates or slow requests
Querying Spans
Use the Uptrace query interface to find specific spans:
# Find all HTTP requests
where _kind = "server" AND http_method exists
# Find slow database queries
where db_system exists AND _dur_ms > 100ms
# Find errors in a specific service
where service_name = "user-service" AND _status_code = "error"
Analyzing Metrics
Access metrics in the Uptrace dashboard:
- System metrics: CPU, memory, disk usage
- Application metrics: Request rates, response times, error rates
- Custom metrics: Business-specific counters and histograms
Searching Logs
Query logs using the Uptrace log explorer:
# Find error logs
where log_severity = "ERROR"
# Find logs from specific user
where user_id = "12345"
# Find logs with specific message
where display_name contains "database"
Best Practices
Span Naming
Use descriptive span names that follow OpenTelemetry conventions:
// Good: Describes the operation
$span = $tracer->spanBuilder('GET /users/{id}')->startSpan();
// Bad: Too generic
$span = $tracer->spanBuilder('request')->startSpan();
Attribute Management
Add meaningful attributes to spans:
$span->setAttributes([
'user.id' => $userId,
'user.role' => $userRole,
'request.size' => strlen($requestBody),
'response.size' => strlen($responseBody)
]);
Error Handling
Properly record exceptions and errors:
try {
// Your code here
} catch (\Exception $e) {
$span->recordException($e);
$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());
throw $e; // Re-throw if needed
}
Memory Management
For long-running processes, ensure proper cleanup:
// End spans explicitly
$span->end();
// Use try-finally for guaranteed cleanup
try {
$span = $tracer->spanBuilder('operation')->startSpan();
// Your code here
} finally {
$span->end();
}
Performance Considerations
Sampling
Configure appropriate sampling rates for production:
use OpenTelemetry\SDK\Trace\Sampler\TraceIdRatioBasedSampler;
// Sample 1% of traces in high-traffic scenarios
$sampler = new TraceIdRatioBasedSampler(0.01);
Batch Processing
Optimize batch processor settings:
$spanProcessor = new BatchSpanProcessor(
$spanExporter,
ClockFactory::getDefault(),
512, // Max queue size
5000, // Schedule delay (ms)
30000, // Export timeout (ms)
512, // Max export batch size
true // Export on shutdown
);
Resource Usage
Monitor resource consumption:
// Check memory usage
$memoryUsage = memory_get_usage(true);
$span->setAttribute('memory.usage', $memoryUsage);
// Monitor CPU time
$cpuStart = microtime(true);
// ... your code ...
$cpuEnd = microtime(true);
$span->setAttribute('cpu.duration', ($cpuEnd - $cpuStart) * 1000);
Troubleshooting
Common Issues
Traces not appearing in Uptrace
- Verify your DSN is correct
- Check network connectivity to
api.uptrace.dev
- Ensure spans are properly ended before script termination
High memory usage
- Reduce batch processor queue size
- Implement sampling for high-traffic applications
- Monitor span creation rate
Missing spans in auto-instrumentation
- Verify auto-instrumentation is enabled
- Check library versions for compatibility
- Review instrumentation configuration
Performance impact
- Implement appropriate sampling rates
- Use asynchronous exporters when possible
- Profile your application with and without telemetry
Debug Mode
Enable debug logging to troubleshoot issues:
$uptrace = Uptrace\Distro::builder()
->setDsn($dsn)
->setServiceName('myservice')
->setDebug(true) // Enable debug mode
->buildAndRegisterGlobal();
Logging Configuration
Configure PHP logging to capture OpenTelemetry messages:
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('log_errors', 1);
ini_set('error_log', '/var/log/php-errors.log');
Health Checks
Implement health checks to monitor telemetry:
function checkTelemetryHealth() {
$tracer = Globals::tracerProvider()->getTracer('health-check');
$span = $tracer->spanBuilder('health-check')->startSpan();
try {
// Test trace creation
$span->setAttribute('health.status', 'ok');
return true;
} catch (\Exception $e) {
$span->recordException($e);
return false;
} finally {
$span->end();
}
}
What's Next?
Now that you have OpenTelemetry PHP configured with Uptrace, explore these advanced topics:
- OpenTelemetry PHP Tracing API - Create custom instrumentations
- OpenTelemetry PHP Metrics API - Implement custom metrics
- OpenTelemetry PHP Resource Detectors - Automatic resource detection