PatternsObservability
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!