OpenTelemetry PHP distro for Uptrace
This document explains how to configure OpenTelemetry PHP SDK to export spans, logs, and metrics to Uptrace using OTLP/HTTP.
To learn about OpenTelemetry API, see OpenTelemetry PHP Tracing API and OpenTelemetry PHP Metrics API.
Uptrace PHP
uptrace-php is a thin wrapper over opentelemetry-php that configures OpenTelemetry SDK to export data to Uptrace. It does not add any new functionality and is provided only for your convenience.
First, install Composer using the installation instructions and add the following line to your project's composer.json
file, as this library has not reached a stable release status yet:
"minimum-stability": "dev"
Then, you can install uptrace-php:
composer require uptrace/uptrace
Configuration
You can configure Uptrace client using a DSN (Data Source Name) from the project settings page.
$uptrace = Uptrace\Distro::builder()
// copy your project DSN here or use UPTRACE_DSN env var
->setDsn('https://FIXME@api.uptrace.dev?grpc=4317')
->setServiceName('myservice')
->setServiceVersion('1.0.0')
->setResourceAttributes(['deployment.environment' => 'production'])
->buildAndRegisterGlobal();
// Create a tracer. You can have as many tracers as you need.
$tracer = Globals::tracerProvider()->getTracer('app_or_package_name');
You can also use environment variables to configure the client:
Env var | Description |
---|---|
UPTRACE_DSN | A data source that is used to connect to uptrace.dev. For example, https://<token>@uptrace.dev/<project_id> . |
OTEL_RESOURCE_ATTRIBUTES | Key-value pairs to be used as resource attributes. For example, service.name=myservice,service.version=1.0.0 . |
OTEL_SERVICE_NAME=myservice | Sets the value of the service.name resource attribute. Takes precedence over OTEL_RESOURCE_ATTRIBUTES . |
OTEL_PROPAGATORS | Propagators to be used as a comma separated list. The default is tracecontext,baggage . |
See OpenTelemetry documentation for details.
Quickstart
Spend 5 minutes to install OpenTelemetry distro, generate your first trace, and click the link in your terminal to view the trace.
Step 0. Create an Uptrace project to obtain a DSN (connection string), for example,
https://<token>@api.uptrace.dev?grpc=4317
.Step 1. Add uptrace to the
composer.json
:composer require uptrace/uptrace
Step 2. Copy the code to
main.php
replacing the<dsn>
:
<?php
declare(strict_types=1);
require __DIR__ . '/../../vendor/autoload.php';
use OpenTelemetry\API\Common\Instrumentation\Globals;
use OpenTelemetry\API\Trace\SpanKind;
$uptrace = Uptrace\Distro::builder()
// copy your project DSN here or use UPTRACE_DSN env var
->setDsn('https://FIXME@api.uptrace.dev?grpc=4317')
->setServiceName('myservice')
->setServiceVersion('1.0.0')
->buildAndRegisterGlobal();
// Create a tracer. Usually, tracer is a global variable.
$tracer = Globals::tracerProvider()->getTracer('app_or_package_name');
// Create a root span (a trace) to measure some operation.
$main = $tracer->spanBuilder('main-operation')->startSpan();
// Future spans will be parented to the currently active span.
$mainScope = $main->activate();
$child1 = $tracer->spanBuilder('GET /posts/:id')
->setSpanKind(SpanKind::KIND_SERVER)
->startSpan();
$child1Scope = $child1->activate();
$child1->setAttribute('http.method"', 'GET');
$child1->setAttribute('http.route"', '/posts/:id');
$child1->setAttribute('http.url', 'http://localhost:8080/posts/123');
$child1->setAttribute('http.status_code', 200);
try {
throw new \Exception('Some error message');
} catch (\Exception $exc) {
$child1->setStatus('error', $exc->getMessage());
$child1->recordException($exc);
}
$child1Scope->detach();
$child1->end();
$child2 = $tracer->spanBuilder('child2-of-main')->startSpan();
$child2Scope = $child1->activate();
$child2->setAttributes([
'db.system' => 'mysql',
'db.statement' => 'SELECT * FROM posts LIMIT 100',
]);
$child2Scope->detach();
$child2->end();
// End the span and detached context when the operation we are measuring is done.
$mainScope->detach();
$main->end();
echo $uptrace->traceUrl($main) . PHP_EOL;
- Step 3. Run the example to get a link for the generated trace:
php main.php
trace URL: https://uptrace.dev/traces/<trace_id>
- Step 4. Follow the link to view the generated trace:
Already using OTLP exporter?
If you are already using OTLP exporter, you can continue to use it with Uptrace by changing some configuration options.
To maximize performance and efficiency, consider the following recommendations when configuring OpenTelemetry SDK.
Recommendation | Signals | Significance |
---|---|---|
Use BatchSpanProcessor to export multiple spans in a single request. | All | Essential |
Enable gzip compression to compress the data before sending and reduce the traffic cost. | All | Essential |
Prefer delta metrics temporality, because such metrics are smaller and Uptrace must convert cumulative metrics to delta anyway. | Metrics | Recommended |
Prefer Protobuf encoding over JSON. | All | Recommended |
Use AWS X-Ray ID generator for OpenTelemetry. | Traces, Logs | Optional |
To configure OpenTelemetry to send data to Uptrace, use the provided endpoint and pass the DSN via uptrace-dsn
header:
Transport | Endpoint | Port |
---|---|---|
gRPC | https://otlp.uptrace.dev:4317 | 4317 |
HTTPS | https://otlp.uptrace.dev | 443 |
Most languages allow to configure OTLP exporter using environment variables:
# Uncomment the appropriate protocol for your programming language.
# Only for OTLP/gRPC
#export OTEL_EXPORTER_OTLP_ENDPOINT="https://otlp.uptrace.dev:4317"
# Only for OTLP/HTTP
#export OTEL_EXPORTER_OTLP_ENDPOINT="https://otlp.uptrace.dev"
# Pass Uptrace DSN in gRPC/HTTP headers.
export OTEL_EXPORTER_OTLP_HEADERS="uptrace-dsn=https://FIXME@api.uptrace.dev?grpc=4317"
# Enable gzip compression.
export OTEL_EXPORTER_OTLP_COMPRESSION=gzip
# Enable exponential histograms.
export OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION=BASE2_EXPONENTIAL_BUCKET_HISTOGRAM
# Prefer delta temporality.
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=DELTA
When configuring BatchSpanProcessor
, use the following settings:
# Maximum allowed time to export data in milliseconds.
export OTEL_BSP_EXPORT_TIMEOUT=10000
# Maximum batch size.
# Using larger batch sizes can be problematic,
# because Uptrace rejects requests larger than 20MB.
export OTEL_BSP_MAX_EXPORT_BATCH_SIZE=10000
# Maximum queue size.
# Increase queue size if you have lots of RAM, for example,
# `10000 * number_of_gigabytes`.
export OTEL_BSP_MAX_QUEUE_SIZE=30000
# Max concurrent exports.
# Setting this to the number of available CPUs might be a good idea.
export OTEL_BSP_MAX_CONCURRENT_EXPORTS=2
Exporting traces
Here is how you can export traces to Uptrace following the recommendations above:
<?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');
}
echo 'using DSN: ', $dsn, PHP_EOL;
$resource = ResourceInfoFactory::emptyResource()->merge(
ResourceInfo::create(Attributes::create([
"service.name" => "test",
"service.version" => "1.0.0",
])),
ResourceInfoFactory::defaultResource()
);
$transportFactory = new OtlpHttpTransportFactory();
$transport = $transportFactory->create(
'https://otlp.uptrace.dev/v1/traces',
'application/json',
['uptrace-dsn' => $dsn],
TransportFactoryInterface::COMPRESSION_GZIP,
);
$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,
);
$tracerProvider = TracerProvider::builder()
->addSpanProcessor($spanProcessor)
->setResource($resource)
->setSampler(new ParentBased(new AlwaysOnSampler()))
->build();
Sdk::builder()
->setTracerProvider($tracerProvider)
->setPropagator(TraceContextPropagator::getInstance())
->setAutoShutdown(true)
->buildAndRegisterGlobal();
// Create a tracer. Usually, tracer is a global variable.
$tracer = \OpenTelemetry\API\Globals::tracerProvider()->getTracer('app_or_package_name');
// Create a root span (a trace) to measure some operation.
$main = $tracer->spanBuilder('main-operation')->startSpan();
// Future spans will be parented to the currently active span.
$mainScope = $main->activate();
$child1 = $tracer->spanBuilder('GET /posts/:id')
->setSpanKind(SpanKind::KIND_SERVER)
->startSpan();
$child1Scope = $child1->activate();
$child1->setAttribute('http.method"', 'GET');
$child1->setAttribute('http.route"', '/posts/:id');
$child1->setAttribute('http.url', 'http://localhost:8080/posts/123');
$child1->setAttribute('http.status_code', 200);
try {
throw new \Exception('Some error message');
} catch (\Exception $exc) {
$child1->setStatus('error', $exc->getMessage());
$child1->recordException($exc);
}
$child1Scope->detach();
$child1->end();
$child2 = $tracer->spanBuilder('child2-of-main')->startSpan();
$child2Scope = $child1->activate();
$child2->setAttributes([
'db.system' => 'mysql',
'db.statement' => 'SELECT * FROM posts LIMIT 100',
]);
$child2Scope->detach();
$child2->end();
// End the span and detached context when the operation we are measuring is done.
$mainScope->detach();
$main->end();
$context = $main->getContext();
echo sprintf('https://app.uptrace.dev/traces/%s', $context->getTraceId()) . PHP_EOL;
Exporting metrics
Here is how you can configure OTLP/HTTP exporter for Uptrace following the recommendations above:
<?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');
}
echo 'using DSN: ', $dsn, PHP_EOL;
$resource = ResourceInfoFactory::emptyResource()->merge(
ResourceInfo::create(Attributes::create([
"service.name" => "test",
"service.version" => "1.0.0",
])),
ResourceInfoFactory::defaultResource()
);
$transportFactory = new OtlpHttpTransportFactory();
$reader = new ExportingReader(
new MetricExporter(
$transportFactory->create(
'https://otlp.uptrace.dev/v1/metrics',
'application/json',
['uptrace-dsn' => $dsn],
TransportFactoryInterface::COMPRESSION_GZIP,
)
),
ClockFactory::getDefault()
);
$meterProvider = MeterProvider::builder()
->setResource($resource)
->addReader($reader)
->build();
$meter = $meterProvider->getMeter('app_or_package_name');
$counter = $meter->createCounter('uptrace.demo.counter_name', '', 'counter description');
for ($i = 0; $i < 100000; $i++) {
$counter->add(1);
if ($i % 10 === 0) {
$reader->collect();
}
usleep(100);
}
Exporting logs
Here is how you can export logs to Uptrace following the recommendations above:
<?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');
}
echo 'using DSN: ', $dsn, PHP_EOL;
$resource = ResourceInfoFactory::emptyResource()->merge(
ResourceInfo::create(Attributes::create([
"service.name" => "test",
"service.version" => "1.0.0",
])),
ResourceInfoFactory::defaultResource()
);
$transportFactory = new OtlpHttpTransportFactory();
$transport = $transportFactory->create(
'https://otlp.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();
$logger = $loggerProvider->getLogger('demo', '1.0', 'http://schema.url', [/*attributes*/]);
$eventLogger = new EventLogger($logger, 'my-domain');
$record = (new LogRecord('hello world'))
->setSeverityText('INFO')
->setAttributes([/*attributes*/]);
$eventLogger->logEvent('foo', $record);
Auto-instrumentation
OpenTelemetry PHP auto-instrumentation is a powerful feature that allows you to collect telemetry data from your PHP applications with minimal manual configuration.
It automatically adds instrumentation to your application, capturing telemetry data without requiring you to modify code manually.
To use auto-instrumentation, you need to install OpenTelemetry extension.
Setup development environment. Installing from source requires proper development environment and some dependencies:
sudo apt-get install gcc make autoconf php-dev
brew install gcc make autoconf
Build/install the extension. With your environment set up you can install the extension:
pecl install opentelemetry
php pickle.phar install opentelemetry
install-php-extensions opentelemetry
Add the extension to your
php.ini
file (runphp --ini
to find out file location):[opentelemetry] extension=opentelemetry.so
Verify that the extension is installed and enabled:
php -m | grep opentelemetry
What's next?
Next, instrument more operations to get a more detailed picture. Try to prioritize network calls, disk operations, database queries, error and logs.
You can also create your own instrumentations using OpenTelemetry PHP Tracing API.