Ir al contenido

Telemetría: puente de OpenTelemetry y respaldo no-op

El módulo Telemetry es el puente opcional de OpenTelemetry del motor. Cuando el SDK de OpenTelemetry está instalado, emite spans y métricas con atributos saneados. Si no está instalado, un conjunto completo de objetos tracer y meter no-op mantiene válidas y sin costo las llamadas de instrumentación. Por eso, siempre es seguro dejar la instrumentación en la ruta del código.

Ventana de terminal
composer require nextpdf/core:^3

El objetivo de diseño es que la observabilidad tenga costo cero cuando OTel está ausente. Las rutas críticas del motor llaman a un tracer y a un meter de forma incondicional. Que esas llamadas hagan algo depende del entorno de ejecución, no de un condicional en cada punto de llamada.

OpenTelemetryInterceptor es el puente. isAvailable() indica si el SDK de OTel está presente. startSpan(string $name, array $attributes = []) / endSpan(?object $span) delimitan una operación trazada, y recordMetric() registra un valor de contador o de medidor. Cuando OTel no está presente, el interceptor indica que no está disponible y las llamadas no tienen efecto. TelemetryBridge conecta el interceptor con los puntos de observación del motor.

AttributeSanitizer es la capa de seguridad. sanitize(array $attributes) depura el mapa de atributos antes de que salga del proceso. Los atributos de telemetría son un canal habitual para PII accidental, por lo que el saneamiento forma parte del contrato y no es un añadido. Está disponible desde @since 2.3.0, al igual que el interceptor y el puente.

NullTracer, NullSpanBuilder, NullSpan, NullMeter, NullCounter y NullHistogram son el respaldo no-op. Implementan la misma superficie que expone el SDK de OTel: spanBuilder(), setAttribute() (encadenable), startSpan(), end(), createHistogram(), createUpDownCounter(), add(), record(), y no hacen nada. Como el respaldo es completo, el código instrumentado no se ramifica según la disponibilidad; llama al tracer y el no-op absorbe la llamada.

ClaseMiembros claveFunción
OpenTelemetryInterceptorisAvailable(), startSpan(), endSpan(), recordMetric()Puente de span/metric de OTel (@since 2.3.0)
TelemetryBridgecableado del motorConecta el interceptor con los puntos de observación (@since 2.3.0)
AttributeSanitizersanitize(array $attributes): arraySaneamiento de atributos protegido frente a PII (@since 2.3.0)
NullTracerspanBuilder(string $name): NullSpanBuilderTracer no-op
NullSpanBuildersetAttribute(), startSpan(): NullSpanConstructor de span no-op (encadenable)
NullSpanend()Span no-op
NullMetercreateHistogram(), createUpDownCounter()Meter no-op
NullCounter / NullHistogramadd(), record()Instrumentos no-op

Ejecutar composer docs:generate-api-php -- --module=Telemetry para obtener la tabla PHPDoc completa.

Fuente: 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']);

Cuando el SDK de OTel no está presente, cada llamada anterior es un no-op: el código no cambia y el costo es cero.

Envolver un renderizado con atributos saneados para que los metadatos suministrados por el llamador no puedan filtrarse a un 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);
}
}
}
  • El respaldo no-op es completo por diseño. No proteger la instrumentación con isAvailable() «para ahorrar trabajo». El no-op ya no tiene costo, y esa protección añade una ramificación que el diseño busca eliminar.
  • Pasar siempre los atributos suministrados por el llamador por AttributeSanitizer antes de crear un span o registrar una métrica. Los atributos de telemetría son un canal de PII accidental.
  • endSpan(null) es válido: un span nulo es el caso no-op. Emparejar cada startSpan() con un endSpan() en un finally.
  • NullSpanBuilder::setAttribute() devuelve static para encadenar; la cadena de llamadas es inerte bajo el no-op, lo cual es intencionado, no un error.
  • El perfil de reproducibilidad es structural: los spans llevan marcas de tiempo e identificadores de traza, por lo que dos ejecuciones difieren en esos campos.

Cuando OTel no está presente, el costo es una llamada a un método de un no-op: prácticamente nulo. Cuando está presente, el costo es el del SDK de OTel, no el de este módulo; el puente añade el saneamiento de atributos, que es lineal respecto al número de atributos. El performance_budget de 1500 ms de reloj / 64 MB de pico es la referencia del motor, no un SLA de telemetría.

La telemetría es una superficie de salida de datos. AttributeSanitizer es el control que mantiene los secretos y la PII fuera de los spans y las métricas. Considerar obligatorio el saneamiento de cualquier atributo influido por el llamador; esa es la obligación de telemetría segura del proyecto. El exportador de OTel envía datos a un backend externo. La frontera con ese backend es un límite de confianza. Configurar el endpoint y las credenciales desde un gestor de secretos, no desde una configuración versionada. Debe asumirse que los datos de spans y métricas llegan a un sumidero de registros. Depurar en consecuencia. Consultar el modelo de amenazas del motor en /modules/core/security/.

Este módulo no declara ningún requisito normativo de la especificación PDF. Actúa como puente hacia el modelo de datos de OpenTelemetry, que es una especificación de observabilidad externa, no una cláusula de PDF. El respaldo no-op refleja la superficie de la API de OTel para que el código instrumentado sea portable; eso es una propiedad de compatibilidad de API, no una declaración de conformidad con PDF. La conformidad del motor se valida mediante el oráculo y las suites golden descritas en /modules/core/conformance/.