Skip to main content

OTLP proxy and independent telemetry stack

Backplane has two integration boundaries: an OTLP/HTTP proxy like Komeet's gateway and an authenticated query API through storage drivers (Explore, Errors). The Collector and telemetry stores are independent deployment-owned services. They ingest, process and retain telemetry without Backplane; applications may send directly to the Collector. Backplane does not manage their lifecycle.

The console listener can proxy OTLP/HTTP at <console-prefix>/telemetry/v1/logs, /v1/traces, and /v1/metrics. Set BACKPLANE_OTLP_URL to the Collector base URL to enable these routes. An empty URL disables them. They require no operator session, registry lookup, module declaration or IAM call. This is HTTP admission; OTLP/gRPC admission is not implemented. The server process still has its ordinary startup dependencies.

The console itself sends logs to <console-prefix>/auth/telemetry/v1/logs. This POST-only route requires a valid operator session and passes the console's origin checks. It shares the public admission handler's rate, size, concurrency and timeout budgets, but uses session authentication instead of an ingest key. It accepts logs only, strips credentials before forwarding, and marks traffic as X-Backplane-Ingest: proxy just like the public routes. The Vite console proxies it with the other /auth/ routes.

The proxy treats signal payloads as opaque bytes. It never parses or re-encodes OTLP messages, reserves attribute names, injects storage metadata, duplicates records or archives metric points. It marks every forwarded request with the header X-Backplane-Ingest: proxy (replacing any the client sent): the Collector's backplane pipeline drops audit records that came this way, so browsers and public ingest keys cannot write audit (audit-api.md); exception records from the proxy are stored as errors marked ingest=proxy (errors.md). Gzip is decoded only to enforce the body budget; the uncompressed payload is forwarded unchanged. Protocol validation, redaction, enrichment, batching and export belong to the external OTel pipeline.

Only POST and browser preflight OPTIONS are accepted. JSON and protobuf are supported, with identity or gzip encoding. Client credentials, cookies, forwarding headers, baggage and URL query parameters never reach the Collector. Public resource attributes remain untrusted source data. The proxy does not retry an uncertain export or follow Collector redirects. Collector responses, including OTLP partial-success bodies and Retry-After, pass back to the sender. HTTP acceptance is not a guarantee of durable storage.

Deployment controls​

Environment variableDefaultMeaning
BACKPLANE_OTLP_URLemptyCollector base URL; empty disables admission
BACKPLANE_OTLP_BODY_BYTES2097152Maximum compressed and decompressed request bytes
BACKPLANE_OTLP_CONCURRENT16Concurrent body reads/decompression/forwards per replica; no waiting queue
BACKPLANE_OTLP_RATE_PER_MINUTE300Per-IP token refill rate
BACKPLANE_OTLP_BURST120Per-IP bucket capacity
BACKPLANE_OTLP_MAX_IPS4096Maximum active IP buckets per replica
BACKPLANE_OTLP_IDLE_TTL10mIdle bucket expiry; cannot be shorter than full bucket refill time
BACKPLANE_OTLP_TIMEOUT10sDeadline covering body read and Collector response
BACKPLANE_OTLP_TRUSTED_PROXIESemptyJSON array of CIDRs allowed in the forwarded-address chain
BACKPLANE_OTLP_KEYSemptyOptional JSON array of ingest-only bearer keys, each at least 16 characters

Rate exhaustion returns 429. Full concurrent-request or IP-table capacity returns 503. Both include Retry-After. Expired IP entries are reclaimed; new addresses cannot evict active entries and reset their rate limits. Proxy chains are walked from the right only through configured trusted networks. Excessive forwarded headers are ignored. Response bodies from Collector are bounded to 64 KiB. Memory for active request buffers scales with body size times concurrency, including allocation overhead; the IP table is independently bounded.

Limits apply separately on each replica. Installation-wide budgets and network connection limits belong at the deployment edge. Optional key rotation uses an overlapping configured set and a rollout; no key-management API or UI exists. Keys do not grant reads/admin access. A key shipped in a browser is public.

Local pipeline and verification​

docker-compose.observability.yaml starts a separate optional Collector/Victoria stack. Use a dedicated Compose project for tests:

docker compose -p backplane-obs-check -f docker-compose.observability.yaml up -d
make test-otlp
docker compose -p backplane-obs-check -f docker-compose.observability.yaml down

The sample Collector uses a memory limiter before export and a container memory limit. Its queues, asynchronous batching and exporter retries are disabled; the original sender handles transient failures. Internal query/Collector ports are published on loopback for development only. Storage retention is configured in Compose and never exposed in platform UI.

Tests cover gzip expansion and chunked limits, slow body deadlines, IP spoofing, IP-table bounds/expiry, concurrency exhaustion, cancellation, upstream timeout, credential stripping and optional key rotation overlap. The live test sends logs, metrics and a cross-service trace through the proxy and Collector, then reads Victoria through its native query APIs. It also closes the proxy and sends directly to the Collector, verifying that telemetry ingestion remains independent. Opaque JSON/protobuf forwarding tests cover unknown fields, custom names and Collector error/partial-success responses. The observability live suite also reads stored signals through ObsService; see read API acceptance.

Storage and query boundary​

Storage representation is owned by the selected telemetry backend. Backplane's query driver exposes that backend's data and capabilities without introducing another persistent representation or inferring types that the backend discarded.

The pinned VictoriaLogs maps structured log bodies to flattened string-valued search fields. The live fixture verifies the decimal digits of int64 9007199254740993, nested search-field values and nanosecond timestamps; it does not claim reconstruction of the original typed OTLP envelope. VictoriaTraces is read through its trace API and VictoriaMetrics through its time-series API. The platform UI and module APIs consume the driver's query results.

Queries require the operator cookie or the protected internal service API's installation secret. Collector/storage endpoints and credentials are deployment configuration and are not exposed to browser plugins. The query API remains a separate implementation step; the proxy needs no storage credentials.