Śledzenie end-to-end w fintechu: od frontendu po Collector OpenTelemetry
W rozproszonych systemach fintechowych logi bez traceId nie pozwalają powiązać błędu 500 z konkretną czynnością użytkownika. OpenTelemetry zapewnia śledzenie end-to-end — od jednostronicowej aplikacji (SPA) po serwisy backendowe — dzięki Collectorowi. Implementacja w TypeScriptie z CompositeLogger i nadpisaniem fetch zachowuje hierarchię spanów dla kluczowych procesów biznesowych, np. przelewów pieniężnych.
Kluczowe pojęcia OpenTelemetry dla frontendu
OpenTelemetry standaryzuje zbieranie danych obserwowalności: śladów (traces), zakresów (spans), zdarzeń (events) i atrybutów (attributes).
- Trace — struktura nadrzędna z unikalnym traceId, służąca do grupowania zakresów.
- Span — przedział czasowy z określonym początkiem i końcem; obsługuje zagnieżdżanie.
- Event — zdarzenie z sygnaturą czasową, atrybutami i odniesieniem do konkretnego spana.
- Attribute — para klucz-wartość umożliwiająca filtrowanie i wyszukiwanie.
Te podstawowe elementy pozwalają zbudować pełną hierarchię — od kliknięcia użytkownika po integracje backendowe.
Architektura systemu obserwowalności
Frontend przesyła ślady przez mikrousługę Audit do OpenTelemetry Collector. Backendy (produktowe i platformowe) agregują dane w Jaegerze (śledzenie), Prometheusie (metryki) i Kibanie (logi). Grafana zapewnia ich wizualizację.
Zalety tej architektury:
- Centralne zbieranie danych bez bezpośredniego dostępu frontendu do infrastruktury platformowej.
- Stopniowe wdrażanie — po jednej mikrousłudze.
- Obfuskacja danych wrażliwych jest obowiązkowa.
Implementacja na froncie: interfejs Logger
Podstawową abstrakcją jest interfejs Logger, zapewniający spójne logowanie we wszystkich warstwach aplikacji.
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 implementuje wzorzec Kompozytu, przekazując zdarzenia do wielu loggerów jednocześnie (OpenTelemetry, Sentry, Audit).
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);
}
});
}
}
Integracja z Reactem przez kontekst
Komponenty LoggerProvider i hook useLogger zapewniają dostęp do loggera w całym drzewie komponentów.
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("Logger nie został znaleziony w kontekście");
}
return logger;
};
OtlpLogger: nadpisywanie fetch i StackContextManager
OtlpLogger inicjuje WebTracerProvider oraz nadpisuje metodę fetch, aby zachować kontekst rodzica (parent span). StackContextManager rozwiązuje problem asynchronicznych wywołań w przeglądarce — automatycznie przypisuje nowe spany do odpowiedniego procesu biznesowego, bez konieczności ręcznego przekazywania kontekstu.
Przykład procesu „przelew pieniężny”:
startBusinessProcess('money-transfer').addEvent('form-step-1', {amount: 1000}).- Wywołanie
fetchdo backendu w ramach bieżącego spana. logErrorprzy awarii integracji.
Korzyści z wdrożenia
- Skrócenie średniego czasu naprawy (MTTR) nawet o kilkadziesiąt razy dzięki filtrowaniu po traceId.
- Elastyczność: nowe loggery można dodać przez npm bez modyfikacji kodu produktu.
- Dane strukturalne umożliwiające korelację akcji frontendu i backendu.
Wizualizacja w Jaegerze pokazuje pełną ścieżkę: kliknięcie → żądanie API → Kafka → backend platformowy.
Na co zwrócić uwagę
- Śledzenie end-to-end łączy logi frontendu i backendu poprzez wspólny traceId.
- CompositeLogger ukrywa złożoność przed deweloperami i wspiera wiele backendów logowania.
- Nadpisywanie fetch zapewnia prawidłową hierarchię spanów w wywołaniach asynchronicznych.
- StackContextManager minimalizuje boilerplate przy śledzeniu po stronie klienta.
- Obfuskacja danych jest wymagana zgodnie z przepisami regulacyjnymi w sektorze fintech.
— Editorial Team
Brak komentarzy.