Skip to main content

OTLP tracing setup

Crewship emits OpenTelemetry GenAI spans for every LLM call via internal/telemetry. Any OTLP-compatible backend (self-hosted or managed) can receive those spans by setting two env vars on the Crewship process — no code change required.

Prerequisites

  • An OTLP/HTTP-capable tracing backend reachable from the Crewship process.
  • Credentials supplied by your backend (typical formats: a bearer token, a Basic-auth pair, or an API-key header).
  • OTEL_EXPORTER_OTLP_* env vars supported by Crewship since the internal/telemetry package was added.

Configure

Set the env vars on the Crewship process (.env.local for dev.sh, systemd unit Environment= for prod, container -e for Docker):
OTEL_EXPORTER_OTLP_ENDPOINT is a base URL, so /v1/traces is appended to whatever you set — including a project-scoped path. A backend documenting /api/public/otel wants spans at /api/public/otel/v1/traces, and setting the prefix is enough:
If a path already ends in /v1/traces it is not doubled, so an endpoint written out in full keeps working. To send traces somewhere the base-URL rule would not reach — a collector serving them off a non-standard path — set the signal-specific variable instead. It is used exactly as written, and takes precedence:
Compute a Basic-auth header once if needed:
Restart crewship. The init logs one of:
  • OTel GenAI telemetry enabled traces_url=http://.../v1/traces → working. This is the resolved URL, signal path included — if it is not where you expected spans to go, the configuration is wrong and this line says so before any span is dropped.
  • telemetry init failed, falling back to noop tracer → check endpoint reachability and that header values parse (no quotes, no newline in base64).

Smoke test

Verify endpoint + auth without waiting for an LLM call. curl needs the resolved URL — the one the startup line prints as traces_url:
A 2xx response confirms the endpoint accepts spans. Most backends expose a Traces / Spans UI where the crewship-smoke service should appear shortly after.

What you get

Crewship’s internal/telemetry wires GenAI Semantic Convention attributes on every LLM call (defined in internal/telemetry/spans.go):
  • gen_ai.systemanthropic, openai, ollama
  • gen_ai.request.model — model identifier
  • gen_ai.usage.input_tokens / output_tokens / cached_input_tokens
  • gen_ai.usage.cache_creation_tokens
  • gen_ai.cost.total_usd
Crewship-specific correlation keys, also from spans.go:
  • crewship.agent.id, crewship.agent.type
  • crewship.crew.id, crewship.mission.id
  • crewship.tool.name, crewship.tool.args_hash, crewship.tool.side_effect
Routine-step traces add (from spans_routine.go):
  • crewship.routine.slug, crewship.routine.run_id, crewship.routine.pipeline_id
  • crewship.routine.step.id, crewship.routine.step.type, crewship.routine.step.attempt
Filter on these in your backend for per-provider, per-crew, or per-mission views.

Operational notes

  • Same-host loopback avoids the reverse-proxy hop. If the backend is on a different host, point the endpoint at its public DNS — Crewship uses HTTPS automatically when the endpoint URL starts https://.
  • Batching is 5 s / 2048 spans / 512 per batch (see internal/telemetry/provider.go). Spans emitted shortly before a forced shutdown may drop; the shutdown hook flushes whatever is still queued.
  • No-op fallback — empty OTEL_EXPORTER_OTLP_ENDPOINT keeps the binary running without an observability stack, which is the local dev default.
  • Per-deployment isolation — if your backend supports per-project credentials, give every deployment its own pair so dev/staging/prod traces stay sorted. Only OTEL_EXPORTER_OTLP_HEADERS needs to change.

Troubleshooting