> ## Documentation Index
> Fetch the complete documentation index at: https://tbd-6fc993ce-hypeship-docs-ia-v2.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Export Telemetry

> Send a session's captured events to your own observability backend over OTLP

Exporting sends the events a session captures to your own OpenTelemetry backend over OTLP/HTTP, so console output, network activity, and crashes from a browser session land next to the rest of your application's logs. Export runs alongside [streaming](/browsers/telemetry/streaming) and Kernel's 30-day [retention](/browsers/telemetry/overview#retention); it doesn't replace them.

<Info>
  Events are exported as OTLP **logs**. Kernel appends `/v1/logs` to your endpoint and doesn't send traces or metrics, so set the destination up as a log source in your backend.
</Info>

## How export works

You create a **destination** - an OTLP/HTTP endpoint plus the headers your backend expects - and select it when you create a browser. Destinations belong to your organization, so a browser in any project can export to them.

Kernel's relay forwards the session's events to the destination and attaches your headers at export time. Headers are encrypted at rest and never reach the browser VM, so a session can export to your backend without ever holding your ingestion key.

## Create a destination

Pass the base endpoint of your collector and the headers it expects. This example exports to Honeycomb:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  import Kernel from '@onkernel/sdk';

  const kernel = new Kernel();

  const destination = await kernel.telemetry.destinations.create({
    name: 'honeycomb-prod',
    endpoint: 'https://api.honeycomb.io',
    description: 'Production browser telemetry',
    headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY! },
  });
  ```

  ```python Python theme={null}
  import os

  from kernel import Kernel

  kernel = Kernel()

  destination = kernel.telemetry.destinations.create(
      name="honeycomb-prod",
      endpoint="https://api.honeycomb.io",
      description="Production browser telemetry",
      headers={"x-honeycomb-team": os.environ["HONEYCOMB_API_KEY"]},
  )
  ```

  ```bash CLI theme={null}
  kernel telemetry destinations create \
    --name honeycomb-prod \
    --endpoint https://api.honeycomb.io \
    --description "Production browser telemetry" \
    --header "x-honeycomb-team=$HONEYCOMB_API_KEY"
  ```
</CodeGroup>

You can also manage destinations in the dashboard under **Telemetry Destinations**.

Creating, updating, and deleting a destination requires organization-scoped authentication, such as an organization-wide API key or the dashboard; a project-scoped API key gets a `403`. Project-scoped keys can still list and retrieve destinations, and select one when creating a browser.

Header values are write-only. API, SDK, and CLI responses show each header name with an empty value, and the dashboard shows only header names: to change a stored value there, enter a replacement, or leave the field blank to keep the current value.

An organization can have up to 50 destinations. Names must match `^[a-zA-Z0-9._-]{1,255}$`, must be unique within the organization, and can't look like a Kernel ID. A destination can have up to 32 headers, with names and values totaling at most 8,192 bytes.

### Endpoint rules

The endpoint is your collector's base URL, without an OTLP signal path. Kernel appends the signal path itself, so pass `https://api.honeycomb.io`, not `https://api.honeycomb.io/v1/logs`. An endpoint that ends in `/v1/logs`, `/v1/traces`, or `/v1/metrics` is rejected. For a self-hosted OpenTelemetry Collector, use the address of its OTLP/HTTP receiver, such as `https://collector.example.com:4318`.

The endpoint must also:

* Use `http` or `https`
* Resolve to a public IP address
* Have no query string or fragment
* Be 2,048 characters or fewer

Kernel doesn't follow redirects. If your endpoint responds with a redirect, the export fails and the destination reports `destination returned a redirect`, so use the final URL.

## Export a session's telemetry

Set `telemetry.export.otlp.destination` when you create the browser, referencing the destination by `name` or `id`:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const browser = await kernel.browsers.create({
    telemetry: {
      browser: {
        console: { enabled: true },
        network: { enabled: true },
      },
      export: {
        otlp: { destination: { name: 'honeycomb-prod' } },
      },
    },
  });
  ```

  ```python Python theme={null}
  browser = kernel.browsers.create(
      telemetry={
          "browser": {
              "console": {"enabled": True},
              "network": {"enabled": True},
          },
          "export": {
              "otlp": {"destination": {"name": "honeycomb-prod"}},
          },
      },
  )
  ```

  ```bash CLI theme={null}
  kernel browsers create --telemetry=console,network --telemetry-export-otlp honeycomb-prod
  ```
</CodeGroup>

Export sends what the session captures, so capture must be on in the same request. Pass `enabled: true` or a category list alongside the destination; otherwise the request fails with `telemetry.export.otlp.destination requires telemetry capture to be enabled`. On `kernel browsers create`, the CLI implies `--telemetry=all` when you pass a destination with `--telemetry-export-otlp` without `--telemetry`.

Provide exactly one of `id` or `name`. Setting a destination turns export on, so you don't need to pass `enabled: true` under `otlp`, and combining a destination with `enabled: false` is rejected.

Every captured category is exported except `screenshot` and `monitor`, whose events stay available through [streaming](/browsers/telemetry/streaming).

<Warning>
  Export is bound at session creation:

  * **Destination is fixed:** `browsers update` ignores `telemetry.export`, so a session keeps the destination it was created with. To export somewhere else, create a new session.
  * **Capture controls export:** turning capture off on update also stops export, and turning capture back on resumes export to the same destination.
  * **No browser pools:** [browser pools](/browsers/pools) don't support export. Pool create, update, and acquire reject `telemetry.export` with a `400`.
</Warning>

## What arrives in your backend

Each event becomes one OTLP log record:

* The record's event name is the event type, such as `network_response` or `console_error`. It's also set as the `kernel.event.type` attribute, because some backends drop the event name.
* `kernel.event.category` holds the category, and `kernel.event.seq` holds the same sequence number the [stream](/browsers/telemetry/streaming) uses.
* The body is the event's payload.
* Network events also carry `http.request.method`, `url.full`, and `http.response.status_code` when the payload has them, and console events carry `kernel.console.level`, so you can filter on them in backends that don't index a structured body.
* The resource's `service.name` is `kernel-browser`.

See [Categories](/browsers/telemetry/categories) for every event type and what it captures.

## Confirm export is working

To confirm a browser's events reach your backend, generate an event you can recognize and search for it. Exported records don't carry the browser's session ID, so this example has the page log a marker that includes it:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  await kernel.browsers.playwright.execute(browser.session_id, {
    code: `await page.evaluate(() => console.log('kernel-export-check ${browser.session_id}'));`,
  });
  ```

  ```python Python theme={null}
  kernel.browsers.playwright.execute(
      browser.session_id,
      code=f"await page.evaluate(() => console.log('kernel-export-check {browser.session_id}'));",
  )
  ```

  ```bash CLI theme={null}
  kernel browsers playwright execute <session-id> \
    "await page.evaluate(() => console.log('kernel-export-check <session-id>'))"
  ```
</CodeGroup>

Search your backend for `kernel-export-check` followed by the session ID. It arrives as a `console_log` record whose body's `text` field holds the marker. This relies on the browser capturing `console`; if it doesn't, generate an event from a category it does capture.

If the record doesn't arrive, check the browser and the destination. Browser responses report the session's export state under `telemetry.export.otlp`. When the session is exporting, `enabled` is `true` and `destination` is the ID of the destination it's bound to:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const session = await kernel.browsers.retrieve(browser.session_id);

  console.log(session.telemetry?.export?.otlp);
  ```

  ```python Python theme={null}
  session = kernel.browsers.retrieve(browser.session_id)

  print(session.telemetry.export.otlp)
  ```

  ```bash CLI theme={null}
  kernel browsers get <session-id> -o json
  ```
</CodeGroup>

Delivery health lives on the destination. It covers every session exporting to that destination, so it can show that deliveries are succeeding but not that a particular browser's events arrived:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const health = await kernel.telemetry.destinations.retrieve('honeycomb-prod');

  console.log(health.last_export_at, health.consecutive_failures, health.last_error);
  ```

  ```python Python theme={null}
  health = kernel.telemetry.destinations.retrieve("honeycomb-prod")

  print(health.last_export_at, health.consecutive_failures, health.last_error)
  ```

  ```bash CLI theme={null}
  kernel telemetry destinations get honeycomb-prod
  ```
</CodeGroup>

* `last_export_at` is the time of the last successful delivery. While deliveries keep succeeding, it's refreshed about once a minute rather than on every export.
* `consecutive_failures` is `0` when the most recent recorded delivery succeeded. Read this field to tell whether the destination is failing right now.
* `last_error` and `last_error_at` describe the most recent failure and are kept after the destination recovers, so their presence alone doesn't mean exports are failing.

`last_error` is a fixed message for the class of failure, such as `destination returned a 4xx response` or `destination TLS handshake failed`. Response bodies, endpoint URLs, and credentials are never returned.

Health only reflects deliveries Kernel attempted. If `telemetry.export.otlp` shows the browser is exporting, `consecutive_failures` is `0`, and your marker still hasn't arrived after a minute, [contact support](mailto:support@kernel.sh) with the session ID.

## Rotate credentials

Update the destination's headers. Updates merge header by header rather than replacing the whole set, and sessions that are already exporting use the new values on their next export request, so you can rotate a key without restarting sessions:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  await kernel.telemetry.destinations.update('honeycomb-prod', {
    headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY_NEXT! },
  });
  ```

  ```python Python theme={null}
  kernel.telemetry.destinations.update(
      "honeycomb-prod",
      headers={"x-honeycomb-team": os.environ["HONEYCOMB_API_KEY_NEXT"]},
  )
  ```

  ```bash CLI theme={null}
  kernel telemetry destinations update honeycomb-prod \
    --header "x-honeycomb-team=$HONEYCOMB_API_KEY_NEXT"
  ```
</CodeGroup>

Header names are matched case-insensitively, so `authorization` replaces a stored `Authorization` instead of adding a second header. Set a header to `null` (`None` in Python, or `--remove-header` in the CLI) to delete it. Headers you don't name keep their current values.

## Managed auth connections

[Managed auth](/auth/overview) logins run in a browser too, so they can export the same way. Set `browser.telemetry` with an `export` block when you create or update a connection, or on a single login, using the same shape as browser create:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const auth = await kernel.auth.connections.create({
    domain: 'example.com',
    profile_name: 'my-profile',
    browser: {
      telemetry: {
        browser: {
          console: { enabled: true },
          network: { enabled: true },
        },
        export: {
          otlp: { destination: { name: 'honeycomb-prod' } },
        },
      },
    },
  });
  ```

  ```python Python theme={null}
  auth = kernel.auth.connections.create(
      domain="example.com",
      profile_name="my-profile",
      browser={
          "telemetry": {
              "browser": {
                  "console": {"enabled": True},
                  "network": {"enabled": True},
              },
              "export": {
                  "otlp": {"destination": {"name": "honeycomb-prod"}},
              },
          },
      },
  )
  ```

  ```bash CLI theme={null}
  kernel auth connections create \
    --domain example.com \
    --profile-name my-profile \
    --telemetry=console,network \
    --telemetry-export-otlp honeycomb-prod
  ```
</CodeGroup>

A `browser.telemetry` block on update or login replaces the connection's stored telemetry config instead of merging into it, so include the categories you want captured along with the export block. On login, the block applies to that login only, and the connection keeps its stored config:

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const login = await kernel.auth.connections.login(auth.id, {
    browser: {
      telemetry: {
        browser: {
          console: { enabled: true },
          network: { enabled: true },
        },
        export: {
          otlp: { destination: { name: 'honeycomb-prod' } },
        },
      },
    },
  });
  ```

  ```python Python theme={null}
  login = kernel.auth.connections.login(
      auth.id,
      browser={
          "telemetry": {
              "browser": {
                  "console": {"enabled": True},
                  "network": {"enabled": True},
              },
              "export": {
                  "otlp": {"destination": {"name": "honeycomb-prod"}},
              },
          },
      },
  )
  ```

  ```bash CLI theme={null}
  kernel auth connections login <connection-id> \
    --telemetry=console,network --telemetry-export-otlp honeycomb-prod
  ```
</CodeGroup>

In the CLI:

* `kernel auth connections update` takes the same flags as `login`.
* On connection create, passing a destination implies `--telemetry=all` if you omit `--telemetry`.
* When selecting a destination on update or login, you must pass `--telemetry` in the same command, even if the connection already has capture enabled. This keeps the CLI from replacing your category selection with the default set.

Kernel stores the resolved destination ID on the connection, so renaming the destination later doesn't change where the connection's logins export.

## Delete a destination

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  await kernel.telemetry.destinations.delete('honeycomb-prod');
  ```

  ```python Python theme={null}
  kernel.telemetry.destinations.delete("honeycomb-prod")
  ```

  ```bash CLI theme={null}
  kernel telemetry destinations delete honeycomb-prod
  ```
</CodeGroup>

Deleting a destination fails with a `409` while it's still in use:

* A browser session created with it hasn't ended. Wait for those sessions to end or delete them, then retry.
* A managed auth connection selects it. Point the connection at another destination or turn off its export first.
* A managed auth login using it is still in progress. Wait for the login to finish.

## What's next

* [Telemetry Overview](/browsers/telemetry/overview) - enable capture and choose what a session records.
* [Categories](/browsers/telemetry/categories) - every category, what it captures, and its cost characteristics.
* [Stream Telemetry](/browsers/telemetry/streaming) - consume the live stream instead of, or alongside, exporting.


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