# How to Configure OpenTelemetry Exports for Goose Observability

> Configure OpenTelemetry exports for Goose observability with ease. Enable OTLP for traces metrics and logs using simple environment variables. No code changes needed with the otel feature.

- Repository: [Block Open Source/goose](https://github.com/block/goose)
- Tags: how-to-guide
- Published: 2026-04-05

---

**Goose provides built-in OpenTelemetry support that exports traces, metrics, and logs via OTLP through environment variables like `OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_TRACES_EXPORTER`, requiring no code changes when the `otel` feature is enabled.**

Goose (the `block/goose` repository) ships with native OpenTelemetry integration that enables full observability of AI agent execution through standardized telemetry protocols. You can configure OpenTelemetry exports for Goose observability entirely through environment variables, routing telemetry data to any OTLP-compatible collector such as Jaeger, Prometheus, or Grafana Tempo without modifying source code.

## Understanding Goose's Three-Layer Telemetry Architecture

Goose implements OpenTelemetry through three distinct layers in [`crates/goose/src/otel/otlp.rs`](https://github.com/block/goose/blob/main/crates/goose/src/otel/otlp.rs), each handling a specific signal type:

| Layer | Function | Purpose |
|-------|----------|---------|
| **Tracer Layer** | `create_otlp_tracing_layer()` (lines 45-73) | Captures distributed tracing spans generated by Goose agents by selecting an exporter (`Otlp`, `Console`, or `None`) and registering with the global provider. |
| **Metrics Layer** | `create_otlp_metrics_layer()` (lines 89-111) | Emits Goose-generated metrics (e.g., tool-call counts, token usage) with configurable temporalities via `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE`. |
| **Logs Layer** | `create_otlp_logs_layer()` (lines 119-141) | Sends structured logs produced by the `tracing` subscriber to an OTLP log endpoint by bridging to the OTel log exporter. |

These layers are conditionally added to Goose's global `tracing_subscriber` when the `otel` feature is enabled. The CLI ([`crates/goose-cli/src/logging.rs`](https://github.com/block/goose/blob/main/crates/goose-cli/src/logging.rs) lines 71-74) and server ([`crates/goose-server/src/logging.rs`](https://github.com/block/goose/blob/main/crates/goose-server/src/logging.rs) lines 60-61) both call `otlp::init_otlp_layers(goose::config::Config::global())` while building their respective subscribers.

## Environment Variable Configuration

Goose mirrors the official OpenTelemetry SDK specification, allowing complete configuration through environment variables. The `init_otlp_layers()` function processes these variables to determine exporter behavior.

### Core Configuration Variables

- **`OTEL_EXPORTER_OTLP_ENDPOINT`** — Base endpoint (e.g., `http://localhost:4318`) used for all signals.
- **`OTEL_EXPORTER_OTLP_{SIGNAL}_ENDPOINT`** — Override endpoint for a specific signal (`TRACES`, `METRICS`, `LOGS`).
- **`OTEL_{SIGNAL}_EXPORTER`** — Select exporter type per signal: `otlp` (default), `console` (stdout), or `none` (disable).
- **`OTEL_TRACES_SAMPLER` / `OTEL_TRACES_SAMPLER_ARG`** — Control trace sampling to reduce volume (e.g., `parentbased_traceidratio` with `0.1`).
- **`OTEL_EXPORTER_OTLP_TIMEOUT`** — HTTP timeout in milliseconds for OTLP calls.
- **`OTEL_SERVICE_NAME`** — Logical service name appearing in the collector UI.
- **`OTEL_RESOURCE_ATTRIBUTES`** — Additional key-value pairs (e.g., `deployment.environment=prod`).
- **`OTEL_SDK_DISABLED`** — Completely disables OTel when set to `true`.

### Configuration Precedence

When `init_otlp_layers` executes, it first promotes config-file values (`otel_exporter_otlp_endpoint`, `otel_exporter_otlp_timeout`) to environment variables if the corresponding env vars are missing. This makes the Goose CLI config file a source of truth while allowing environment overrides.

The function then calls the three `create_*_layer` helpers. Each helper determines the exporter type via `signal_exporter(signal)` (lines 44-71), which checks `OTEL_SDK_DISABLED`, `OTEL_{SIGNAL}_EXPORTER`, and endpoint variables before instantiating concrete SDK exporters (`opentelemetry_otlp::SpanExporter`, `MetricExporter`, `LogExporter`) or stdout alternatives.

The **resource builder** (`create_resource` lines 97-113) adds standard attributes (`service.name`, `service.version`, `service.namespace`) while respecting `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES`.

## Practical Configuration Examples

### Local Collector Setup with Docker

Start an OTLP collector and export all signals:

```bash
docker run -d -p 4318:4318 \
  -v $(pwd)/collector.yaml:/etc/otel-collector-config.yaml \
  otel/opentelemetry-collector-contrib \
  --config /etc/otel-collector-config.yaml

```

Configure Goose to export to the local collector:

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOGS_EXPORTER="otlp"
export OTEL_SERVICE_NAME="goose-cli"

goose run --recipe my-recipe.yaml

```

### Export Only Traces with Sampling

Disable metrics and logs while sampling 5% of traces:

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4318"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="none"
export OTEL_LOGS_EXPORTER="none"
export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.05"

goose run --recipe hello-world.yaml

```

### Console Exporter for Debugging

Route telemetry to stdout without requiring a collector:

```bash
export OTEL_TRACES_EXPORTER="console"
export OTEL_METRICS_EXPORTER="console"
export OTEL_LOGS_EXPORTER="console"

goose run --recipe debug.yaml

```

Traces appear as JSON on stdout, metrics as periodic JSON, and logs as human-readable lines.

### Separate Metrics Endpoint

Use different collectors for different signals:

```bash
export OTEL_EXPORTER_OTLP_ENDPOINT="http://collector-all:4318"
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="http://prometheus-collector:4318"
export OTEL_METRICS_EXPORTER="otlp"

goose run --recipe-metrics-only.yaml

```

### Programmatic Configuration

For custom Rust tools embedding Goose, invoke the same helpers directly:

```rust
use goose::otel::otlp::{init_otlp_layers, shutdown_otlp};
use goose::config::Config;

let layers = init_otlp_layers(&config);
// Attach layers to your tracing_subscriber::Registry
// On shutdown:
shutdown_otlp();

```

The `shutdown_otlp()` function (defined in [`crates/goose/src/otel/otlp.rs`](https://github.com/block/goose/blob/main/crates/goose/src/otel/otlp.rs)) flushes all providers to ensure pending data reaches the collector before the process exits.

## Key Implementation Files

- **[`crates/goose/src/otel/otlp.rs`](https://github.com/block/goose/blob/main/crates/goose/src/otel/otlp.rs)** — Contains `create_otlp_tracing_layer()`, `create_otlp_metrics_layer()`, `create_otlp_logs_layer()`, and `init_otlp_layers()`, plus the resource builder and shutdown logic.
- **[`crates/goose-cli/src/logging.rs`](https://github.com/block/goose/blob/main/crates/goose-cli/src/logging.rs)** — Adds OTel layers to the CLI subscriber when the `otel` feature is enabled.
- **[`crates/goose-server/src/logging.rs`](https://github.com/block/goose/blob/main/crates/goose-server/src/logging.rs)** — Same initialization for the server binary.
- **[`documentation/docs/guides/environment-variables.md`](https://github.com/block/goose/blob/main/documentation/docs/guides/environment-variables.md)** — User-facing documentation for all OTel-related variables.

## Summary

- Goose implements OpenTelemetry through three layers (traces, metrics, logs) in [`crates/goose/src/otel/otlp.rs`](https://github.com/block/goose/blob/main/crates/goose/src/otel/otlp.rs), activated via the `otel` feature flag.
- Configure exports entirely through standard OTel environment variables like `OTEL_EXPORTER_OTLP_ENDPOINT` and `OTEL_TRACES_EXPORTER`.
- The `init_otlp_layers()` function automatically selects exporters (`otlp`, `console`, or `none`) and registers global providers.
- Resource attributes including `service.name` derive from `OTEL_SERVICE_NAME` and `OTEL_RESOURCE_ATTRIBUTES`.
- Always use `shutdown_otlp()` on termination to ensure complete data flushing to collectors.

## Frequently Asked Questions

### How do I completely disable OpenTelemetry in Goose?

Set `OTEL_SDK_DISABLED=true` or configure individual signals to `none` (e.g., `OTEL_TRACES_EXPORTER="none"`). This prevents the `init_otlp_layers()` function from instantiating exporters, effectively disabling telemetry collection without requiring a rebuild.

### Can I use different endpoints for traces and metrics?

Yes. While `OTEL_EXPORTER_OTLP_ENDPOINT` sets a base endpoint for all signals, you can override specific signals using `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`, `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`, or `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`. This enables routing traces to Jaeger while sending metrics to Prometheus, for example.

### What happens to telemetry data when Goose shuts down?

The `shutdown_otlp()` function in [`crates/goose/src/otel/otlp.rs`](https://github.com/block/goose/blob/main/crates/goose/src/otel/otlp.rs) flushes all pending spans, metrics, and logs to the configured collectors before the process exits. This ensures no data loss during short-lived CLI runs or server restarts.

### Can I configure OpenTelemetry through the Goose config file instead of environment variables?

Yes. Goose promotes configuration values such as `otel_exporter_otlp_endpoint` and `otel_exporter_otlp_timeout` from the config file to environment variables if the corresponding env vars are missing. This allows you to store persistent OTel settings in your Goose configuration while still respecting environment overrides for sensitive or dynamic values.