Documentation Index
Fetch the complete documentation index at: https://recipe.uselettuce.dev/llms.txt
Use this file to discover all available pages before exploring further.
Observability
Self-hosted Lettuce can export traces and structured logs to any OpenTelemetry-compatible backend over OTLP/gRPC. The hosted SaaS doesn’t use this path — it’s intended for operators who already run an observability stack and want Lettuce signals in the same place as the rest of their services. WhenOTEL_EXPORTER_OTLP_ENDPOINT is unset, the OTel subsystem is a
hard no-op: no SDK code loads, no providers are replaced, zero
overhead per request. The same single env var flips both traces and
logs on — see Logs configuration below if you
want to opt out of just one half of the pipeline.
Install the optional extra
Theopentelemetry-* packages are gated behind an optional extra so
your image only pulls them if you actually want them:
Configure the exporter
The standard OpenTelemetry environment variables are honoured — set the endpoint and (optionally) the service name, then restart the service and worker:| Env var | Required | Notes |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | yes | gRPC endpoint of your OTLP collector. Example: http://otel-collector:4317. Unset = OTel disabled. |
OTEL_SERVICE_NAME | no | Defaults to lettuce. Override to differentiate multiple installs. |
OTEL_EXPORTER_OTLP_HEADERS | depends | Backend-specific auth headers. Honeycomb wants x-honeycomb-team=..., New Relic wants api-key=.... |
OTEL_RESOURCE_ATTRIBUTES | no | Free-form key=value,key2=value2 attached to every span. Useful for deployment.environment=prod. |
OTEL_* vars defined by the OpenTelemetry spec also
work — they’re read by the SDK directly, Lettuce doesn’t re-parse
them.
What Lettuce emits
Traces (LET-25) and structured logs (LET-40). Metrics beyond the auto-instrumentation defaults are still a roadmap item.Traces
- HTTP server spans — every inbound request to the API / MCP
endpoint, with
http.method,http.route,http.status_code. httpxclient spans — outbound calls to GitHub, GitLab, Bitbucket, Stripe, OAuth providers. Nested under the inbound request that triggered them.psycopgSQL spans — every database statement with sanitised query text. The single most useful signal for diagnosing API latency.- Worker
job.<kind>spans — one span per claimed background job (job.clone_index,job.embed). Tagged withlettuce.job.id,lettuce.repo.id,lettuce.account.id.
Logs
Every stdliblogging record (INFO and above by default) is shipped
over the same OTLP/gRPC endpoint as a LogRecord. Records emitted
inside an HTTP request span or a background-job span automatically
carry that span’s trace_id and span_id as attributes — which is
what makes log lines clickable from Tempo back to Loki / Datadog
Logs / New Relic Logs. Resource attributes (service.name,
service.version) are shared with the trace pipeline, so the same
Grafana / Datadog filter narrows both signals at once.
Logs configuration
Logs ship alongside traces over the sameOTEL_EXPORTER_OTLP_ENDPOINT,
under a separate OTel signal type (logs) so a collector can fan
them out to a different backend than traces if you want (typically:
traces → Tempo, logs → Loki).
| Env var | Required | Notes |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | yes | Same endpoint as traces. Unset = OTel disabled entirely. |
OTEL_LOGS_EXPORTER | no | Set to none to disable log shipping while keeping traces (e.g. you already ship logs through promtail or a Datadog agent and don’t want a parallel pipeline). Default = OTLP. |
OTEL_LOG_LEVEL | no | Minimum stdlib log level to ship. Default INFO. DEBUG is shippable but very chatty over gRPC. |
Trace-log correlation
opentelemetry-instrumentation-logging patches the stdlib log
record factory so every record emitted inside an active span picks
up the current trace_id and span_id as record attributes. When
the OTel Collector forwards those records to Loki, they land as the
traceID / spanID labels — which the Grafana “Logs to traces”
data-link feature turns into a clickable jump from a Loki log line
back to the originating span in Tempo, and vice versa.
In Datadog Logs / New Relic Logs the same attributes are surfaced
under their respective trace-correlation panels with no extra setup.
Worked example: Loki via OTel Collector
The standard Grafana stack pipeline: Lettuce → OTel Collector → Loki (logs) + Tempo (traces). Lettuce only needs to know about the collector — fan-out is the collector’s job.otel-collector.yaml:
traceID and link it to the Tempo data source. Click the
link on any log line and you land on the originating trace.
Worked example: Grafana Tempo + Loki
Point Lettuce at an OpenTelemetry Collector that fans out to Tempo (traces) and Loki (logs). One env var, both signals.docker-compose.yml excerpt:
otel-collector.yaml that fans out to both Tempo and
Loki.
Once everything’s up, find a recent slow request in Grafana’s Tempo
search; you’ll see the inbound HTTP span at the top, the httpx call
to GitHub nested under it, and any SQL statements that ran while
serving it. Click any log line tied to that request in Loki and
Grafana jumps you straight to the corresponding span in Tempo —
that’s the trace_id / span_id correlation working end-to-end.
Other backends
The endpoint is the only thing that changes:- Datadog Agent (OTLP receiver enabled) —
http://datadog-agent:4317. - Honeycomb —
https://api.honeycomb.io:443+ setOTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=<your-key>. - New Relic —
https://otlp.nr-data.net:4317+ setOTEL_EXPORTER_OTLP_HEADERS=api-key=<your-key>.
Troubleshooting
- Nothing showing up in your backend: confirm the endpoint
reachable from inside the container (
nc -vz otel-collector 4317). The exporter buffers and retries silently — if the collector is unreachable the worker won’t crash, but you also won’t see traces or logs. OTEL_EXPORTER_OTLP_ENDPOINT is set but the [otel] extra isn't installedin the logs: rebuild the image withpip install '.[otel]'. The SaaS Dockerfile deliberately omits this extra.- Traces show up but logs don’t: check that
OTEL_LOGS_EXPORTERisn’t set tonone, and that your collector has alogspipeline configured (the trace pipeline alone will silently drop log records). - Logs show up but no
traceIDlabel in Loki: confirm the log line was actually emitted inside a request or job span — log lines from startup code, scheduled timers etc. legitimately have no trace context. If a log emitted inside a request span also has notraceID, check thatopentelemetry-instrumentation-loggingis installed (it ships with the[otel]extra by default). - Bad scheme / hostname: the service logs
OTLP exporter init failed(orOTel logs setup failedfor the logs half) and continues without that signal. Lettuce will never refuse to boot because of an observability-config mistake.