Trazado de extremo a extremo en fintech: desde el frontend hasta el colector de OpenTelemetry
En sistemas fintech distribuidos, los registros sin un traceId no permiten vincular un error 500 con la acción del usuario. OpenTelemetry habilita el trazado de extremo a extremo —desde el frontend de una SPA hasta los servicios backend— mediante el Colector de OpenTelemetry. Nuestra implementación en TypeScript utiliza un CompositeLogger y una versión modificada de fetch para preservar la jerarquía de spans en flujos críticos para el negocio, como las transferencias de dinero.
Conceptos clave de OpenTelemetry para desarrolladores frontend
OpenTelemetry estandariza la recopilación de datos de observabilidad: trazas, spans, eventos y atributos.
- Traza — la estructura raíz, identificada por un
traceId, que agrupa spans relacionados. - Span — una operación con marca temporal de inicio y finalización; admite anidamiento para reflejar la jerarquía de llamadas.
- Evento — una ocurrencia con marca de tiempo vinculada a un span, con atributos personalizables.
- Atributo — metadatos en formato clave-valor que permiten filtrado, búsqueda y enriquecimiento contextual.
Juntos, estos elementos permiten rastrear un clic del usuario hasta las integraciones backend subyacentes.
Visión general de la arquitectura de observabilidad
El frontend envía trazas mediante un Microservicio de Auditoría al Colector de OpenTelemetry. Los servicios backend —tanto de equipos de producto como de plataforma— redirigen la telemetría a Jaeger (trazas), Prometheus (métricas) y Kibana (registros). Grafana ofrece paneles unificados.
Ventajas clave de esta arquitectura:
- Ingesta centralizada: el frontend no accede directamente a la infraestructura interna de observabilidad.
- Implementación gradual por microservicio, minimizando riesgos.
- La ofuscación de datos sensibles es obligatoria, no opcional.
Implementación frontend: la interfaz Logger
La abstracción fundamental es una interfaz Logger para garantizar un registro consistente y multiplataforma.
export interface Logger {
readonly name: string;
init?(options?: CompositeInitOptions | SentryInitOptions | AuditInitOptions): void;
logError(error: Error, context?: ErrorContextType | ErrorContextType[]): void;
logMessage?(message: string, context?: Record<string, unknown>): void;
}
CompositeLogger implementa el patrón Compuesto, reenviando eventos a múltiples registradores subyacentes (OpenTelemetry, Sentry, Auditoría).
export class CompositeLogger implements Logger {
public readonly name = "composite";
constructor(private readonly loggers: Logger[]) {}
init(options?: CompositeInitOptions): void {
this.loggers.forEach((logger) => {
logger.init?.(options?.[logger.name as keyof CompositeInitOptions]);
});
}
logError(error: Error, context?: ErrorContextType[]): void {
this.loggers.forEach((logger) => {
logger.logError(error, context);
});
}
logMessage(message: string, context?: Record<string, unknown>): void {
this.loggers.forEach((logger) => {
logger.logMessage?.(message, context);
});
}
startBusinessProcess(name: string, attributes?: Attributes): void {
this.loggers.forEach((logger) => {
if (logger.name === "otlp") {
(logger as OtlpLogger).startBusinessProcess(name, attributes);
}
});
}
addEvent(name: string, attributes?: Attributes): void {
this.loggers.forEach((logger) => {
if (logger.name === "otlp") {
(logger as OtlpLogger).addEvent(name, attributes);
}
});
}
}
Integración con React mediante Context
LoggerProvider y useLogger ofrecen acceso fluido al registrador en todo el árbol de componentes.
import React, { createContext, useContext } from "react";
import { CompositeLogger } from "./composite-logger";
const LoggerContext = createContext<CompositeLogger | null>(null);
export const LoggerProvider: React.FC<{
logger: CompositeLogger;
children: React.ReactNode;
}> = ({ logger, children }) => (
<LoggerContext.Provider value={logger}>{children}</LoggerContext.Provider>
);
export const useLogger = (): CompositeLogger => {
const logger = useContext(LoggerContext);
if (!logger) {
throw new Error("No se encontró ningún registrador en el contexto");
}
return logger;
};
OtlpLogger: modificación de fetch y uso de StackContextManager
OtlpLogger inicializa el WebTracerProvider, modifica fetch para propagar automáticamente los spans padres y aprovecha StackContextManager para resolver la pérdida de contexto asíncrono en navegadores —asegurando que los spans hijos se adjunten correctamente a los procesos comerciales sin necesidad de pasar manualmente el contexto.
Ejemplo: flujo de transferencia de dinero:
startBusinessProcess('transferencia-dinero')addEvent('paso-formulario-1', {monto: 1000})- Llamada
fetchal backend —hereda automáticamente el contexto del span actual logErrorante un fallo de integración, conservando toda la línea de traza
Beneficios de la implementación
- Reducción del MTTR en 10 veces o más, gracias al filtrado instantáneo basado en
traceIden registros frontend y backend. - Extensibilidad: nuevos registradores (por ejemplo, Datadog o Honeycomb) se integran vía npm —sin cambios en el código de la aplicación.
- Correlación estructurada: atributos consistentes permiten emparejar con precisión eventos frontend y backend.
Las visualizaciones en Jaeger muestran el recorrido completo: clic del usuario → llamada API → Kafka → backend de plataforma.
Conclusiones clave
- El trazado de extremo a extremo une los registros frontend y backend usando el
traceIdcomo única fuente de verdad. - El
CompositeLoggerprotege a los desarrolladores de la complejidad mientras soporta telemetría multi-destino. - La versión modificada de
fetchgarantiza una jerarquía precisa de spans, incluso en llamadas de red asíncronas. - El
StackContextManagerelimina el código repetitivo necesario para el trazado cliente en frameworks JavaScript modernos. - La ofuscación de datos es imprescindible para cumplir con la normativa regulatoria en el sector fintech.
— Editorial Team
Aún no hay comentarios.