Pular para o conteúdo

Telemetry: ponte com OpenTelemetry e fallback no-op

O módulo Telemetry é a ponte opcional entre o engine e o software development kit (SDK) do OpenTelemetry (OTel). Quando o SDK está instalado, ele emite spans e métricas com atributos sanitizados. Quando o SDK está ausente, um conjunto completo de objetos no-op de tracer e meter mantém as chamadas de instrumentação válidas e praticamente sem custo. Você pode manter a instrumentação no caminho do código com segurança.

Terminal window
composer require nextpdf/core:^3

O objetivo do projeto é oferecer observabilidade com custo zero quando o SDK está ausente. Os caminhos críticos do engine chamam um tracer e um meter sem verificação prévia. Se essas chamadas executam trabalho depende do runtime, não de um condicional em cada ponto de chamada.

OpenTelemetryInterceptor é a ponte. isAvailable() informa se o SDK do OTel está presente. startSpan(string $name, array $attributes = []) / endSpan(?object $span) delimitam uma operação rastreada, e recordMetric() registra um valor de contador ou gauge. Quando o OTel está ausente, o interceptor informa indisponibilidade, e as chamadas ficam inertes. TelemetryBridge conecta o interceptor aos pontos de observação do engine.

AttributeSanitizer é a camada de segurança. sanitize(array $attributes) limpa o mapa de atributos antes que ele saia do processo. Atributos de telemetria são um canal acidental comum para informações de identificação pessoal (PII), portanto a sanitização faz parte do contrato; não é um complemento. O sanitizador, o interceptor e a ponte são @since 2.3.0.

NullTracer, NullSpanBuilder, NullSpan, NullMeter, NullCounter e NullHistogram são o fallback no-op. Eles correspondem aos formatos de chamada expostos pelo SDK do OTel: spanBuilder(), setAttribute() (encadeável), startSpan(), end(), createHistogram(), createUpDownCounter(), add() e record(). Eles não fazem nada. Como o fallback é completo, o código instrumentado não ramifica conforme a disponibilidade; ele chama o tracer, e o no-op absorve a chamada.

ClasseMembros principaisFunção
OpenTelemetryInterceptorisAvailable(), startSpan(), endSpan(), recordMetric()Ponte de spans e métricas do OTel (@since 2.3.0)
TelemetryBridgeintegração com o engineConecta o interceptor aos pontos de observação do engine (@since 2.3.0)
AttributeSanitizersanitize(array $attributes): arrayLimpa atributos para proteger PII (@since 2.3.0)
NullTracerspanBuilder(string $name): NullSpanBuilderTracer no-op
NullSpanBuildersetAttribute(), startSpan(): NullSpanSpan builder no-op (encadeável)
NullSpanend()Span no-op
NullMetercreateHistogram(), createUpDownCounter()Meter no-op
NullCounter / NullHistogramadd(), record()Instrumentos no-op

Execute composer docs:generate-api-php -- --module=Telemetry para gerar a tabela completa de PHPDoc.

Fonte: examples/33-opentelemetry-observability.php.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Telemetry\OpenTelemetryInterceptor;
$otel = new OpenTelemetryInterceptor(/* optional OTel tracer/meter */);
$span = $otel->startSpan('pdf.render', ['doc.pages' => 12]);
// ... render work ...
$otel->endSpan($span);
$otel->recordMetric('pdf.render.bytes', 482_113, ['profile' => 'pdfa4']);

Quando o SDK do OTel está ausente, toda chamada acima é um no-op. O código permanece idêntico, e o custo é zero.

Envolva uma operação de renderização com atributos sanitizados para impedir que metadados fornecidos pelo chamador vazem para um span.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Telemetry\AttributeSanitizer;
use NextPDF\Telemetry\OpenTelemetryInterceptor;
final readonly class InstrumentedRenderer
{
public function __construct(
private OpenTelemetryInterceptor $otel,
private AttributeSanitizer $sanitizer,
) {}
/**
* @param callable():string $render Returns the rendered PDF bytes.
* @param array<string, mixed> $attributes Caller-supplied span attributes.
*/
public function render(callable $render, array $attributes): string
{
$span = $this->otel->startSpan('pdf.render', $this->sanitizer->sanitize($attributes));
try {
return $render();
} finally {
$this->otel->endSpan($span);
}
}
}
  • O fallback no-op é completo por design. Não proteja a instrumentação com isAvailable() “para economizar trabalho”. O no-op já não tem custo, e a proteção adiciona a ramificação que este design remove.
  • Sempre passe os atributos fornecidos pelo chamador pelo AttributeSanitizer antes de anexá-los a um span ou métrica. Atributos de telemetria são um canal acidental para PII.
  • endSpan(null) é válido: um span nulo é o caso no-op. Pareie todo startSpan() com um endSpan() em um finally.
  • NullSpanBuilder::setAttribute() retorna static para encadeamento. Sob o no-op, a cadeia é inerte por design.
  • O perfil de reprodutibilidade é structural: os spans carregam timestamps e identificadores de trace, portanto duas execuções diferem nesses campos.

Quando o OTel está ausente, o custo é uma chamada de método para um no-op, praticamente sem custo. Quando o OTel está presente, o custo vem do SDK do OTel; a ponte adiciona a sanitização de atributos, que é linear em relação à quantidade de atributos. O performance_budget de 1500 ms de wall / 64 MB de pico é o orçamento de referência do engine, não um acordo de nível de serviço (SLA) para telemetria.

A telemetria é uma superfície de saída de dados. O AttributeSanitizer mantém segredos e PII fora dos spans e métricas. Trate a sanitização como obrigatória para qualquer atributo influenciado pelo chamador; essa é a obrigação de telemetria segura do projeto. O exporter do OTel envia dados para um backend externo, e esse backend é uma fronteira de confiança. Configure o endpoint e as credenciais a partir de um gerenciador de segredos, não de uma configuração versionada. Suponha que dados de span e métrica cheguem a um destino de log e limpe-os adequadamente. Consulte o modelo de ameaças do engine em /modules/core/security/.

Este módulo não faz nenhuma afirmação normativa sobre a especificação Portable Document Format (PDF). Ele faz a ponte com o modelo de dados do OpenTelemetry, uma especificação externa de observabilidade, não com uma cláusula do PDF. O fallback no-op espelha a superfície da application programming interface (API) do OpenTelemetry para que o código instrumentado permaneça portável. Isso é uma propriedade de compatibilidade de API, não uma declaração de conformidade com o PDF. A conformidade do engine é validada pelas suítes de oráculo e golden descritas em /modules/core/conformance/.