# OpenTelemetry (/docs/opentelemetry)

OpenTelemetry in Daytona covers two independent paths. Sandbox telemetry is the logs, traces, and metrics produced inside a sandbox, which Daytona collects and can forward to your own OpenTelemetry collector. SDK tracing is the client spans that the Daytona SDKs emit in your application for each request they send to Daytona.

Every sandbox reports its own telemetry without any setup: traces of toolbox requests, the output of its processes, and resource metrics. Programs that you run in a sandbox send telemetry only when you point them at the sandbox telemetry endpoint. Daytona does not point any program at the endpoint, including instrumented services that an image starts on its own, such as Docker.

SDK tracing is off by default. When it is on, each SDK request runs in a client span in your application's trace and carries W3C trace context headers. The SDKs do not install an OpenTelemetry tracer provider or exporter; your application configures both.

| **Term**                   | **Description**                                                                                                         |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **Sandbox telemetry**      | Logs, traces, and metrics produced in a sandbox, by the sandbox itself and by programs configured to send them          |
| **Telemetry endpoint**     | The OTLP/HTTP endpoint inside each sandbox, at the address in `DAYTONA_OTEL_ENDPOINT`                                   |
| **Telemetry forwarding**   | The organization setting that forwards sandbox telemetry to your OpenTelemetry collector                                |
| **SDK tracing**            | Client spans that the Python and TypeScript SDKs emit for each request to Daytona                                       |

## Send telemetry from a program [#send-telemetry-from-a-program]

Point a program in a sandbox at the telemetry endpoint to send its logs, traces, and metrics. Every process in the sandbox sees the endpoint address in `DAYTONA_OTEL_ENDPOINT`. Daytona does not set the standard OpenTelemetry exporter variables, so set both of them when you start the program. Telemetry that the program sends is collected with the sandbox's own telemetry and forwarded to your collector when [telemetry forwarding](#forward-sandbox-telemetry) is set.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import Daytona

daytona = Daytona()
sandbox = daytona.v2.create()

response = sandbox.process.run(
    "OTEL_EXPORTER_OTLP_ENDPOINT=$DAYTONA_OTEL_ENDPOINT "
    "OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf "
    "python app.py"
)
```

| **Variable**                  | **Description**                                                                                     |
| ----------------------------- | --------------------------------------------------------------------------------------------------- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Sets the endpoint the OpenTelemetry SDK in the program exports to. Set it to `$DAYTONA_OTEL_ENDPOINT`. |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Sets the export protocol. Set it to `http/protobuf`.                                                |

<Callout type="note" title="Notes">
- The program must use an OpenTelemetry SDK or another OTLP exporter.
- The endpoint accepts only uncompressed OTLP/HTTP with protobuf bodies, so set the protocol explicitly. It rejects gRPC, JSON, and gzip.
</Callout>

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()
const sandbox = await daytona.v2.create()

const response = await sandbox.process.run(
  'OTEL_EXPORTER_OTLP_ENDPOINT=$DAYTONA_OTEL_ENDPOINT ' +
    'OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf ' +
    'node app.js',
)
```

| **Variable**                  | **Description**                                                                                     |
| ----------------------------- | --------------------------------------------------------------------------------------------------- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Sets the endpoint the OpenTelemetry SDK in the program exports to. Set it to `$DAYTONA_OTEL_ENDPOINT`. |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Sets the export protocol. Set it to `http/protobuf`.                                                |

<Callout type="note" title="Notes">
- The program must use an OpenTelemetry SDK or another OTLP exporter.
- The endpoint accepts only uncompressed OTLP/HTTP with protobuf bodies, so set the protocol explicitly. It rejects gRPC, JSON, and gzip.
</Callout>

</TabItem>
<TabItem label="CLI" icon="seti:shell">

```bash
daytona sandbox exec {sandboxId} -- bash -c \
  'OTEL_EXPORTER_OTLP_ENDPOINT=$DAYTONA_OTEL_ENDPOINT OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf python app.py'
```

| **Variable**                  | **Description**                                                                                     |
| ----------------------------- | --------------------------------------------------------------------------------------------------- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Sets the endpoint the OpenTelemetry SDK in the program exports to. Set it to `$DAYTONA_OTEL_ENDPOINT`. |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Sets the export protocol. Set it to `http/protobuf`.                                                |

<Callout type="note" title="Notes">
- The program must use an OpenTelemetry SDK or another OTLP exporter.
- The endpoint accepts only uncompressed OTLP/HTTP with protobuf bodies, so set the protocol explicitly. It rejects gRPC, JSON, and gzip.
- Wrap the command in `bash -c '...'` with single quotes, so the variable expands in the sandbox and not on your machine.
</Callout>

</TabItem>
</Tabs>

## Forward sandbox telemetry [#forward-sandbox-telemetry]

Forward the sandbox telemetry of an organization to your OpenTelemetry collector. Daytona forwards the logs, traces, and metrics of every sandbox in the organization to the OTLP/HTTP endpoint in the forwarding configuration. A configuration set before a plan change keeps forwarding until it is deleted.

### Get the configuration [#get-the-configuration]

Get the telemetry forwarding configuration. The response lists the names of the configured request headers; header values are write-only and never returned.

<Tabs syncKey="language">
<TabItem label="API" icon="seti:json">

```bash
curl 'https://api.daytona.com/orgs/ORGANIZATION_ID/otel-config' \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```

```json
{
  "endpoint": "https://otel.example.com",
  "header_names": ["Authorization"],
  "created_at": "2026-09-10T12:00:00Z",
  "updated_at": "2026-09-10T12:00:00Z"
}
```

</TabItem>
</Tabs>

### Set the configuration [#set-the-configuration]

Set telemetry forwarding. Each write replaces the whole configuration, including headers.

<Tabs syncKey="language">
<TabItem label="API" icon="seti:json">

```bash
curl 'https://api.daytona.com/orgs/ORGANIZATION_ID/otel-config' \
  --request PUT \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
  "endpoint": "https://otel.example.com",
  "headers": { "Authorization": "Bearer YOUR_COLLECTOR_TOKEN" }
}'
```

| **Field**  | **Type** | **Description**                                                                                                                                                                                                                  |
| ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `endpoint` | string | Required. Must use `https`, be at most 2048 bytes, and resolve only to public IP addresses; `localhost` and local host names are refused. The collector appends `/v1/logs`, `/v1/traces`, or `/v1/metrics` to the endpoint for each signal. |
| `headers`  | object | Holds at most 16 entries and at most 16 KiB of names and values in total. Header names are 1 to 128 bytes and unique regardless of case. Header values are 1 to 4096 bytes of visible ASCII, space, or tab. You cannot set transport headers such as `Host`, `Content-Type`, and `Content-Length`. You also cannot set `User-Agent` or any `Proxy-*` header. To keep a stored header value without resending the value, pass `********` as the value. |

<Callout type="note" title="Notes">
- The request requires the `write:organization` permission.
- The request requires the Enterprise [plan](/docs/billing#get-the-plan).
</Callout>

</TabItem>
</Tabs>

### Delete the configuration [#delete-the-configuration]

Delete the telemetry forwarding configuration. Sandbox telemetry stops being forwarded.

<Tabs syncKey="language">
<TabItem label="API" icon="seti:json">

```bash
curl 'https://api.daytona.com/orgs/ORGANIZATION_ID/otel-config' \
  --request DELETE \
  --header 'Authorization: Bearer YOUR_OIDC_TOKEN'
```

<Callout type="note" title="Notes">
- The request requires the `write:organization` permission.
</Callout>

</TabItem>
</Tabs>

## Trace SDK requests [#trace-sdk-requests]

Turn on SDK tracing to emit a client span for each request the SDK sends to Daytona. Each span is named after the HTTP method and the route template, such as `daytona.post /sandboxes`, and the SDK adds W3C trace context headers to the request. The spans go to the tracer provider that your application registers; without one, they are not exported.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```bash
pip install 'daytona[otel]==2.0.0a8' opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
```

```python
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from daytona import Daytona, DaytonaConfig

provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)

with Daytona(DaytonaConfig(otel_enabled=True)) as daytona:
    sandbox = daytona.v2.create()
```

| **Parameter**  | **Type** | **Description**                                                                                 |
| -------------- | -------- | ----------------------------------------------------------------------------------------------- |
| `otel_enabled` | bool     | Emits a client span for each SDK request. Falls back to `DAYTONA_OTEL_ENABLED` (`1` or `true`). |

<Callout type="note" title="Notes">
- The `otel` extra installs only `opentelemetry-api`. Install the OpenTelemetry SDK and an exporter for your collector.
</Callout>

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```bash
npm install @daytona/sdk@2.0.0-alpha.8 @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-proto
```

```typescript
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { Daytona } from '@daytona/sdk'

const otel = new NodeSDK({ traceExporter: new OTLPTraceExporter() })
otel.start()

const daytona = new Daytona({ otelEnabled: true })
const sandbox = await daytona.v2.create()
```

| **Parameter**  | **Type** | **Description**                                                                                                  |
| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `otelEnabled`  | boolean  | Emits a client span for each SDK request. Falls back to `DAYTONA_OTEL_ENABLED` (`1` or `true`).                  |
| `otelApi`      | object   | Passes your application's `@opentelemetry/api` module. Required in bundled browser applications; optional in Node.js. |

<Callout type="note" title="Notes">
- In Node.js, the SDK loads `@opentelemetry/api` itself. In a bundled browser application, pass `import * as otel from '@opentelemetry/api'` as `otelApi`.
</Callout>

</TabItem>
</Tabs>

Each span has the following attributes:

| **Attribute**               | **Description**                                                       |
| --------------------------- | --------------------------------------------------------------------- |
| `http.request.method`       | HTTP method of the request                                            |
| `url.template`              | Route template of the request, such as `/sandboxes/{id}`               |
| `server.address`            | Host name of the server the request goes to, such as the Daytona API or the toolbox |
| `daytona.sandbox_id`        | ID of the sandbox the request targets, when the request has one       |
| `http.response.status_code` | HTTP status code of the response. A status of 400 or higher marks the span as an error. |

## Permissions [#permissions]

Telemetry forwarding is configured per organization and is governed by organization permissions, which you can grant through an [organization role](/docs/organizations#roles). Sending telemetry from a program and SDK tracing require no permissions beyond those of the requests they run. The [audit logs](/docs/audit-logs) record changes to the forwarding configuration.

| **Permission**           | **Grants**                                      |
| ------------------------ | ----------------------------------------------- |
| **`read:organization`**  | Get the telemetry forwarding configuration      |
| **`write:organization`** | Set and delete the telemetry forwarding configuration |
