Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save sneycampos/a06949c69ae219f9bf8e98286d692dd6 to your computer and use it in GitHub Desktop.

Select an option

Save sneycampos/a06949c69ae219f9bf8e98286d692dd6 to your computer and use it in GitHub Desktop.
Instrumentação de aplicações legadas PHP7+

Guia de Instrumentação Manual OpenTelemetry - PHP

Última atualização: 26/11/2025
Documentação oficial: https://opentelemetry.io/docs/languages/php/instrumentation/
Versão mínima suportada: PHP 7.4+


Objetivo

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.


Passo 1: Instalar Dependências

Para Aplicações (recomendado)

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

O que cada pacote faz:

  • open-telemetry/api - Interface da API OpenTelemetry
  • open-telemetry/sdk - SDK completo (necessário apenas em aplicações)
  • open-telemetry/exporter-otlp - Exportador OTLP para enviar dados
  • open-telemetry/sem-conv - Convenções semânticas oficiais
  • guzzlehttp/guzzle - HTTP client para transporte PSR-18

Para Bibliotecas (library instrumentation)

composer require \
  open-telemetry/api \
  open-telemetry/sem-conv

Passo 2: Criar Arquivo de Inicialização do SDK

Apenas 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();

Passo 3: Carregar Inicialização no Bootstrap

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);

Passo 4: Adquirir um Tracer e Criar Spans

Acquisition Pattern (padrão recomendado)

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();
        }
    }
}

Spans Aninhados

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();
    }
}

Passo 5: Adicionar Atributos e Eventos aos Spans

Atributos Semânticos

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');

Adicionar Eventos

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');

Passo 6: Registrar Status e Exceções

Definir Status do Span

<?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();
}

Passo 7: Middleware HTTP para Rastreamento Automático (Recomendado)

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
];

Passo 8: Métricas (Sincronizadas)

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();

Passo 9: Integração com Monolog (Logs)

Instalação

composer require \
  monolog/monolog \
  open-telemetry/opentelemetry-logger-monolog

Configuração

<?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',
]);

Passo 10: Validação e Testes

Testar Instrumentação

# 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_id

Troubleshooting

Erro: Classes não encontradas

composer dump-autoload

Erro: HTTP client não configurado

composer require guzzlehttp/guzzle

Dados não chegam ao coletor

  • Verificar OTEL_EXPORTER_OTLP_ENDPOINT
  • Verificar conectividade de rede
  • Verificar logs de erro do PHP

Checklist de Implementação

  • 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

Documentação Oficial

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment