Local Stacks
Instrumenting code you cannot see the output of is guesswork. The repo ships compose files so a backend is one command away, with no account to create and nothing to bill you.
| Stack | Signals | Start | UI |
|---|---|---|---|
| Jaeger | Traces | docker compose -f docker/jaeger.yml up -d |
http://localhost:16686 |
| LGTM | Traces, metrics, logs | docker compose -f docker/lgtm.yml up -d |
http://localhost:3000 |
| Langfuse | LLM traces, cost, evaluations | docker compose -f docker/langfuse.yml up -d |
http://localhost:3000 |
Swap up -d for down -v to stop and discard the data.
Pick Jaeger when you only care about traces and want the smallest thing that works. Pick LGTM when you want metrics or logs as well, or when you want to query your telemetry back from autotel-mcp.
Pick Langfuse when the thing you are debugging is an LLM call rather than a request: it reads gen_ai.* spans as generations, prices them, and keeps prompts and responses next to the trace.
All three take OTLP straight from your app. Put a collector in front once you want to sample whole traces, mask attributes autotel does not recognise, or count requests before anything is dropped.
Jaeger
Section titled “Jaeger”docker compose -f docker/jaeger.yml up -dOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318import { init } from 'autotel';
init({ service: 'checkout-api' });Traces land at http://localhost:16686.
Grafana’s all-in-one image runs Loki, Grafana, Tempo and Mimir in one container. Anonymous admin is on, so there is no password to look up.
docker compose -f docker/lgtm.yml up -d| Port | Service | Used for |
|---|---|---|
3000 |
Grafana | The UI |
3100 |
Loki | Log push + query |
3200 |
Tempo | Trace query |
9090 |
Prometheus | Metric query |
4317 |
OTLP gRPC | Ingest |
4318 |
OTLP HTTP | Ingest |
The three query ports are published on purpose. Ingest alone would let you write telemetry you could never read back from a tool.
Traces and metrics
Section titled “Traces and metrics”OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318Events into Loki
Section titled “Events into Loki”LOKI_ENDPOINT=http://localhost:3100import { init } from 'autotel';import { LokiSubscriber } from 'autotel-subscribers/loki';
init({ service: 'checkout-api', eventSubscribers: [new LokiSubscriber()],});Then in Grafana, filter by label and reach into the event with | json:
{service="checkout-api"} | json | durationMs > 1000See Grafana Loki for label and cardinality guidance.
Langfuse
Section titled “Langfuse”Langfuse ingests plain OTLP at /api/public/otel, so autotel reaches it through destinations and needs no Langfuse SDK.
docker compose -f docker/langfuse.yml up -dSix containers, roughly two minutes to first boot. Langfuse needs Postgres, ClickHouse, Redis and MinIO, so there is no single-container version the way LGTM has one. Wait for it rather than guessing:
until curl -sf http://localhost:3000/api/public/health >/dev/null; do sleep 5; doneThe project is provisioned on first start from LANGFUSE_INIT_*, so the keys are fixed and you never open the UI to copy one:
LANGFUSE_BASEURL=http://localhost:3000LANGFUSE_PUBLIC_KEY=pk-lf-0d5c0dc9-3b4f-4f3c-9d3a-000000000001LANGFUSE_SECRET_KEY=sk-lf-0d5c0dc9-3b4f-4f3c-9d3a-000000000002import { init } from 'autotel';
const auth = Buffer.from( `${process.env.LANGFUSE_PUBLIC_KEY}:${process.env.LANGFUSE_SECRET_KEY}`,).toString('base64');
init({ service: 'support-agent', destinations: [ { endpoint: `${process.env.LANGFUSE_BASEURL}/api/public/otel`, headers: { Authorization: `Basic ${auth}` }, signals: ['traces'], }, ],});Langfuse turns a chat span into a generation, reads gen_ai.usage.input_tokens and gen_ai.usage.output_tokens as usage, and prices the call from its own model table. Sign in at http://localhost:3000 as dev@example.com / localdevpassword.
Investigate it with autotel-mcp
Section titled “Investigate it with autotel-mcp”Point the MCP server at the query ports and let it work out what is running:
AUTOTEL_BACKEND=auto \TEMPO_BASE_URL=http://localhost:3200 \PROMETHEUS_BASE_URL=http://localhost:9090 \LOKI_BASE_URL=http://localhost:3100 \npx autotel-mcpAutodetection probes /api/echo on Tempo, /api/v1/status/buildinfo on Prometheus and /ready on Loki, then uses whichever answer. An agent can ask about a slow request and get it out of Tempo without you naming a backend.
Running autotel-devtools alongside
Section titled “Running autotel-devtools alongside”autotel-devtools listens on 4318 by default, which is the port LGTM binds for OTLP HTTP. To run both, move devtools:
AUTOTEL_DEVTOOLS_PORT=4319 npx autotel-devtoolsSend to whichever you want to read from: devtools at http://127.0.0.1:4319 for a live view of the request you just made, LGTM at http://localhost:4318 for history and querying.
With Jaeger there is no clash, because Jaeger only takes the OTLP ports and devtools can keep 4318 if Jaeger is down.
Adding a stack
Section titled “Adding a stack”One file per stack under docker/, so a new backend never means editing a shared file. Each should carry:
- a
name:matching the stack, so the compose project is stable wherever it is run from - a
container_name, sodocker logsis predictable - a
healthcheckgating on the component that starts last, not on the UI — a healthy Grafana does not mean Loki is accepting writes - a comment naming any port that clashes with an existing stack
See docker/README.md in the repo.