End-to-End Tracing in Fintech: From Frontend to OpenTelemetry Collector
In distributed fintech systems, logs without a traceId can’t link a 500 error back to the user’s action. OpenTelemetry enables end-to-end tracing—from SPA frontend through backend services—via the OpenTelemetry Collector. Our TypeScript implementation uses a CompositeLogger and patched fetch to preserve span hierarchy for business-critical flows like money transfers.
Core OpenTelemetry Concepts for Frontend Developers
OpenTelemetry standardizes observability data collection: traces, spans, events, and attributes.
- Trace — the root structure, identified by a
traceId, used to group related spans. - Span — a timed operation with start/finish timestamps; supports nesting to reflect call hierarchy.
- Event — a timestamped occurrence linked to a span, with custom attributes.
- Attribute — key-value metadata enabling filtering, search, and contextual enrichment.
Together, these primitives let you trace a user click all the way to downstream backend integrations.
Observability Architecture Overview
The frontend sends traces via an Audit Microservice to the OpenTelemetry Collector. Backend services—both product and platform teams—route telemetry to Jaeger (traces), Prometheus (metrics), and Kibana (logs). Grafana provides unified dashboards.
Key advantages of this architecture:
- Centralized ingestion—no direct frontend access to internal observability infrastructure.
- Incremental rollout per microservice, minimizing risk.
- Sensitive data obfuscation is mandatory—not optional.
Frontend Implementation: The Logger Interface
The foundational abstraction is a Logger interface for consistent, cross-platform logging.
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 implements the Composite pattern, forwarding events to multiple underlying loggers (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);
}
});
}
}
React Integration via Context
LoggerProvider and useLogger provide seamless logger access across your component tree.
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 not found in context");
}
return logger;
};
OtlpLogger: Patching fetch and Using StackContextManager
OtlpLogger initializes the WebTracerProvider, patches fetch to propagate parent spans automatically, and leverages StackContextManager to solve browser async context loss—ensuring child spans attach correctly to business processes without manual context passing.
Example: Money transfer flow:
startBusinessProcess('money-transfer')addEvent('form-step-1', {amount: 1000})fetchto backend—automatically inherits current span contextlogErroron integration failure, preserving full trace lineage
Benefits of Implementation
- MTTR reduction by 10x or more, thanks to instant
traceId-based filtering across frontend and backend logs. - Extensibility: New loggers (e.g., Datadog, Honeycomb) plug in via npm—zero app code changes required.
- Structured correlation: Consistent attributes enable precise frontend-backend event matching.
Jaeger visualizations show the complete path: user click → API call → Kafka → platform backend.
Key Takeaways
- End-to-end tracing unifies frontend and backend logs using
traceIdas the single source of truth. CompositeLoggershields developers from complexity while supporting multi-destination telemetry.- Patched
fetchensures accurate span hierarchy—even across asynchronous network calls. StackContextManagereliminates boilerplate for client-side tracing in modern JavaScript frameworks.- Data obfuscation is non-negotiable for regulatory compliance in fintech.
— Editorial Team
No comments yet.