> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bronto.io/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenTelemetry in Vercel Functions

> Export traces from Vercel Functions to Bronto over OTLP with @vercel/otel or an agent framework such as eve, and decide when that is better than a trace drain.

Drains send what Vercel's platform records: request logs, build logs, firewall events and Vercel's own spans. Your code can also send its own telemetry straight to Bronto over OTLP. You choose what's in it (custom spans, attributes, AI calls), and there is no drain fee for it. You pay in function time instead, because the function waits for the export.

Most projects use both: [drains](/integrations/vercel-drains.md) for what only Vercel sees, and OTLP from code for the application.

## When to use this

* You want spans from your own code: database queries, calls to other services, AI model and tool calls.
* You want those spans to continue into [services outside Vercel](/integrations/vercel-tracing.md).
* Your functions produce a lot of telemetry per request, where the drain's \$0.50 per GB adds up.

It doesn't replace a log drain: CDN, firewall, static, redirect and build logs only leave Vercel through drains.

## Bronto OTLP endpoint

| Region | Endpoint (`OTEL_EXPORTER_OTLP_ENDPOINT`) |
| - | - |
| EU | `https://ingestion.eu.bronto.io` |
| US | `https://ingestion.us.bronto.io` |

Exporters append `/v1/traces`, `/v1/metrics` and `/v1/logs`. Authenticate with the header `x-bronto-api-key`. See [API Keys](/Account-Management/API-Keys.md).

## Next.js (and other frameworks) with `@vercel/otel`

1. Install the package:

   ```bash theme={"dark"}
   npm install @vercel/otel @opentelemetry/api
   ```

2. Register it in `instrumentation.ts` in the project root (or in `src/`):

   ```ts instrumentation.ts theme={"dark"}
   import { registerOTel } from "@vercel/otel";

   export function register() {
     registerOTel({
       serviceName: "my-app",
       attributes: { "service.namespace": "my-team" },
       instrumentationConfig: {
         // Services outside Vercel that should continue the trace.
         fetch: { propagateContextUrls: ["https://api.example.com"] },
       },
     });
   }
   ```

3. Set the exporter in the project's environment variables (Settings → Environment Variables):

   ```bash theme={"dark"}
   OTEL_EXPORTER_OTLP_ENDPOINT=https://ingestion.eu.bronto.io
   OTEL_EXPORTER_OTLP_HEADERS=x-bronto-api-key=<YOUR_API_KEY>
   ```

   Values are percent-decoded (W3C Baggage format), so encode a comma, semicolon or `%` inside a value. An `=` at the end of an API key can stay as it is.

4. Redeploy. Spans appear in [Explore Traces](/tracing/explore-traces.md) under the service name you set.

What you get: Next.js spans (rendering, route handlers, middleware), `fetch` client spans, and your own spans created with the OpenTelemetry API. These spans share one trace with Vercel's infrastructure spans for the same request (`Resolve Route`, `Invoke Middleware`, `Invoke Function`), which reach Bronto through a [trace drain](/integrations/vercel-drains.md). Spans from functions on the Edge runtime are not supported.

`@vercel/otel` adds resource attributes such as `vercel.project_id`, `vercel.deployment_id`, `vercel.region` and `vercel.sha`, so you can filter by deployment.

### With a trace drain on the same project

A trace drain forwards your own spans too, not only Vercel's infrastructure spans (`vercel.edge-network`, `vercel.serverless-runtime`). `@vercel/otel` (2.1 and later) is built to skip its `OTEL_EXPORTER_OTLP_*` exporter while a trace drain is configured, so that spans travel through the drain only. In a test project with both, that didn't happen: every span of the Next.js app and of an eve agent arrived twice, once through the direct export and once through the trace drain, and the duplicates were about two thirds of the trace drain's spans.

Choose one path for your own spans:

* **Through the trace drain:** remove the direct exporter (or don't set `OTEL_EXPORTER_OTLP_*`). Your spans are billed as drain volume at \$0.50 per GB.
* **Direct export:** keep `OTEL_EXPORTER_OTLP_*` and don't add a trace drain. The export costs function time instead of drain fees (see below), but Vercel's infrastructure spans then don't reach Bronto.

Agent frameworks with their own exporter, such as eve's `otelIntegration`, always export directly, so with a trace drain their spans arrive twice as well.

## Agents built with eve

[eve](https://eve.dev) has OpenTelemetry built in: model calls, tool calls and agent turns become spans that follow the [GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) (`gen_ai.provider.name`, `gen_ai.usage.input_tokens`, `gen_ai.tool.name`, …). Add an OTLP exporter in `agent/instrumentation/`:

```bash theme={"dark"}
npm install @opentelemetry/exporter-trace-otlp-proto@0.223
```

```ts agent/instrumentation/otlp.ts theme={"dark"}
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { otelIntegration } from "eve/instrumentation/otel";

// Reads OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS
export default otelIntegration({ traceExporter: new OTLPTraceExporter() });
```

The OpenTelemetry OTLP exporter reads the same `OTEL_EXPORTER_OTLP_*` variables as `@vercel/otel`, so a Next.js app and an eve agent in one project share one configuration. Version 0.223 matches the OpenTelemetry JS 2.x packages that `@vercel/otel` 2 uses.

eve starts a new trace for each agent turn. To carry the trace into the services your tools call, add the undici instrumentation, as shown in [Tracing into other services](/integrations/vercel-tracing.md#agents-built-with-eve).

## What exporting costs

The exporter sends spans over the network, and the function stays billed while it waits. In a test on Fluid Compute (2 GB, `iad1`) exporting 11 spans per request, one request at a time, the flush added about 24 ms of billed instance time to an endpoint in the same region and 73 ms to one that answered in 50 ms. Active CPU rose by about 3 ms. That is roughly $0.25 per 1M invocations for a nearby endpoint and $0.55 for a 50 ms one. With concurrent requests on Fluid Compute, the waits overlap and the cost per request is lower.

* Exporting in `waitUntil` (or `after()` in Next.js) keeps the delay away from your users, but not from the bill.
* Work in `waitUntil` shares the function's timeout. If the function times out, unsent spans are lost.
* Vercel traces the export itself: the trace drain carries a `vercel.serverless-runtime` span for every outgoing `POST …/v1/traces` call (2,325 in 20 minutes in the test project).

For comparison, a log drain at 1 KB per request costs about $0.50 per 1M requests (at $0.50 per GB). Exporting from code saves the most when each request produces a lot of data, because its cost follows round trips, not bytes.

## Example

The lab [petclinic-agent](https://github.com/bronto-community/petclinic-agent) uses both: `apps/web/instrumentation.ts` (`@vercel/otel` in Next.js) and `agent/instrumentation/otlp.ts` (eve), next to log, trace and Web Analytics drains.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.