How to Configure OpenTelemetry Exports for Goose Observability

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, 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 lines 71-74) and server (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:

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:

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:

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:

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:

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:

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) flushes all providers to ensure pending data reaches the collector before the process exits.

Key Implementation Files

Summary

  • Goose implements OpenTelemetry through three layers (traces, metrics, logs) in 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →