Run one collector as a gateway and fan out — your existing vendor keeps everything it gets today, and SightLift gets its own copy.
← Back to the dashboardIs this the guide you want?
Yes if Datadog, Splunk, Grafana, Honeycomb or similar already consumes your Claude telemetry and you want SightLift too.
No if nothing consumes it yet — then skip the collector and use Connect Claude to SightLift instead.
Step 1: the shape
You run the gateway; SightLift is one destination on it, not the hub. Claude Code and Cowork each send to one place — your collector — which decides who gets a copy.
Claude Code ─┐
│ ┌─────────────────────────┐ ┌──────────────────────┐
Cowork ─┼───────►│ YOUR collector │─────►│ your existing vendor │
│ │ (gateway — you own it) │ └──────────────────────┘
anything else ┘ │ │ ┌──────────────────────┐
│ │─────►│ SightLift │
└─────────────────────────┘ └──────────────────────┘
This is the Collector's gateway pattern, and fan-out is built in: a pipeline lists destinations, every record goes to all of them. One egress point to allowlist, and adding or dropping a vendor (us included) is one config block — not a settings rollout to every developer.
Why not let your vendor relay to us? It makes them a dependency for our data freshness and hands them all of your prompt content. We can't be your gateway either — our ingest writes to storage and forwards nowhere, by design.
Step 2: what to send
Every capability survives three hops: Claude emits it, your collector carries it, your collector sends it. This table is the contract.
| What you get in SightLift | Turn on at the source | Travels as | Pipeline |
|---|---|---|---|
| Activity, adoption, cost | CLAUDE_CODE_ENABLE_TELEMETRYOTEL_LOGS_EXPORTER |
log records | logs |
| Accurate topic classification | OTEL_LOG_USER_PROMPTS |
log record body | logs |
| Candidate automations (the command that ran) | OTEL_LOG_TOOL_DETAILS |
tool_parameters attributes |
logs |
| Verified capabilities (proof it reproduces your results) | CLAUDE_CODE_ENHANCED_TELEMETRY_BETAOTEL_TRACES_EXPORTEROTEL_LOG_TOOL_CONTENT |
a tool.output event on a span |
traces |
| Cowork at all | Cowork Monitoring fields (content on by default) |
log records + spans | both |
| Rule | Because |
|---|---|
| Enable content at the source | Redaction only removes. Without OTEL_LOG_USER_PROMPTS every prompt arrives as the literal <REDACTED> — no pipeline change brings it back. |
| Don't filter toward us | Our ingest drops what it can't use via a denylist, so anything unrecognized is kept. A filter on your side is an allowlist — it would drop what we can't re-request. Trim on the vendor pipeline. |
| Never send us metrics | No metrics endpoint — we 405 every batch. Your vendor can use them, so enable Claude Code metrics and route them to the vendor only. |
Step 3: credentials
Needs the Admin role in SightLift.
Authorization=Bearer sl_… as one string, which is what Claude Code's env var wants. Collector YAML wants a header map, so pasting it there yields a header whose value starts Authorization=Bearer — a 401 that looks like a bad key. Here, SIGHTLIFT_INGEST_KEY is the key alone.
Have your vendor's ingest credential to hand too — both go in the same file in step 5.
Step 4: pick the collector
Run this one
otelcol-contrib 0.157.0 — the OpenTelemetry
project's own build, from the group that defines the protocol. Image
otel/opentelemetry-collector-contrib:0.157.0; step 5 runs it as one
container and you install nothing else. Your gateway shouldn't belong to any vendor it
feeds, us included.
Contrib, not plain otelcol. A two-destination fan-out works
on either, but the redaction processor in step 9 and the
file_storage extension in step 10 are contrib-only —
start on core and you'll be building a custom distribution to finish this page.
otlphttp/sightlift exporter below to your config and list it on your
logs and traces pipelines. Check two things first: it runs as a
gateway, not a per-host agent, and its processors aren't already stripping the
content step 2 needs — plenty of pipelines drop log bodies by
default.
Make a directory for three small files. This is the first, and it's complete — one place in, two places out.
collector.yaml
# Receive OTLP from Claude Code, Cowork, and anything else you point here. receivers: otlp: protocols: http: # 4318 — what Claude Code and Cowork use endpoint: 0.0.0.0:4318 grpc: # 4317 — for anything else you send here endpoint: 0.0.0.0:4317 processors: # Keep the collector from eating the host if a burst arrives. Must run first. memory_limiter: check_interval: 1s limit_mib: 512 spike_limit_mib: 128 batch: {} # group records before sending. Must run last. exporters: # SightLift. The two signal-specific endpoints are used VERBATIM, so the # full path belongs here. Note there is deliberately no metrics endpoint. otlphttp/sightlift: logs_endpoint: https://app.sightlift.ai/v1/logs traces_endpoint: https://app.sightlift.ai/v1/traces headers: Authorization: "Bearer ${env:SIGHTLIFT_INGEST_KEY}" # Your existing vendor. Replace the endpoint and the auth header with # whatever they document. A BASE endpoint like this one gets /v1/logs and # /v1/traces appended automatically. otlphttp/vendor: endpoint: https://otlp.your-vendor.example headers: api-key: "${env:VENDOR_API_KEY}" service: pipelines: # Listing two exporters IS the fan-out. Every record goes to both. logs: receivers: [otlp] processors: [memory_limiter, batch] exporters: [otlphttp/sightlift, otlphttp/vendor] # Do not skip this one: tool OUTPUT rides on spans, not log records. traces: receivers: [otlp] processors: [memory_limiter, batch] exporters: [otlphttp/sightlift, otlphttp/vendor] # Metrics go to your vendor ONLY. SightLift has no metrics endpoint and # will 405 every batch. Delete this block if you don't collect metrics. metrics: receivers: [otlp] processors: [memory_limiter, batch] exporters: [otlphttp/vendor] telemetry: # The collector's own metrics — this is how you prove, in step 7, that # each destination is receiving. 0.0.0.0 so it's reachable outside the # container; the default 127.0.0.1 would only answer inside it. metrics: readers: - pull: exporter: prometheus: host: 0.0.0.0 port: 8888
No routing component needed. A pipeline hands every record to every exporter it lists, and each keeps its own queue and retries independently — so a slow or dead vendor can't block the other. There's no "primary" to choose.
Step 5: run it
Both credentials go in .env beside collector.yaml — out of the config file and your shell history.
.env
# The key on its own — no "Authorization=Bearer" prefix. See step 3. SIGHTLIFT_INGEST_KEY=sl_your_key_here VENDOR_API_KEY=your_other_vendor_key
Then the compose file. Pinned to an exact version, so upgrades are a choice.
compose.yaml
services: otelcol: image: otel/opentelemetry-collector-contrib:0.157.0 restart: unless-stopped env_file: .env volumes: # The contrib image reads this exact path. See the warning below. - ./collector.yaml:/etc/otelcol-contrib/config.yaml:ro ports: - "4318:4318" # OTLP/HTTP — Claude Code and Cowork - "4317:4317" # OTLP/gRPC — anything else - "127.0.0.1:8888:8888" # its own metrics, loopback only
/etc/otelcol-contrib/config.yaml — with the -contrib. Plain otelcol uses /etc/otelcol/config.yaml, and using that path here is the most common mistake with this image: the collector finds nothing, falls back to its baked-in default, and starts perfectly healthy — exporting nothing, anywhere, with no hint in the log.
Validate first — this parses the file, resolves the env vars, and exits without starting anything:
docker run --rm --env-file .env \ -v "$PWD/collector.yaml:/etc/otelcol-contrib/config.yaml:ro" \ otel/opentelemetry-collector-contrib:0.157.0 \ validate --config=/etc/otelcol-contrib/config.yaml
No output means valid. Bring it up:
docker compose up -d docker compose logs -f otelcol
Look for Everything is ready. Begin running and processing data. The startup log lists the pipelines it built — check both logs and traces are there.
Note: the image is FROM scratch with no shell, so docker compose exec otelcol sh fails. Expected — debug with validate above and the metrics in step 7.
https://otel.example.com. Plain http:// on a private network works, but laptops off the VPN stop reporting.Step 6: point Claude at it
Same as Connect Claude to SightLift, with two differences: the endpoint is your collector, and there's no Authorization header — your collector holds the credentials now.
Replace otel.example.com with your hostname. If your org already has managed settings, merge the env block in rather than replacing the file.
{
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA": "1",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_TRACES_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://otel.example.com:4318",
"OTEL_LOG_USER_PROMPTS": "1",
"OTEL_LOG_ASSISTANT_RESPONSES": "1",
"OTEL_LOG_TOOL_DETAILS": "1",
"OTEL_LOG_TOOL_CONTENT": "1"
}
}
No path on that endpoint. OTEL_EXPORTER_OTLP_ENDPOINT is the generic form — the exporter appends /v1/logs and /v1/traces itself. (The other guide uses the signal-specific variables, which are taken verbatim.)
OTEL_LOG_* flags are the difference between a chart and an automation. Off by default, and this is the only place to switch them on — whatever is off here never reaches your collector, so it can never be forwarded. Enable per team as your security review allows. What each unlocks: data access & privacy.
Under Organization settings → Cowork → Monitoring: OTLP endpoint https://otel.example.com:4318, protocol http/json, leave headers and resource attributes blank. Cowork allowlists your endpoint for its sandbox on save and says so under the field.
Step 7: prove it
Run a Claude Code session on a machine that has the new settings — they land at sign-in, so sign out and back in first. Then ask the collector what it did:
curl -s localhost:8888/metrics | grep -E 'otelcol_(receiver_accepted|exporter_sent|exporter_send_failed)'
Read it in this order:
| Metric | What it tells you |
|---|---|
otelcol_receiver_accepted_log_recordsotelcol_receiver_accepted_spans | Claude is reaching the collector. Both should climb — spans as well as logs. |
otelcol_exporter_sent_log_recordsotelcol_exporter_sent_spans | Delivery, per exporter. Expect a separate rising series for otlphttp/sightlift and otlphttp/vendor. |
otelcol_exporter_send_failed_log_recordsotelcol_exporter_send_failed_spans | Should stay zero. The exporter label tells you whose credentials to check. |
The per-exporter breakdown is the point: "SightLift is receiving" and "my vendor is still receiving" become two separate facts, so you never take the fan-out on faith or find out months later that one side stopped.
Once sent climbs for otlphttp/sightlift on both signals you're done — activity appears within the hour. No backfill; it starts from now.
Troubleshooting
"No data in SightLift" looks identical whichever hop failed, so work down this table rather than changing things at random. The metrics from step 7 tell you which row you're in.
| What you see | Where it broke | What to check |
|---|---|---|
receiver_accepted flat at zero |
Claude → collector | Settings land at sign-in — sign out and in. Then: endpoint reachable from that machine, 4318 open, and no path left on OTEL_EXPORTER_OTLP_ENDPOINT. |
| Logs arriving, spans at zero | source flags | Needs CLAUDE_CODE_ENHANCED_TELEMETRY_BETA + OTEL_TRACES_EXPORTER set, and a traces pipeline in your config. |
send_failed climbing on otlphttp/sightlift |
collector → SightLift | Almost always the header — SIGHTLIFT_INGEST_KEY must be the key alone. If only metrics fail, remove us from that pipeline. |
| Everything healthy, still nothing in SightLift | the key | The key identifies your org — one from another tenant delivers successfully somewhere you can't see. Reissue from Live telemetry. |
Data arriving, prompts read <REDACTED> |
source flags | OTEL_LOG_USER_PROMPTS is off. No collector change fixes this. |
| Collector healthy, exporting nothing at all | the config mount | It's on its baked-in default config. Mount to /etc/otelcol-contrib/config.yaml — with the -contrib. |
Still stuck? Send the step-7 curl output and your collector.yaml (keys removed) to support@sightlift.ai.
Optional
Each destination can get a different version of the same stream. The usual reason: we need prompt and tool content to classify work and verify automations; your observability vendor needs volumes and latencies, not your engineers' prompts.
No routing component needed — give each destination its own pipeline off the same receiver, and put the processors only where they belong:
collector.yaml — replacing the pipelines block
processors: # ... memory_limiter and batch as before ... # Strip conversation content. Applied ONLY to the vendor pipeline below. redaction/strip_content: allow_all_keys: true # keep every other attribute as-is blocked_key_patterns: - ".*prompt.*" - ".*tool_parameters.*" service: pipelines: # Full fidelity to SightLift. logs/sightlift: receivers: [otlp] processors: [memory_limiter, batch] exporters: [otlphttp/sightlift] # Same records, content stripped, to your vendor. logs/vendor: receivers: [otlp] processors: [memory_limiter, redaction/strip_content, batch] exporters: [otlphttp/vendor] # Traces split the same way. Keep BOTH — see step 2. traces/sightlift: receivers: [otlp] processors: [memory_limiter, batch] exporters: [otlphttp/sightlift] traces/vendor: receivers: [otlp] processors: [memory_limiter, redaction/strip_content, batch] exporters: [otlphttp/vendor]
One receiver can feed any number of pipelines, each getting its own copy. The logs/sightlift naming is just how the Collector tells two pipelines of the same signal apart.
Test your patterns. Point a copy at the debug exporter and read what comes out — attribute names differ between Claude Code and Cowork, and a pattern matching nothing fails silently. To cut whole records rather than edit fields, filter is simpler.
Optional
Send queues live in memory by default, so a restart while a destination is down drops whatever was waiting — and with no backfill, it's gone. Put the queue on disk (this is what SightLift runs on its own ingest collector):
extensions: file_storage/queue: directory: /var/lib/otelcol/storage timeout: 10s exporters: otlphttp/sightlift: # ... endpoints and headers as before ... sending_queue: enabled: true storage: file_storage/queue retry_on_failure: enabled: true initial_interval: 5s max_interval: 30s max_elapsed_time: 300s service: extensions: [file_storage/queue] # declaring it is not enough — list it here # ... pipelines as before ...
Then give it somewhere to write, owned by the container's unprivileged user:
mkdir -p ./otelcol-queue sudo chown 10001:10001 ./otelcol-queue
And mount it alongside the config:
volumes: - ./collector.yaml:/etc/otelcol-contrib/config.yaml:ro - ./otelcol-queue:/var/lib/otelcol/storage
chown 10001 is not optional. The image runs as UID 10001; a fresh directory is owned by root. Skip it and the collector fails to start on a permission error — loudly, unlike most traps here.
Reference
The official Helm chart open-telemetry/opentelemetry-collector with mode: deployment is the same gateway pattern; put the collector.yaml above under the chart's config: key. Note the chart leaves image.repository empty, so set it to otel/opentelemetry-collector-contrib explicitly. Tell us if you'd like a hand — we can walk through your setup.