Guia para implementar tracing distribuído com OpenTelemetry em aplicações PHP monolito

Por · Publicado em · Atualizado em · 9 min de leitura

padrões monocromáticos com blocos quadrados formando um traço sinuoso sobre fundo claro
Representação abstrata de fluxo e conexão, adequada para tracing distribuído em arquitetura Foto: Google DeepMind / Pexels

Este guia mostra, passo a passo, como implementar tracing distribuído com OpenTelemetry em aplicações PHP monolito. Você vai instrumentar requisições HTTP, consultas ao banco, chamadas a APIs externas e jobs, configurar exporters OTLP e definir amostragem segura para produção.

Por que fazer tracing em um monólito PHP

Mesmo em monólitos, há muitos pontos cegos: controladores, serviços internos, filas, crons e chamadas a APIs. Tracing distribuído encadeia tudo em uma timeline única.

Ele acelera o diagnóstico de lentidão, N+1 em SQL, gargalos de I/O e falhas intermitentes. Você mede cada etapa e vê como a requisição percorre o sistema.

Se ainda avalia ferramentas, veja o comparativo de opções para PHP e quando usar OpenTelemetry vs agentes proprietários.

Para completar a observabilidade, correlacione traces com logs estruturados e métricas. A correlação reduz o tempo de análise de incidentes.

  • Identifique rotas e endpoints mais lentos.
  • Enxergue dependências externas com maior latência.
  • Isole consultas SQL e cache misses.
  • Rastreie jobs e crons que afetam picos de carga.

Arquitetura mínima e escolha do exporter

A arquitetura básica tem: SDK do OpenTelemetry no PHP, propagadores para contexto (traceparent), um sampler e um exporter. Em produção, é comum enviar spans para um OpenTelemetry Collector, que repassa ao backend de traces.

OTLP é o formato padrão do ecossistema. Você pode usar HTTP/protobuf (porta 4318) ou gRPC (porta 4317), conforme rede e política de segurança.

Se já possui um backend que aceita Zipkin ou Jaeger diretamente, o collector faz a tradução e buffering. Isso evita acoplar seu monólito ao provedor final.

Exporter Protocolo Quando usar Observações
OTLP/HTTP HTTP 4318 Ambientes com proxies e firewall mais restritos Padrão, simples de depurar com ferramentas HTTP.
OTLP/gRPC gRPC 4317 Baixa latência e conexões de longa duração Pode exigir regras específicas no balanceador.
Zipkin HTTP 9411 Compatibilidade com coletores legados Use via Collector para não acoplar a aplicação.
Jaeger UDP/HTTP Ambientes com Jaeger existente Prefira OTLP via Collector quando possível.

Pré-requisitos e planejamento do rollout

Planeje antes de tocar código. Defina padrão de nomes de serviço e versões. Decida sampling inicial e endpoints do exporter em cada ambiente.

Proteja a privacidade: não capture dados sensíveis como senhas e cartões. Prefira IDs técnicos e hashes. Revise atributos e eventos antes do go‑live.

Garanta que o front controller do monólito seja um ponto único para iniciar e encerrar spans raiz. Em frameworks, use middleware para envolver a requisição.

  • PHP 8.x com Composer.
  • Acesso para editar bootstrap do app ou middleware.
  • Endpoint do OTLP/Collector por ambiente.
  • Strategy de sampling inicial (ex.: 5–10%).
  • Convenções de atributos (http., db., net.*).

Instalação e bootstrap do OpenTelemetry no monólito PHP

Instale os pacotes do SDK e do exporter OTLP via Composer. Em seguida, crie um bootstrap de tracing para registrar o TracerProvider, sampler, processador e exporter.

Os nomes de classes podem variar conforme a versão do SDK. Verifique o namespace instalado pelo Composer e ajuste se necessário. O exemplo abaixo usa OTLP/HTTP.

Carregue este bootstrap no front controller (ex.: public/index.php) para garantir que cada requisição tenha um span raiz e que o encerramento faça o flush.

composer require open-telemetry/sdk open-telemetry/exporter-otlp

Exemplo de bootstrap: provider, sampler e exporter OTLP/HTTP

Este exemplo ilustra uma configuração típica: serviço nomeado, sampler parent-based com proporção, processamento em lote e exportação OTLP/HTTP para um Collector. Ajuste variáveis de ambiente conforme seu ambiente.

Use variáveis de ambiente para endpoints e amostragem, facilitando mudanças sem novo deploy. Encerrar o provider ao final da requisição garante o envio dos spans.

<?php
// bootstrap/tracing.php

use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Context\Propagation\ArrayAccessGetterSetter;
use OpenTelemetry\SDK\Trace\TracerProvider;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\Sampler\ParentBased;
use OpenTelemetry\SDK\Trace\Sampler\TraceIdRatioBased;
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\Contrib\Otlp\Exporter as OtlpExporter;

$serviceName = getenv('OTEL_SERVICE_NAME') ?: 'php-monolith';
$endpoint = getenv('OTEL_EXPORTER_OTLP_ENDPOINT') ?: 'http://localhost:4318';
$sampleRatio = (float) (getenv('OTEL_TRACES_SAMPLER_ARG') ?: '0.1');

$transport = (new OtlpHttpTransportFactory())
    ->create(rtrim($endpoint, '/') . '/v1/traces', 'application/x-protobuf');
$exporter = new OtlpExporter($transport);

$processor = new BatchSpanProcessor($exporter);
$sampler = new ParentBased(new TraceIdRatioBased($sampleRatio));

$tracerProvider = new TracerProvider($processor, $sampler);
Globals::setTracerProvider($tracerProvider);

// Extração do contexto de entrada (W3C traceparent)
$headers = function_exists('getallheaders') ? getallheaders() : [];
$propagator = TraceContextPropagator::getInstance();
$carrier = $headers;
$getterSetter = new ArrayAccessGetterSetter();
$context = $propagator->extract($carrier, $getterSetter);

// Tracer para a aplicação
$tracer = Globals::tracerProvider()->getTracer($serviceName);

// Span raiz da requisição
$span = $tracer->spanBuilder('http.request')->setParent($context)->startSpan();
$span->setAttribute('service.name', $serviceName);
$span->setAttribute('http.method', $_SERVER['REQUEST_METHOD'] ?? 'CLI');
$span->setAttribute('http.target', $_SERVER['REQUEST_URI'] ?? '');
$span->activate();

register_shutdown_function(function () use ($span, $tracerProvider) {
    $span->end();
    $tracerProvider->shutdown();
});

Instrumente requisições, SQL e jobs com spans filhos

Com o tracer no ar, crie spans filhos para operações críticas. Nomeie spans pelo propósito e adicione atributos chave. Registre exceções no span para facilitar a triagem.

No exemplo, criamos spans para o controlador, uma consulta PDO e um job de fila. Em frameworks, adapte para middleware ou handlers específicos.

<?php
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\SpanKind;

$tracer = Globals::tracerProvider()->getTracer('php-monolith');

// Controlador/ação
$controllerSpan = $tracer->spanBuilder('controller:OrdersController@index')
    ->setSpanKind(SpanKind::KIND_INTERNAL)
    ->startSpan();
try {
    // ... lógica do controlador

    // SQL com PDO
    $dbSpan = $tracer->spanBuilder('db.query:orders_by_customer')->startSpan();
    try {
        $dbSpan->setAttribute('db.system', 'mysql');
        $dbSpan->setAttribute('db.statement', 'SELECT * FROM orders WHERE customer_id = ?');
        // $stmt = $pdo->prepare('SELECT * FROM orders WHERE customer_id = ?');
        // $stmt->execute([$customerId]);
    } finally {
        $dbSpan->end();
    }

    // Job de fila
    $jobSpan = $tracer->spanBuilder('queue:send_invoice_email')->startSpan();
    try {
        $jobSpan->setAttribute('messaging.system', 'redis');
        $jobSpan->setAttribute('messaging.destination', 'emails');
        // enqueue(...)
    } finally {
        $jobSpan->end();
    }

} catch (Throwable $e) {
    $controllerSpan->recordException($e);
    $controllerSpan->setAttribute('exception.escaped', true);
    throw $e;
} finally {
    $controllerSpan->end();
}

Propagação de contexto em chamadas externas (traceparent)

Propague o contexto ao chamar APIs. Isso encadeia spans entre serviços. Extraia no inbound, injete no outbound. O padrão é o W3C traceparent.

No exemplo, criamos um span CLIENT e injetamos o header traceparent em uma chamada HTTP. Adapte para sua biblioteca HTTP. O mesmo vale para mensageria.

<?php
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Context\Propagation\ArrayAccessGetterSetter;

$tracer = Globals::tracerProvider()->getTracer('php-monolith');
$clientSpan = $tracer->spanBuilder('http.client:payment/charge')
    ->setSpanKind(SpanKind::KIND_CLIENT)
    ->startSpan();
$scope = $clientSpan->activate();

try {
    // Injeta traceparent nos headers
    $headers = [];
    $propagator = TraceContextPropagator::getInstance();
    $propagator->inject($headers, new ArrayAccessGetterSetter(), Context::getCurrent());

    // Exemplo com cURL
    $ch = curl_init('https://payments.example.com/charge');
    $curlHeaders = [];
    foreach ($headers as $k => $v) {
        $curlHeaders[] = $k . ': ' . $v;
    }
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => $curlHeaders,
    ]);
    $resp = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE) ?: 0;
    $clientSpan->setAttribute('http.status_code', $status);
    curl_close($ch);
} finally {
    $scope->detach();
    $clientSpan->end();
}

Amostragem em produção: estratégias e ajustes seguros

Amostragem controla custo e volume. Use parent-based para manter a coesão: se a raiz foi amostrada, os filhos seguem. Em produção, combine ratio com regras de erro sempre coletado.

Evite mudanças bruscas. Ajuste a razão por ambiente e horário. Para detectar incidentes raros, aumente a amostragem temporariamente via variável de ambiente.

Estrategia Tipo Quando usar Config (exemplo)
always_on head-based Diagnóstico em staging ou testes OTEL_TRACES_SAMPLER=always_on
always_off head-based Desligar temporariamente OTEL_TRACES_SAMPLER=always_off
traceidratio head-based Controle global simples OTEL_TRACES_SAMPLER=traceidratio; OTEL_TRACES_SAMPLER_ARG=0.1
parentbased_traceidratio head-based Manter coesão nos filhos OTEL_TRACES_SAMPLER=parentbased_traceidratio
# Variáveis úteis por ambiente
export OTEL_SERVICE_NAME=php-monolith
export OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,service.version=1.3.0"

Correlacionando trace_id com logs estruturados

Inclua trace_id e span_id nos logs. Isso permite pular de um erro no log para o trace correspondente. Prefira logs JSON com campos estáveis.

Se você já usa logs estruturados, adicione os IDs de contexto ao logger. Veja um guia completo de logs e correlação para padronizar o formato em produção.

<?php
use OpenTelemetry\API\Trace\Span;

$span = Span::getCurrent();
$ctx = $span->getContext();
$traceId = method_exists($ctx, 'getTraceId') ? $ctx->getTraceId() : null;
$spanId  = method_exists($ctx, 'getSpanId')  ? $ctx->getSpanId()  : null;

$log = [
    'level' => 'error',
    'message' => 'Pedido não encontrado',
    'trace_id' => $traceId,
    'span_id' => $spanId,
    'order_id' => $orderId ?? null,
];
error_log(json_encode($log));

Como validar a instrumentação sem backend de traces

Para validar no local, comece com amostragem always_on em um ambiente de testes. Gere requisições e verifique a propagação do header traceparent em chamadas externas.

Você também pode expor temporariamente o trace_id na resposta HTTP de ambientes de teste. Remova isso antes do go‑live. O objetivo é confirmar encadeamento e atributos.

  • Ative always_on apenas em staging.
  • Gere carga realista com scripts simples.
  • Confirme presence de traceparent nas saídas HTTP.
  • Revise atributos para evitar PII em spans.
<?php
// Em ambiente de teste, retorne o trace_id no header para depuração
use OpenTelemetry\API\Trace\Span;
$ctx = Span::getCurrent()->getContext();
if (method_exists($ctx, 'getTraceId')) {
    header('x-trace-id: ' . $ctx->getTraceId());
}

Perguntas frequentes

Monólitos realmente se beneficiam de tracing distribuído?

Sim. Mesmo sem microsserviços, monólitos possuem camadas, chamadas a APIs, jobs e banco de dados. Tracing mapeia o caminho completo da requisição, revelando gargalos e erros que logs isolados não mostram.

Qual exporter devo usar no PHP para OpenTelemetry?

Prefira OTLP. Use HTTP/protobuf quando a rede tem proxies ou inspeção, e gRPC para conexões de longa duração e menor overhead. Em ambos os casos, envie primeiro a um OpenTelemetry Collector quando possível.

Qual impacto de performance do tracing no PHP?

Depende de sampling, volume e processador de spans. Batch com parent-based ratio costuma ter impacto baixo. Evite coletar atributos sensíveis e spans desnecessários. Meça sempre em staging antes do go‑live.

Como evitar capturar dados sensíveis nos spans?

Defina convenções de atributos. Não registre PII. Masque ou faça hash de identificadores quando necessário. Revise eventos e atributos antes da produção e em auditorias periódicas.

Como correlacionar logs com traces no PHP?

Inclua trace_id e span_id nos logs. Em logs JSON, use campos estáveis. Assim você salta do log para o trace e volta. Um padrão de logs consistente acelera muito a análise de incidentes.

Posso começar com 100% de amostragem?

Sim, em ambientes de teste. Em produção, use parentbased_traceidratio com uma razão controlada. Eleve temporariamente a amostragem ao investigar incidentes, depois retorne ao valor seguro.

Precisa de ajuda para aplicar no seu monólito?

Se você quer acelerar o rollout de tracing com OpenTelemetry no seu PHP, revisar a instrumentação e padronizar atributos, vamos conversar sobre o seu contexto.

Falar sobre seu contexto

Veja também