Guia para implementar tracing distribuído com OpenTelemetry em aplicações PHP monolito
Por Kevin Marcondes · Publicado em · Atualizado em · 9 min de leitura
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 contextoVeja também
- Guia: checklist de pré‑deploy e plano de rollback rápido para aplicações PHP em produção
- Checklist de segurança para pipelines CI/CD com runners self‑hosted e secrets management
- Guia: centralização de logs com Fluentd e Elasticsearch para aplicações PHP em VPS
- Guia: organizar migrations e rollbacks em PostgreSQL para equipes pequenas