Última atualização: 26/11/2025
Documentação oficial: https://opentelemetry.io/docs/languages/php/instrumentation/
Versão mínima suportada: PHP 7.4+
Este guia descreve como implementar instrumentação manual do OpenTelemetry em aplicações PHP/Laravel, seguindo a documentação oficial. A infraestrutura (Docker, variáveis de ambiente, rede) já está configurada. Este guia foca apenas nas modificações de código.
Importante: Se você está instrumentando uma biblioteca, instale apenas o pacote da API (
open-telemetry/api). Se está instrumentando uma aplicação, instale o SDK completo.
composer require \
open-telemetry/api \
open-telemetry/sdk \
open-telemetry/exporter-otlp \
open-telemetry/sem-conv \
guzzlehttp/guzzleO que cada pacote faz:
open-telemetry/api- Interface da API OpenTelemetryopen-telemetry/sdk- SDK completo (necessário apenas em aplicações)open-telemetry/exporter-otlp- Exportador OTLP para enviar dadosopen-telemetry/sem-conv- Convenções semânticas oficiaisguzzlehttp/guzzle- HTTP client para transporte PSR-18
composer require \
open-telemetry/api \
open-telemetry/sem-convApenas para aplicações - Crie o arquivo bootstrap/instrumentation.php:
<?php
/**
* OpenTelemetry SDK Initialization
*
* Esta é a inicialização do SDK global. Deve ser chamada apenas uma vez
* no início da aplicação, antes de qualquer código de negócio.
*
* Ref: https://opentelemetry.io/docs/languages/php/instrumentation/
*/
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
use OpenTelemetry\Contrib\Otlp\LogsExporter;
use OpenTelemetry\Contrib\Otlp\MetricExporter;
use OpenTelemetry\Contrib\Otlp\SpanExporter;
use OpenTelemetry\SDK\Common\Attribute\Attributes;
use OpenTelemetry\SDK\Common\Export\Http\PsrTransportFactory;
use OpenTelemetry\SDK\Logs\LoggerProvider;
use OpenTelemetry\SDK\Logs\Processor\SimpleLogRecordProcessor;
use OpenTelemetry\SDK\Metrics\MeterProvider;
use OpenTelemetry\SDK\Metrics\MetricReader\ExportingReader;
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\TracerProvider;
use OpenTelemetry\SemConv\Attributes\ServiceAttributes;
use OpenTelemetry\SemConv\Incubating\Attributes\DeploymentIncubatingAttributes;
use OpenTelemetry\SemConv\Incubating\Attributes\ServiceIncubatingAttributes;
require_once __DIR__ . '/../vendor/autoload.php';
// ==================== RESOURCE ATTRIBUTES ====================
// Define os atributos do recurso (aplicação) de forma semântica
$resource = ResourceInfoFactory::emptyResource()->merge(
ResourceInfo::create(
Attributes::create([
ServiceIncubatingAttributes::SERVICE_NAMESPACE => getenv('OTEL_SERVICE_NAMESPACE') ?: 'your-namespace',
ServiceAttributes::SERVICE_NAME => getenv('OTEL_SERVICE_NAME') ?: 'laravel-app',
ServiceAttributes::SERVICE_VERSION => getenv('OTEL_SERVICE_VERSION') ?: '1.0.0',
DeploymentIncubatingAttributes::DEPLOYMENT_ENVIRONMENT_NAME => getenv('OTEL_DEPLOYMENT_ENVIRONMENT') ?: 'production',
])
)
);
// ==================== HTTP TRANSPORT ====================
// Configura o transporte HTTP para enviar dados ao coletor OTLP
$httpClient = new Client();
$httpFactory = new HttpFactory();
$transportFactory = new PsrTransportFactory(
$httpClient,
$httpFactory,
$httpFactory
);
$otlpEndpoint = getenv('OTEL_EXPORTER_OTLP_ENDPOINT') ?: 'http://seu-backend:4317';
// ==================== TRACES ====================
// Configurar exportador e processador de spans
$spanExporter = new SpanExporter(
$transportFactory->create(
$otlpEndpoint . '/v1/traces',
'application/json'
)
);
$tracerProvider = TracerProvider::builder()
->addSpanProcessor(
new SimpleSpanProcessor($spanExporter)
)
->setResource($resource)
->setSampler(new ParentBased(new AlwaysOnSampler()))
->build();
// ==================== METRICS ====================
// Configurar exportador e leitor de métricas
$metricExporter = new MetricExporter(
$transportFactory->create(
$otlpEndpoint . '/v1/metrics',
'application/json'
)
);
$metricReader = new ExportingReader($metricExporter);
$meterProvider = MeterProvider::builder()
->setResource($resource)
->addReader($metricReader)
->build();
// ==================== LOGS ====================
// Configurar exportador e processador de logs
$logsExporter = new LogsExporter(
$transportFactory->create(
$otlpEndpoint . '/v1/logs',
'application/json'
)
);
$loggerProvider = LoggerProvider::builder()
->setResource($resource)
->addLogRecordProcessor(
new SimpleLogRecordProcessor($logsExporter)
)
->build();
// ==================== REGISTRAR GLOBALMENTE ====================
// Isto torna os provedores disponíveis via OpenTelemetry\API\Globals
Sdk::builder()
->setTracerProvider($tracerProvider)
->setMeterProvider($meterProvider)
->setLoggerProvider($loggerProvider)
->setPropagator(TraceContextPropagator::getInstance())
->setAutoShutdown(true)
->buildAndRegisterGlobal();Edite public/index.php e adicione a inicialização logo após o autoload do Composer:
<?php
define('LARAVEL_START', microtime(true));
// Autoload do Composer
require __DIR__.'/../vendor/autoload.php';
// ====================================================
// INICIALIZAR OPENTELEMETRY (adicionar esta linha)
// ====================================================
require_once __DIR__.'/../bootstrap/instrumentation.php';
// Resto da aplicação
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);Sempre adquira um tracer quando precisar, em vez de compartilhar uma instância global:
<?php
use OpenTelemetry\API\Globals;
class MinhaClasse
{
public function processar()
{
// Adquirir tracer - escopo de instrumentação único
$tracerProvider = Globals::tracerProvider();
$tracer = $tracerProvider->getTracer(
'minha-aplicacao', // nome (obrigatório)
'1.0.0', // versão (recomendado)
'https://opentelemetry.io/schemas/1.24.0' // schema URL (opcional)
);
// Criar span para operação
$span = $tracer->spanBuilder('operacao-processar')
->startSpan();
try {
// Sua lógica aqui
$resultado = $this->executarLogica();
// Adicionar eventos
$span->addEvent('Processamento concluído');
return $resultado;
} catch (\Throwable $exception) {
// Registrar exceção no span
$span->recordException($exception);
throw $exception;
} finally {
// Sempre finalizar o span
$span->end();
}
}
}Para rastrear operações aninhadas, ative o span pai:
private function rollOnce()
{
$tracer = Globals::tracerProvider()->getTracer('dice-lib', '0.1.0');
// Obter o span atual (pai)
$parent = \OpenTelemetry\API\Trace\Span::getCurrent();
$scope = $parent->activate();
try {
// Criar span filho
$span = $tracer->spanBuilder('rollOnce')
->startSpan();
$result = random_int(1, 6);
$span->setAttribute('dice.result', $result);
$span->end();
return $result;
} finally {
// Obrigatório desativar o escopo
$scope->detach();
}
}Use as convenções semânticas oficiais para atributos padrão:
<?php
use OpenTelemetry\SemConv\Attributes\CodeAttributes;
use OpenTelemetry\SemConv\Attributes\DbAttributes;
use OpenTelemetry\SemConv\TraceAttributes;
// Atributos de código
$span->setAttribute(CodeAttributes::CODE_FUNCTION_NAME, __FUNCTION__);
$span->setAttribute(CodeAttributes::CODE_FILEPATH, __FILE__);
$span->setAttribute(CodeAttributes::CODE_LINENO, __LINE__);
// Atributos de banco de dados
$span->setAttribute(DbAttributes::DB_SYSTEM, 'mysql');
$span->setAttribute(DbAttributes::DB_NAME, 'my_database');
// Atributos customizados
$span->setAttribute('usuario.id', $userId);
$span->setAttribute('operacao.tipo', 'importacao');Eventos são marcos de tempo com contexto dentro de um span:
$span->addEvent('Init', Attributes::create([
'etapa' => 'inicializacao',
]));
// ... fazer algo ...
$span->addEvent('Validação Concluída', Attributes::create([
'registros_validados' => 1000,
'erros' => 5,
]));
$span->addEvent('End');<?php
use OpenTelemetry\API\Trace\StatusCode;
$span = $tracer->spanBuilder('meu-span')->startSpan();
try {
// Operação que pode falhar
$resultado = $this->processar();
// Sucesso - manter padrão (Unset) ou marcar explicitamente
$span->setStatus(StatusCode::STATUS_OK);
} catch (\Exception $e) {
// Erro
$span->setStatus(
StatusCode::STATUS_ERROR,
'Falha ao processar: ' . $e->getMessage()
);
// Registrar a exceção
$span->recordException($e, ['exception.escaped' => true]);
throw $e;
} finally {
$span->end();
}Crie app/Http/Middleware/OpenTelemetryMiddleware.php:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use OpenTelemetry\API\Globals;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\API\Trace\StatusCode;
use OpenTelemetry\SemConv\TraceAttributes;
class OpenTelemetryMiddleware
{
/**
* Handle an incoming request.
*
* Captura automaticamente:
* - Traces de requisições HTTP
* - Atributos semânticos HTTP
* - Exceções e status de resposta
*/
public function handle(Request $request, Closure $next)
{
$tracerProvider = Globals::tracerProvider();
$tracer = $tracerProvider->getTracer(
'laravel-http',
'1.0.0'
);
// Nome do span: METHOD PATH
$spanName = $request->method() . ' ' . $request->path();
$span = $tracer->spanBuilder($spanName)
->setSpanKind(SpanKind::KIND_SERVER)
->startSpan();
// Adicionar atributos semânticos HTTP
$span->setAttribute(TraceAttributes::HTTP_REQUEST_METHOD, $request->method());
$span->setAttribute(TraceAttributes::URL_FULL, $request->fullUrl());
$span->setAttribute(TraceAttributes::URL_PATH, $request->path());
$span->setAttribute(TraceAttributes::HTTP_HOST, $request->getHost());
$span->setAttribute(TraceAttributes::USER_AGENT_ORIGINAL, $request->userAgent());
$span->setAttribute(TraceAttributes::CLIENT_ADDRESS, $request->ip());
if ($request->route()) {
$span->setAttribute(TraceAttributes::HTTP_ROUTE, $request->route()->uri());
}
// Ativar contexto
$scope = $span->activate();
try {
$response = $next($request);
// Adicionar status da resposta
$span->setAttribute(TraceAttributes::HTTP_RESPONSE_STATUS_CODE, $response->status());
// Marcar status
if ($response->status() >= 500) {
$span->setStatus(StatusCode::STATUS_ERROR, 'Server Error');
} elseif ($response->status() >= 400) {
$span->setStatus(StatusCode::STATUS_ERROR, 'Client Error');
} else {
$span->setStatus(StatusCode::STATUS_OK);
}
return $response;
} catch (\Throwable $e) {
$span->recordException($e);
$span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());
throw $e;
} finally {
$scope->detach();
$span->end();
}
}
}Registrar em app/Http/Kernel.php:
protected $middleware = [
\App\Http\Middleware\OpenTelemetryMiddleware::class,
// ... outros middlewares
];Use métricas para medir valores contínuos:
<?php
use OpenTelemetry\API\Globals;
$meterProvider = Globals::meterProvider();
$meter = $meterProvider->getMeter(
'minha-aplicacao',
'1.0.0'
);
// Criar contador
$processados = $meter->createCounter(
'registros_processados',
'total',
'Total de registros processados'
);
// Incrementar
$processados->add(100, [
'tipo' => 'nfe',
'status' => 'sucesso',
]);
// UpDownCounter (pode aumentar ou diminuir)
$fila = $meter->createUpDownCounter(
'fila_trabalhos',
'jobs',
'Trabalhos enfileirados'
);
$fila->add(5); // novos jobs chegam
$fila->add(-1); // job concluído
// Forçar exportação
$meterProvider->forceFlush();composer require \
monolog/monolog \
open-telemetry/opentelemetry-logger-monolog<?php
use OpenTelemetry\API\Globals;
use OpenTelemetry\Contrib\Logs\Monolog\Handler as OTelHandler;
use Psr\Log\LogLevel;
use Monolog\Logger;
$loggerProvider = Globals::loggerProvider();
$handler = new OTelHandler(
$loggerProvider,
LogLevel::INFO
);
$logger = new Logger('minha-aplicacao', [$handler]);
// Usar normalmente
$logger->info('Aplicação iniciada');
$logger->error('Erro ao processar', [
'usuario_id' => 123,
'operacao' => 'importacao',
]);# 1. Fazer requisição
curl http://localhost:8000/api/test
# 2. Verificar logs
tail -f storage/logs/laravel.log
# 3. Acessar painel (URL fornecida pela infraestrutura)
# Verificar se aparecem:
# - Traces com spans
# - Métricas
# - Logs correlacionados com trace_idErro: Classes não encontradas
composer dump-autoloadErro: HTTP client não configurado
composer require guzzlehttp/guzzleDados não chegam ao coletor
- Verificar
OTEL_EXPORTER_OTLP_ENDPOINT - Verificar conectividade de rede
- Verificar logs de erro do PHP
- Instalar dependências via Composer
- Criar
bootstrap/instrumentation.php - Adicionar require em
public/index.php - Criar middleware OpenTelemetry
- Registrar middleware em
app/Http/Kernel.php - Instalar Monolog handler (opcional)
- Configurar variáveis de ambiente
- Testar requisição HTTP
- Verificar spans no painel SigNoz
- Verificar métricas sendo exportadas
- Verificar logs com trace_id