otellens is a custom OpenTelemetry Collector exporter designed for live, on-demand debugging in high-volume environments.
Collector live streams are hard to debug safely:
- Enabling debug logs floods storage.
- Reproducing pipelines locally is often unrealistic.
This project provides an exporter sink that drops data by default and only captures targeted telemetry when an API client asks for it.
- No retention by default: if there are no active debug sessions, incoming telemetry is rejected immediately.
- Low overhead hot path: exporter checks an atomic flag and returns.
- On-demand capture: HTTP request creates a temporary filter session.
- Bounded capture: session ends after
max_batches, timeout, or client disconnect. - Streaming output: NDJSON stream over HTTP for immediate consumption.
- Collector sends logs/metrics/traces to
otellensexporter. - Exporter checks active filter registry.
- If no active sessions: drop batch (
nilreturn). - If active sessions exist: match filters and push compact payload envelopes to session queues.
- API handler streams envelopes to the caller and deregisters session on completion.
Request example:
{
"signals": ["metrics"],
"metric_names": ["A", "!foo", "!bar"],
"attribute_names": ["service.name", "http.route"],
"bucket_counts_count": 28,
"explicit_bounds_count": 29,
"verbose_metrics": true,
"max_batches": 15,
"timeout_seconds": 30
}For exact-match NOT filters, prefix a value with !:
metric_names: include/exclude metric namesspan_names: include/exclude span namesattribute_names: include/exclude attribute keys
Example: attribute_names=["client_name"] and metric_names=["!foo","!bar"] means
"metrics that have client_name and whose metric name is not foo or bar".
Set verbose_metrics=true to include histogram datapoint fields bucket_counts and explicit_bounds.
By default (verbose_metrics=false), those fields are omitted for lower payload size.
Use bucket_counts_count and/or explicit_bounds_count to filter histogram metrics by datapoint shape.
Matching rule is exact equality and succeeds when any histogram datapoint in the metric matches.
attribute_names matches on OTEL attribute keys found in parsed attribute maps across signal structures:
- metrics: resource/scope/datapoint attributes
- traces: resource/scope/span/span-event attributes
- logs: resource/scope/log-record attributes
Response type: application/x-ndjson
Each line is either:
- a telemetry
Envelope, or - a terminal
StreamEndevent.
Built-in web UI for interactive live capture:
- start/stop streaming sessions
- configure all request filters (
signals,metric_names,span_names,attribute_names,resource_attributes,log_body_contains,min_severity_number,max_batches,timeout_seconds) - optional
verbose_metricstoggle to include histogram bucket details - view streamed NDJSON events as formatted JSON
This module exposes otellens.NewFactory() so it can be wired into a custom Collector distribution.
Exporter type name: otellens
Example config section:
exporters:
otellens:
http_addr: ":18080"
max_concurrent_sessions: 256
default_session_timeout: 30s
session_buffer_size: 64exporter.go: public collector factory entrypoint.internal/exporter: collector exporter and shared runtime.internal/capture: filter/session/registry domain.internal/httpapi: NDJSON streaming API.internal/model: wire payload contracts.
go mod tidy
go test './...'The repository includes:
Dockerfile(multi-stage build)build/otelcol-builder.yaml(OCB manifest)build/otelcol-config.yaml(runtime collector config)
Build image:
docker build -t otellens-otelcol:latest .Run image:
docker run --rm -p 4317:4317 -p 4318:4318 -p 9411:9411 -p 13133:13133 -p 18080:18080 otellens-otelcol:latestIncluded receivers in the default config:
- OTLP gRPC:
4317 - OTLP HTTP:
4318 - Zipkin:
9411 - Prometheus scrape receiver (collector self-scrape):
localhost:8888 - Health check extension:
13133
Open capture stream:
curl -N -X POST http://localhost:18080/v1/capture/stream \
-H 'content-type: application/json' \
-d '{"signals":["metrics"],"metric_names":["A"],"max_batches":15,"timeout_seconds":30}'Workflow file:
.github/workflows/docker-publish.yml
It builds and pushes to GHCR on:
- pushes to
main - version tags like
v1.0.0 - manual runs (
workflow_dispatch)
Published image:
ghcr.io/<github-owner>/otellens
To make it public, open the package page in GitHub Container Registry and set visibility to Public.
This project intentionally emits compact summaries rather than full pdata dumps in v1 to keep CPU and memory overhead predictable.