Front-end Engineering Lab

Distributed Tracing (OpenTelemetry)

Trace requests across frontend, backend, and services

Distributed tracing tracks requests from frontend through backend services. See exactly where time is spent in complex systems.

OpenTelemetry Setup

npm install @opentelemetry/api @opentelemetry/sdk-trace-web @opentelemetry/instrumentation-fetch
// utils/telemetry.ts
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
import { ConsoleSpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';

const provider = new WebTracerProvider();

provider.addSpanProcessor(
  new SimpleSpanProcessor(new ConsoleSpanExporter())
);

provider.register();

registerInstrumentations({
  instrumentations: [
    new FetchInstrumentation({
      propagateTraceHeaderCorsUrls: [/^https:\/\/api\.example\.com\/.*/],
    }),
  ],
});

export const tracer = provider.getTracer('frontend-app');

Creating Spans

import { tracer } from '@/utils/telemetry';

export async function fetchUserData(userId: string) {
  const span = tracer.startSpan('fetchUserData');
  
  span.setAttribute('user.id', userId);
  
  try {
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();
    
    span.setStatus({ code: 0 }); // Success
    return data;
  } catch (error) {
    span.setStatus({ code: 2, message: error.message }); // Error
    throw error;
  } finally {
    span.end();
  }
}

Automatic Fetch Instrumentation

// Auto-traces all fetch calls
new FetchInstrumentation({
  applyCustomAttributesOnSpan: (span, request, result) => {
    span.setAttribute('http.url', request.url);
    span.setAttribute('http.method', request.method);
    if (result instanceof Response) {
      span.setAttribute('http.status_code', result.status);
    }
  },
});

React Component Tracing

export function TracedComponent({ userId }: Props) {
  const [data, setData] = useState(null);

  useEffect(() => {
    const span = tracer.startSpan('UserProfile.mount');
    
    span.setAttribute('user.id', userId);
    
    fetchUserData(userId)
      .then((result) => {
        setData(result);
        span.end();
      })
      .catch((error) => {
        span.recordException(error);
        span.end();
      });
  }, [userId]);

  return <div>{data?.name}</div>;
}

Best practices: Always end spans, add context attributes, use consistent naming, propagate trace IDs to backend!

On this page