flow php

Telemetry OTLP Bridge

The OTLP (OpenTelemetry Protocol) Bridge provides serializers and transports for sending telemetry data to OpenTelemetry-compatible backends. It extends the Flow Telemetry library with production-ready export capabilities.

Installation

composer require flow-php/telemetry-otlp-bridge:~0.30.0

Transports

Transport Required Extension Supported Serializers
Curl ext-curl JSON, Protobuf
HTTP - JSON, Protobuf
gRPC ext-grpc Protobuf

Serializers

Serializer Required Packages Supported Transports
JSON - Curl, HTTP
Protobuf google/protobuf, open-telemetry/gen-otlp-protobuf Curl, HTTP, gRPC

To install Protobuf dependencies:

composer require google/protobuf open-telemetry/gen-otlp-protobuf

Transports

The bridge provides three transport options for sending telemetry data to OTLP endpoints.

Transport Protocol Use Case Requirements
Curl HTTP (async) Production, low latency ext-curl
HTTP HTTP (sync) Standard PSR-18 integration PSR-18 client
gRPC gRPC High-performance binary protocol ext-grpc

Curl Transport (Recommended)

The Curl transport uses curl_multi for non-blocking I/O, making it ideal for production environments where you want to minimize latency impact on your application.

<?php

use function Flow\Bridge\Telemetry\OTLP\DSL\{
    otlp_curl_transport,
    otlp_curl_options,
    otlp_json_serializer,
};

$transport = otlp_curl_transport(
    endpoint: 'http://localhost:4318',
    serializer: otlp_json_serializer(),
);

// With custom options
$transport = otlp_curl_transport(
    endpoint: 'https://otlp.example.com:4318',
    serializer: otlp_json_serializer(),
    options: otlp_curl_options()
        ->withTimeout(60)
        ->withConnectTimeout(15)
        ->withHeader('Authorization', 'Bearer your-token')
        ->withCompression(),
);

HTTP Transport (PSR-18)

The HTTP transport uses any PSR-18 compatible HTTP client for synchronous requests. This is useful when you want to integrate with an existing HTTP client in your application.

<?php

use function Flow\Bridge\Telemetry\OTLP\DSL\{
    otlp_http_transport,
    otlp_json_serializer,
};

// Using any PSR-18 client (e.g., Symfony HttpClient, Guzzle)
$transport = otlp_http_transport(
    client: $httpClient,
    requestFactory: $psr17Factory,
    streamFactory: $psr17Factory,
    endpoint: 'http://localhost:4318',
    serializer: otlp_json_serializer(),
);

// With authentication headers
$transport = otlp_http_transport(
    client: $httpClient,
    requestFactory: $psr17Factory,
    streamFactory: $psr17Factory,
    endpoint: 'https://otlp.example.com:4318',
    serializer: otlp_json_serializer(),
    headers: [
        'Authorization' => 'Bearer your-token',
    ],
);

gRPC Transport

The gRPC transport uses the native gRPC protocol for high-performance binary communication. It requires the ext-grpc PHP extension.

<?php

use function Flow\Bridge\Telemetry\OTLP\DSL\{
    otlp_grpc_transport,
    otlp_protobuf_serializer,
};

$transport = otlp_grpc_transport(
    endpoint: 'localhost:4317',
    serializer: otlp_protobuf_serializer(),
);

// With authentication
$transport = otlp_grpc_transport(
    endpoint: 'otlp.example.com:4317',
    serializer: otlp_protobuf_serializer(),
    headers: [
        'Authorization' => 'Bearer your-token',
    ],
    insecure: false, // Use TLS
);

Serializers

The bridge provides two serialization formats for OTLP data.

Serializer Format Size Readability Dependencies
JSON Text Larger Human-readable None
Protobuf Binary Smaller Not readable google/protobuf, open-telemetry/gen-otlp-protobuf

JSON Serializer

The default serializer that produces human-readable JSON. Best for development and debugging.

<?php

use function Flow\Bridge\Telemetry\OTLP\DSL\otlp_json_serializer;

$serializer = otlp_json_serializer();

Protobuf Serializer

Binary serialization for smaller payloads and better performance. Recommended for production.

<?php

use function Flow\Bridge\Telemetry\OTLP\DSL\otlp_protobuf_serializer;

$serializer = otlp_protobuf_serializer();

Complete Setup

Here's a complete example showing how to set up telemetry with OTLP export.

Production Setup with Curl Transport

<?php

use function Flow\Telemetry\DSL\{
    telemetry,
    resource,
    tracer_provider,
    meter_provider,
    logger_provider,
    batching_span_processor,
    batching_metric_processor,
    batching_log_processor,
};
use function Flow\Bridge\Telemetry\OTLP\DSL\{
    otlp_curl_transport,
    otlp_curl_options,
    otlp_json_serializer,
    otlp_span_exporter,
    otlp_metric_exporter,
    otlp_log_exporter,
};

$resource = resource([
    'service.name' => 'order-service',
    'service.version' => '1.2.0',
    'deployment.environment' => 'production',
]);

$options = otlp_curl_options()
    ->withTimeout(30)
    ->withCompression();

$transport = otlp_curl_transport(
    endpoint: 'http://otel-collector:4318',
    serializer: otlp_json_serializer(),
    options: $options,
);

$telemetry = telemetry(
    $resource,
    tracer_provider(batching_span_processor(otlp_span_exporter($transport))),
    meter_provider(batching_metric_processor(otlp_metric_exporter($transport))),
    logger_provider(batching_log_processor(otlp_log_exporter($transport))),
);

// Register shutdown handler for graceful termination
$telemetry->registerShutdownFunction();

// Use telemetry
$tracer = $telemetry->tracer('http-handler');
$span = $tracer->span('handle-request');
$span->setAttribute('http.method', 'POST');
$span->setAttribute('http.url', '/api/orders');
$tracer->complete($span);

Setup with Grafana Cloud

<?php

use function Flow\Telemetry\DSL\{
    telemetry,
    resource,
    tracer_provider,
    batching_span_processor,
};
use function Flow\Bridge\Telemetry\OTLP\DSL\{
    otlp_curl_transport,
    otlp_curl_options,
    otlp_protobuf_serializer,
    otlp_span_exporter,
};

$transport = otlp_curl_transport(
    endpoint: 'https://otlp-gateway-prod-eu-west-0.grafana.net/otlp',
    serializer: otlp_protobuf_serializer(),
    options: otlp_curl_options()
        ->withHeader('Authorization', 'Basic ' . base64_encode('instance-id:api-key')),
);

$telemetry = telemetry(
    resource(['service.name' => 'my-app']),
    tracer_provider(batching_span_processor(otlp_span_exporter($transport))),
);

Setup with Honeycomb

<?php

use function Flow\Telemetry\DSL\{
    telemetry,
    resource,
    tracer_provider,
    batching_span_processor,
};
use function Flow\Bridge\Telemetry\OTLP\DSL\{
    otlp_curl_transport,
    otlp_curl_options,
    otlp_protobuf_serializer,
    otlp_span_exporter,
};

$transport = otlp_curl_transport(
    endpoint: 'https://api.honeycomb.io',
    serializer: otlp_protobuf_serializer(),
    options: otlp_curl_options()
        ->withHeader('x-honeycomb-team', 'your-api-key'),
);

$telemetry = telemetry(
    resource(['service.name' => 'my-app']),
    tracer_provider(batching_span_processor(otlp_span_exporter($transport))),
);

OpenTelemetry Collector

The recommended production architecture is to deploy an OpenTelemetry Collector close to your application (same network, Kubernetes cluster, or sidecar container). This approach provides several benefits:

  • Decouple application from backends - Your application sends telemetry to a single local endpoint, unaware of the final destinations
  • Change APM backends without code changes - Switch from Jaeger to Grafana or add Datadog by updating collector configuration only
  • Fan-out to multiple backends - Send the same telemetry data to multiple APM systems simultaneously
  • Offload processing - Batching, retry logic, filtering, and sampling happen in the collector, not your application
  • Reduce network latency - Local collector accepts data quickly; it handles slow or unreliable external connections

Sample Collector Configuration

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 512

exporters:
  # Local Jaeger for development
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true

  # Grafana Cloud
  otlp/grafana:
    endpoint: otlp-gateway-prod-eu-west-0.grafana.net:443
    headers:
      Authorization: Basic ${GRAFANA_AUTH}

  # Honeycomb
  otlp/honeycomb:
    endpoint: api.honeycomb.io:443
    headers:
      x-honeycomb-team: ${HONEYCOMB_API_KEY}

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/jaeger, otlp/grafana, otlp/honeycomb]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/grafana]
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/grafana]

Docker Compose Example

# compose.yaml
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"  # gRPC
      - "4318:4318"  # HTTP
    environment:
      - GRAFANA_AUTH=${GRAFANA_AUTH}
      - HONEYCOMB_API_KEY=${HONEYCOMB_API_KEY}

  app:
    build: .
    environment:
      - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
    depends_on:
      - otel-collector

With this setup, your PHP application only needs to know about http://otel-collector:4318. Adding or removing APM backends becomes a configuration change in the collector, requiring no application redeployment.

Configuration Options

The CurlTransportOptions class provides a fluent interface for configuring the curl transport.

Method Description Default
withTimeout(int $seconds) Request timeout 30
withConnectTimeout(int $seconds) Connection timeout 10
withHeader(string $name, string $value) Add a single header -
withHeaders(array $headers) Set all headers []
withCompression(bool $enabled) Enable gzip compression false
withSslVerification(bool $verifyPeer, bool $verifyHost) SSL verification true, true
withSslCertificate(string $certPath, ?string $keyPath) Client certificate -
withCaInfo(string $caInfoPath) CA certificate bundle -
withProxy(string $proxy) Proxy server -
withFollowRedirects(bool $follow, int $maxRedirects) Redirect behavior true, 3

Example:

<?php

use function Flow\Bridge\Telemetry\OTLP\DSL\otlp_curl_options;

$options = otlp_curl_options()
    ->withTimeout(60)
    ->withConnectTimeout(15)
    ->withHeader('Authorization', 'Bearer token')
    ->withHeader('X-Custom-Header', 'value')
    ->withCompression()
    ->withSslVerification(verifyPeer: true, verifyHost: true)
    ->withProxy('http://proxy:8080');

OTLP Endpoints

Standard OTLP endpoint paths:

Signal HTTP Path gRPC Port
Traces /v1/traces 4317
Metrics /v1/metrics 4317
Logs /v1/logs 4317

The transport automatically appends the correct path based on the signal type. You only need to provide the base endpoint URL.

HTTP endpoints:

  • OpenTelemetry Collector: http://localhost:4318
  • Grafana Cloud: https://otlp-gateway-prod-{region}.grafana.net/otlp
  • Honeycomb: https://api.honeycomb.io

gRPC endpoints:

  • OpenTelemetry Collector: localhost:4317
  • Jaeger: localhost:4317

Contributors

Join us on GitHub external resource
scroll back to top