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

# OpenTelemetry Export: Forward Agent Traces to Any Backend

> Forward Anvil traces to any OTel-compatible backend — Datadog, Grafana, Langfuse, Braintrust — using the built-in OTLP/HTTP exporter.

Anvil traces map directly to OpenTelemetry spans following the GenAI semantic conventions, so you can forward them to any OTLP-compatible backend without a separate SDK or agent. Use `otlpHttpExporter` to create an `onExport` callback and pass it to `Tracer` — every finished trace is serialised to the OTLP JSON format and POSTed to your collector in the background.

## How export works

When a `Tracer` is constructed with an `onExport` callback, that callback fires once per trace, immediately after the agent run ends and `traceHandle.end()` is called. The `otlpHttpExporter` factory returns exactly such a callback: it converts the `Trace` object into an OTLP `resourceSpans` payload and sends it via `fetch`. Export is fire-and-forget — failures are logged to `console.error` but never propagate to the request, so a collector outage cannot break your API.

## Setting up the exporter

```ts theme={null}
import { Tracer, SqliteTraceStore, otlpHttpExporter, traceToOtelSpans } from 'anvil-sdk/trace';

const store = await SqliteTraceStore.open('.anvil/traces.db');

const exporter = otlpHttpExporter({
  url: 'https://your-collector/v1/traces',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
  },
  serviceName: 'my-anvil-app',
});

const tracer = new Tracer(store, { onExport: exporter });
```

Pass `tracer` to `defineAgent` as usual. Every completed trace is now forwarded to your collector alongside being stored locally.

## OtlpExporterOptions

<ParamField body="url" type="string" required>
  The OTLP/HTTP endpoint to POST traces to. This is the `/v1/traces` path on your collector. Common values:

  | Backend         | Endpoint                                               |
  | --------------- | ------------------------------------------------------ |
  | Datadog         | `https://http-intake.logs.datadoghq.com/api/v2/otlp`   |
  | Grafana Tempo   | `https://<your-stack>.grafana.net/otlp/v1/traces`      |
  | Langfuse        | `https://cloud.langfuse.com/api/public/otel/v1/traces` |
  | Braintrust      | `https://api.braintrustdata.com/otel/v1/traces`        |
  | Local collector | `http://localhost:4318/v1/traces`                      |
</ParamField>

<ParamField body="headers" type="Record<string, string>" optional>
  HTTP headers added to every export request. Use this to pass API keys, tokens, or dataset identifiers required by your backend.
</ParamField>

<ParamField body="serviceName" default="anvil" type="string">
  The `service.name` resource attribute included in every exported payload. Set this to identify your application in the backend's UI.
</ParamField>

## Converting traces manually with traceToOtelSpans

If you need to control the HTTP transport yourself, use `traceToOtelSpans` directly to obtain the OTLP payload and send it with your own client:

```ts theme={null}
import { traceToOtelSpans } from 'anvil-sdk/trace';
import type { Trace } from 'anvil-sdk/trace';

function myExporter(trace: Trace): void {
  const resourceSpans = traceToOtelSpans(trace, 'my-service');

  fetch('https://collector.example.com/v1/traces', {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ resourceSpans: [resourceSpans] }),
  }).catch(console.error);
}

const tracer = new Tracer(store, { onExport: myExporter });
```

`traceToOtelSpans(trace, serviceName?)` returns an `OtlpResourceSpans` object ready to be nested under `{ resourceSpans: [...] }` in the OTLP JSON body.

## GenAI semantic convention attributes

Anvil maps span fields to the [OpenTelemetry GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/). The following attributes are emitted on `model` spans:

| OTel attribute               | Source                               |
| ---------------------------- | ------------------------------------ |
| `gen_ai.system`              | `span.attributes.provider`           |
| `gen_ai.request.model`       | `span.attributes.model`              |
| `gen_ai.response.model`      | `span.attributes.model`              |
| `gen_ai.usage.input_tokens`  | `span.attributes.usage.inputTokens`  |
| `gen_ai.usage.output_tokens` | `span.attributes.usage.outputTokens` |
| `gen_ai.usage.cost`          | `span.attributes.costUsd`            |
| `gen_ai.operation.name`      | `span.kind`                          |

Tool spans emit `gen_ai.tool.name` from `span.attributes.name`.

## Sending to multiple backends

Compose multiple exporters inside a single `onExport` callback:

```ts theme={null}
import { otlpHttpExporter } from 'anvil-sdk/trace';

const datadogExporter = otlpHttpExporter({
  url: 'https://http-intake.logs.datadoghq.com/api/v2/otlp',
  headers: { 'DD-API-KEY': process.env.DD_API_KEY! },
  serviceName: 'anvil-prod',
});

const langfuseExporter = otlpHttpExporter({
  url: 'https://cloud.langfuse.com/api/public/otel/v1/traces',
  headers: {
    'Authorization': `Basic ${Buffer.from(`${process.env.LANGFUSE_PUBLIC_KEY}:${process.env.LANGFUSE_SECRET_KEY}`).toString('base64')}`,
  },
});

const tracer = new Tracer(store, {
  onExport: (trace) => {
    datadogExporter(trace);
    langfuseExporter(trace);
  },
});
```

<Note>
  Both exporters run concurrently and independently. A failure in one does not affect the other, and neither can throw back into the request path because export errors are caught and logged internally.
</Note>

## OtlpResourceSpans type reference

For advanced use cases, the full OTLP type shapes are exported from `anvil/trace`:

```ts theme={null}
interface OtlpResourceSpans {
  resource: { attributes: OtlpAttribute[] };
  scopeSpans: Array<{
    scope: { name: string; version: string };
    spans: OtlpSpan[];
  }>;
}

interface OtlpSpan {
  traceId: string;      // 32-char lowercase hex
  spanId: string;       // 16-char lowercase hex
  parentSpanId?: string;
  name: string;
  kind: number;         // 1 = SPAN_KIND_INTERNAL
  startTimeUnixNano: string;
  endTimeUnixNano: string;
  attributes: OtlpAttribute[];
  status: { code: number; message?: string };
}
```
