Custom metrics using @vercel/otel
This guide explains how to properly implement custom metrics in Next.js applications using @vercel/otel. Before proceeding, you should be familiar with:
- OpenTelemetry (OTEL) fundamentals
- OTEL metrics
- Basic usage of
@vercel/otel
in Next.js applications
Problem
When implementing custom metrics with @vercel/otel
in Next.js Route Handlers, you might notice that metrics aren’t being properly reported to your OTEL collector. This happens because Route Handlers complete their execution before the metrics have a chance to be flushed to the collector.
Solution
To ensure all metrics are properly collected, we need to:
- Create a custom
MetricProvider
- Register it with OTEL
- Manually flush metrics at the end of Route Handler execution
Here’s the step-by-step implementation:
First, create a custom metric provider:
// meter-provider.ts
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
import { Resource } from "@opentelemetry/resources";
import { MeterProvider } from "@opentelemetry/sdk-metrics";
import { createMetricsReader } from "@saleor/apps-otel/src/metrics-reader-factory";
export const meterProvider = new MeterProvider({
readers: [
new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter(),
}),
],
// Create new resource as `@vercel/otel` creates its own under the hood and doesn't expose it
// https://github.com/vercel/otel/issues/153
resource: new Resource(),
});
Next, set up the OTEL instrumentation:
// src/instrumentation.ts
import { metrics } from "@opentelemetry/api";
import { registerOTel } from "@vercel/otel";
export const register = () => {
registerOTel();
metrics.setGlobalMeterProvider(meterProvider);
};
Finally, implement the manual flush in your Route Handlers:
// app/api/route.ts
import { headers } from "next/headers";
import { meterProvider } from "./meter-provider"; // previously created file
export async function GET(request: Request) {
after(async () => {
await meterProvider.forceFlush();
});
return new Response("Hello from Route Handler", {
status: 200,
});
}
With this setup, your custom metrics will be properly flushed and collected before the Route Handler completes its execution. This ensures no data loss and accurate metric reporting in your observability pipeline.