All Products
Search
Document Center

Application Real-Time Monitoring Service:Report trace data from PHP applications through OpenTelemetry

Last Updated:Mar 11, 2026

When you need distributed tracing for PHP applications running on Alibaba Cloud, OpenTelemetry provides a vendor-neutral instrumentation standard. Instrument your PHP application with the OpenTelemetry SDK and report trace data to Managed Service for OpenTelemetry. After traces reach Application Real-Time Monitoring Service (ARMS), you can view application topology, traces, abnormal and slow transactions, and SQL analysis in the ARMS console.

Choose one of the following instrumentation approaches based on your requirements:

ApproachPHP versionBest for
Automatic instrumentationPHP 8.0+Zero-code setup using the OpenTelemetry PHP extension. Works with supported frameworks.
Manual instrumentationPHP 7.4+Full control over spans, attributes, and events. Use for custom spans or unsupported frameworks.

Prerequisites

Before you begin, make sure that you have:

  • PHP 8.0 or later (automatic instrumentation) or PHP 7.4 or later (manual instrumentation)

  • Composer

  • PECL

  • An OTLP endpoint and authentication token from the ARMS console (see the Get an endpoint section below)

Get an endpoint

  1. Log on to the ARMS console.

  2. In the left-side navigation pane, click Integration Center.

  3. In the Server-side Applications section, click the OpenTelemetry card.

  4. In the OpenTelemetry panel, click the Start Integration tab and select a region.

    When you access a region for the first time, resources are automatically initialized there.
  5. Set Connection Type and Export Protocol, then copy the endpoint.

    ParameterOptionsGuidance
    Connection TypeAlibaba Cloud VPC Network, Public NetworkUse VPC if your service runs in the same Alibaba Cloud region. Otherwise, use Public Network.
    Export ProtocolHTTP (recommended), gRPCChoose based on your client's protocol support.

    Endpoint configuration

Supported frameworks for automatic instrumentation

The OpenTelemetry PHP SDK supports automatic instrumentation for the following frameworks. For the full list, see the OpenTelemetry Registry.

  • CodeIgniter Framework

  • Laravel

  • MongoDB

  • PDO

  • PHP HTTP Async Client

  • PHP IO

  • PSR-3

  • PSR-15 middleware

  • PSR-18 HTTP clients

  • Slim

  • Symfony

  • WordPress

  • Yii

Sample code

Download the complete sample code from php-demo on GitHub.

Automatic instrumentation

Automatic instrumentation uses the OpenTelemetry PHP extension to generate traces without modifying your application code. The following steps use a Slim-based dice-rolling app as an example.

Step 1: Create the application

  1. Initialize the project:

       mkdir <project-name> && cd <project-name>
       composer init \
         --no-interaction \
         --stability beta \
         --require slim/slim:"^4" \
         --require slim/psr7:"^1"
       composer update
  2. Create an index.php file in the project directory:

       <?php
       use Psr\Http\Message\ResponseInterface as Response;
       use Psr\Http\Message\ServerRequestInterface as Request;
       use Slim\Factory\AppFactory;
    
       require __DIR__ . '/vendor/autoload.php';
    
       $app = AppFactory::create();
    
       $app->get('/rolldice', function (Request $request, Response $response) {
           $result = random_int(1,6);
           $response->getBody()->write(strval($result));
           return $response;
       });
    
       $app->run();
  3. Verify the app runs: Open http://localhost:8080/rolldice in a browser. You should see a random number between 1 and 6.

       php -S localhost:8080

Step 2: Build the OpenTelemetry PHP extension

  1. Install build dependencies:

    • macOS:

      brew install gcc make autoconf
    • Linux (apt):

      sudo apt-get install gcc make autoconf
  2. Build and install the extension with PECL: A successful build ends with output similar to:

       pecl install opentelemetry
       Build process completed successfully
       Installing '/opt/homebrew/Cellar/php/8.2.8/pecl/20220829/opentelemetry.so'
       install ok: channel://pecl.php.net/opentelemetry-1.0.0beta6
       Extension opentelemetry enabled in php.ini
  3. (Optional) If the output does not include Extension opentelemetry enabled in php.ini, manually add the extension to your php.ini file:

       [opentelemetry]
       extension=opentelemetry.so
  4. Verify the extension is loaded: Expected output: Alternatively, run php --ri opentelemetry for detailed extension info:

       php -m | grep opentelemetry
       opentelemetry
       opentelemetry
       opentelemetry support => enabled
       extension version => 1.0.0beta6

Step 3: Install instrumentation dependencies

# Install the gRPC extension (may take several minutes)
pecl install grpc
composer config allow-plugins.php-http/discovery false
composer require \
  open-telemetry/sdk \
  open-telemetry/opentelemetry-auto-slim \
  open-telemetry/exporter-otlp \
  php-http/guzzle7-adapter \
  open-telemetry/transport-grpc
PackagePurpose
open-telemetry/sdkCore OpenTelemetry SDK for PHP
open-telemetry/opentelemetry-auto-slimAutomatic instrumentation for Slim
open-telemetry/exporter-otlpOTLP exporter
php-http/guzzle7-adapterHTTP client adapter
open-telemetry/transport-grpcgRPC transport layer

Step 4: Run the instrumented application

Start the application with environment variables that configure the OpenTelemetry SDK:

env OTEL_PHP_AUTOLOAD_ENABLED=true \
    OTEL_SERVICE_NAME=<your-service-name> \
    OTEL_TRACES_EXPORTER=otlp \
    OTEL_METRICS_EXPORTER=none \
    OTEL_LOGS_EXPORTER=none \
    OTEL_EXPORTER_OTLP_PROTOCOL=grpc \
    OTEL_EXPORTER_OTLP_ENDPOINT=<endpoint> \
    OTEL_EXPORTER_OTLP_HEADERS=Authentication=<token> \
    OTEL_PROPAGATORS=baggage,tracecontext \
    php -S localhost:8080

Replace the following placeholders with your values:

PlaceholderDescriptionExample
<your-service-name>Application name displayed in ARMSphp-demo
<endpoint>gRPC endpoint from the Prerequisites sectionhttp://tracing-analysis-dc-hz.aliyuncs.com:8090
<token>Authentication token from the Prerequisites section--

Verify

  1. Open http://localhost:8080/rolldice in a browser. Each request generates a trace automatically.

  2. Log on to the Managed Service for OpenTelemetry console.

  3. On the Applications page, find your application and click its name to view traces.

It may take a few minutes before traces appear in the console. If traces still do not appear, verify that the OpenTelemetry extension is loaded (php -m | grep opentelemetry) and that your endpoint and token are correct.

Manual instrumentation

Manual instrumentation gives you full control over span creation, attributes, events, and error recording. Use this approach when automatic instrumentation does not cover your use case or when you need custom spans alongside auto-instrumented ones.

Step 1: Create the application

Follow the same steps as in the Automatic instrumentation > Step 1 section to create the Slim-based dice-rolling app.

Step 2: Install OpenTelemetry SDK dependencies

Install the HTTP client library and the OpenTelemetry SDK packages. Choose the tab that matches your export protocol.

gRPC

composer require guzzlehttp/guzzle
composer require \
  open-telemetry/sdk \
  open-telemetry/exporter-otlp
# Install the gRPC extension if not already installed
pecl install grpc
composer require open-telemetry/transport-grpc

HTTP

composer require guzzlehttp/guzzle
composer require \
  open-telemetry/sdk \
  open-telemetry/exporter-otlp

Step 3: Write the initialization code

Create an opentelemetry_util.php file in the same directory as index.php. This file sets up the TracerProvider, exporter, and sampler.

gRPC

<?php
use OpenTelemetry\API\Common\Instrumentation\Globals;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Sdk;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessorBuilder;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SemConv\ResourceAttributes;
use OpenTelemetry\Contrib\Grpc\GrpcTransportFactory;
use OpenTelemetry\Contrib\Otlp\OtlpUtil;
use OpenTelemetry\API\Signals;

function initOpenTelemetry()
{
    // 1. Define the service resource
    $resource = ResourceInfoFactory::emptyResource()->merge(ResourceInfo::create(Attributes::create([
        ResourceAttributes::SERVICE_NAME => '<your-service-name>',  // Required
        ResourceAttributes::HOST_NAME => '<your-host-name>'         // Optional
    ])));

    // 2. Create a SpanExporter that sends data over gRPC
    $headers = [
        'Authentication' => '<your-token>',
    ];
    $transport = (new GrpcTransportFactory())->create(
        '<grpc-endpoint>' . OtlpUtil::method(Signals::TRACE),
        'application/x-protobuf',
        $headers
    );
    $spanExporter = new SpanExporter($transport);

    // 3. Build and register the global TracerProvider
    $tracerProvider = TracerProvider::builder()
        ->addSpanProcessor(
            (new BatchSpanProcessorBuilder($spanExporter))->build()
        )
        ->setResource($resource)
        ->setSampler(new ParentBased(new AlwaysOnSampler()))
        ->build();

    Sdk::builder()
        ->setTracerProvider($tracerProvider)
        ->setPropagator(TraceContextPropagator::getInstance())
        ->setAutoShutdown(true)   // Flush pending spans on shutdown
        ->buildAndRegisterGlobal();
}
?>

Replace the following placeholders:

PlaceholderDescription
<your-service-name>Application name displayed in ARMS
<your-host-name>Hostname of your server
<your-token>Authentication token for gRPC export
<grpc-endpoint>gRPC endpoint from the Prerequisites section

HTTP

<?php
use OpenTelemetry\API\Common\Instrumentation\Globals;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Export\Stream\StreamTransportFactory;
use OpenTelemetry\SDK\Resource\ResourceInfo;
use OpenTelemetry\SDK\Resource\ResourceInfoFactory;
use OpenTelemetry\SDK\Sdk;
use OpenTelemetry\SDK\Trace\Sampler\AlwaysOnSampler;
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessorBuilder;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SemConv\ResourceAttributes;
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\SDK\Common\Export\TransportFactoryInterface;

function initOpenTelemetry()
{
    // 1. Define the service resource
    $resource = ResourceInfoFactory::emptyResource()->merge(ResourceInfo::create(Attributes::create([
        ResourceAttributes::SERVICE_NAME => '<your-service-name>',  // Required
        ResourceAttributes::HOST_NAME => '<your-host-name>'         // Optional
    ])));

    // 2. Create a SpanExporter that sends data over HTTP
    $transport = (new OtlpHttpTransportFactory())->create(
        '<http-endpoint>',
        'application/x-protobuf'
    );
    $spanExporter = new SpanExporter($transport);

    // 3. Build and register the global TracerProvider
    $tracerProvider = TracerProvider::builder()
        ->addSpanProcessor(
            (new BatchSpanProcessorBuilder($spanExporter))->build()
        )
        ->setResource($resource)
        ->setSampler(new ParentBased(new AlwaysOnSampler()))
        ->build();

    Sdk::builder()
        ->setTracerProvider($tracerProvider)
        ->setPropagator(TraceContextPropagator::getInstance())
        ->setAutoShutdown(true)   // Flush pending spans on shutdown
        ->buildAndRegisterGlobal();
}
?>

Replace the following placeholders:

PlaceholderDescription
<your-service-name>Application name displayed in ARMS
<your-host-name>Hostname of your server
<http-endpoint>HTTP endpoint from the Prerequisites section

Step 4: Add instrumentation to the application

Update index.php to initialize OpenTelemetry and create spans.

  1. Import the required packages and initialize OpenTelemetry at the top of index.php:

       <?php
       use OpenTelemetry\API\Common\Instrumentation\Globals;
       use OpenTelemetry\SDK\Common\Attribute\Attributes;
       use OpenTelemetry\SDK\Trace\TracerProvider;
    
       require __DIR__ . '/opentelemetry_util.php';
    
       // Initialize OpenTelemetry: configure service name, exporter, and global TracerProvider
       initOpenTelemetry();
  2. Create a span in the /rolldice route to capture request-level tracing:

       $app->get('/rolldice', function (Request $request, Response $response) {
           // Get a tracer instance
           $tracer = \OpenTelemetry\API\Globals::tracerProvider()->getTracer('my-tracer');
    
           // Start a span for this operation
           $span = $tracer->spanBuilder("/rolldice")->startSpan();
           $span->setAttribute("http.method", "GET");
           $span->addEvent("Init");
    
           // Configure events with attributes
           $eventAttributes = Attributes::create([
               "key1" => "value",
               "key2" => 3.14159,
           ]);
    
           // Business logic
           $result = random_int(1,6);
           $response->getBody()->write(strval($result));
    
           $span->addEvent("End");
           $span->end();
    
           return $response;
       });
  3. Create nested spans to trace multi-step operations. The following /rolltwodices route demonstrates a parent-child span relationship:

       $app->get('/rolltwodices', function (Request $request, Response $response) {
           $tracer = \OpenTelemetry\API\Globals::tracerProvider()->getTracer('my-tracer');
    
           // Create a parent span
           $parentSpan = $tracer->spanBuilder("/rolltwodices/parent")->startSpan();
           $scope = $parentSpan->activate();
    
           $value1 = random_int(1,6);
    
           // Create a child span within the parent scope
           $childSpan = $tracer->spanBuilder("/rolltwodices/parent/child")->startSpan();
    
           // Business logic
           $value2 = random_int(1,6);
           $result = "dice1: " . $value1 . ", dice2: " . $value2;
    
           // End spans and detach scope
           $childSpan->end();
           $parentSpan->end();
           $scope->detach();
    
           $response->getBody()->write(strval($result));
           return $response;
       });
  4. Record exceptions on spans. The following /error route demonstrates error handling:

       $app->get('/error', function (Request $request, Response $response) {
           $tracer = \OpenTelemetry\API\Globals::tracerProvider()->getTracer('my-tracer');
           $span3 = $tracer->spanBuilder("/error")->startSpan();
    
           try {
               // Simulate an exception
               throw new \Exception('exception!');
           } catch (\Throwable $t) {
               // Mark the span as an error
               $span3->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_ERROR, "exception in span3!");
               // Record the exception with stack trace
               $span3->recordException($t, ['exception.escaped' => true]);
           } finally {
               $span3->end();
               $response->getBody()->write("error");
               return $response;
           }
       });

Step 5: Run and verify

  1. Start the application:

       php -S localhost:8080
  2. Send requests to generate trace data:

       http://localhost:8080/rolldice
       http://localhost:8080/rolltwodices
       http://localhost:8080/error
  3. Log on to the Managed Service for OpenTelemetry console. On the Applications page, find your application and click its name to view traces.

It may take a few minutes before traces appear in the console. If traces still do not appear, verify that your endpoint and token are correct, and that your server can reach the endpoint over the network.

What's next