Part of the multi-sink logging effort (meta issue below). Builds on the core pipeline and
BatchingSink issues.
Why this one first among the integrations
One OTLP/HTTP JSON sink reaches Grafana Loki ≥ 3.0 (/otlp/v1/logs), Parseable, SigNoz,
Datadog, Axiom, Honeycomb, New Relic and any OpenTelemetry Collector — so it covers most of
the integration matrix before a single vendor-specific sink exists. The OTLP spec declares
the logs signal Stable and specifies a JSON encoding as a first-class alternative to
binary protobuf, so no protobuf library is needed.
The official JS exporter is deliberately not taken as a dependency: it is still an
experimental 0.x package whose README warns that any release may break. Hand-rolling the
wire format against a stable spec is the lower-risk option, and it matches how the OTel
tracing and logs bridge adapters already work here.
Scope
src/logging/OtlpHttpSink.ts + options family, extends BatchingSink.
Body: ExportLogsServiceRequest in proto3 JSON — resourceLogs[].scopeLogs[].logRecords[],
lowerCamelCase keys, enum values as integers (the spec forbids the name strings).
timeUnixNano — a string, computed as (BigInt(timestampMs) * 1_000_000n).toString().
A float multiply would exceed Number.MAX_SAFE_INTEGER and silently corrupt the low
digits.
severityNumber: Debug → 5, Info → 9, Warn → 13, Error → 17, plus severityText.
body: { stringValue: message }.
- attributes from
source, displayName, fields and args, mapped to the typed
stringValue / intValue / doubleValue / boolValue union.
- resource attribute
service.name, defaulting to the system name captured at attach.
Transport: bare global fetch (the accepted cross-runtime position here), guarded like
SseActor does, with an AbortController timeout (requestTimeoutMs, default 10 s) and an
injectable fetchFn?: typeof fetch option as the test seam. Optional gzip request body via
compressorFor('gzip') with Content-Encoding: gzip — servers must support it per spec.
Retry classification per the spec: 429, 502, 503, 504 only are retryable (honouring
Retry-After); every other non-2xx is non-retryable and the batch is dropped rather than
looped on.
Options: url (default http://localhost:4318/v1/logs), gzip, serviceName,
requestTimeoutMs, headers (code-only — credentials do not belong in HOCON, cf. #590 /
#592 / #741), delivery. HOCON block actor-ts.logger.sinks.otlp without headers.
Acceptance criteria
Reference
Part of #1150.
Part of the multi-sink logging effort (meta issue below). Builds on the core pipeline and
BatchingSinkissues.Why this one first among the integrations
One OTLP/HTTP JSON sink reaches Grafana Loki ≥ 3.0 (
/otlp/v1/logs), Parseable, SigNoz,Datadog, Axiom, Honeycomb, New Relic and any OpenTelemetry Collector — so it covers most of
the integration matrix before a single vendor-specific sink exists. The OTLP spec declares
the logs signal Stable and specifies a JSON encoding as a first-class alternative to
binary protobuf, so no protobuf library is needed.
The official JS exporter is deliberately not taken as a dependency: it is still an
experimental 0.x package whose README warns that any release may break. Hand-rolling the
wire format against a stable spec is the lower-risk option, and it matches how the OTel
tracing and logs bridge adapters already work here.
Scope
src/logging/OtlpHttpSink.ts+ options family,extends BatchingSink.Body:
ExportLogsServiceRequestin proto3 JSON —resourceLogs[].scopeLogs[].logRecords[],lowerCamelCase keys, enum values as integers (the spec forbids the name strings).
timeUnixNano— a string, computed as(BigInt(timestampMs) * 1_000_000n).toString().A float multiply would exceed
Number.MAX_SAFE_INTEGERand silently corrupt the lowdigits.
severityNumber: Debug → 5, Info → 9, Warn → 13, Error → 17, plusseverityText.body: { stringValue: message }.source,displayName,fieldsandargs, mapped to the typedstringValue/intValue/doubleValue/boolValueunion.service.name, defaulting to the system name captured atattach.Transport: bare global
fetch(the accepted cross-runtime position here), guarded likeSseActordoes, with anAbortControllertimeout (requestTimeoutMs, default 10 s) and aninjectable
fetchFn?: typeof fetchoption as the test seam. Optional gzip request body viacompressorFor('gzip')withContent-Encoding: gzip— servers must support it per spec.Retry classification per the spec: 429, 502, 503, 504 only are retryable (honouring
Retry-After); every other non-2xx is non-retryable and the batch is dropped rather thanlooped on.
Options:
url(defaulthttp://localhost:4318/v1/logs),gzip,serviceName,requestTimeoutMs,headers(code-only — credentials do not belong in HOCON, cf. #590 /#592 / #741),
delivery. HOCON blockactor-ts.logger.sinks.otlpwithoutheaders.Acceptance criteria
fetchFn: URL, headers, and the fullJSON body shape including nanosecond strings and integer severity numbers
Retry-Afterhonoured, 400 and 401 dropimmediately
observability/logging/integrations(page created here), EN + DE,including the coverage table (which platforms this one sink reaches)
bun run typecheck+bun testgreen on every commit; coverage gate before mergeReference
Part of #1150.