Back to Home

OpenTelemetry Tracing in Fintech from the Frontend

The article describes the implementation of OpenTelemetry for end-to-end tracing in a fintech system. CompositeLogger implementation in TypeScript with fetch patching and React integration. Architecture with Collector, Jaeger and Prometheus accelerates incident analysis.

OpenTelemetry Tracing: Fintech Team Experience
Advertisement 728x90

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.

Google AdInline article slot

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.

Google AdInline article slot
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.

Google AdInline article slot

Example: Money transfer flow:

  • startBusinessProcess('money-transfer')
  • addEvent('form-step-1', {amount: 1000})
  • fetch to backend—automatically inherits current span context
  • logError on 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 traceId as the single source of truth.
  • CompositeLogger shields developers from complexity while supporting multi-destination telemetry.
  • Patched fetch ensures accurate span hierarchy—even across asynchronous network calls.
  • StackContextManager eliminates boilerplate for client-side tracing in modern JavaScript frameworks.
  • Data obfuscation is non-negotiable for regulatory compliance in fintech.

— Editorial Team

Advertisement 728x90

Read Next