OTLP record with `time_unix_nano: 0` is rejected with an empty `error_message`, and the response is JSON for a protobuf request
Repository: quickwit-oss/quickwit
Version: 0.8.2 (quickwit/quickwit:0.8.2), QW_ENABLE_OTLP_ENDPOINT=true
Reproduced first-hand: yes, on 2026-09-09.
What happens
A log record whose time_unix_nano is 0 is answered HTTP 200 with
{ "partial_success": { "rejected_log_records": 1, "error_message": "" } }and never becomes queryable. Two things about that response are worth separating.
The rejection is reported, but with no reason. rejected_log_records: 1 is
the right channel and Quickwit uses it, which several stores do not. But
error_message is empty, so a caller learns that one record was dropped and
nothing about why. observed_time_unix_nano is present and carries a real
instant, so there is somewhere to file the record if Quickwit wanted one.
The response is JSON although the request was protobuf. The OTLP/HTTP
specification requires the response to use the same encoding as the request.
Here the body is JSON and content-type says application/json, so a
conformant protobuf client cannot decode it — meaning the report above does not
reach the client it is meant for. (OpenObserve makes the same mistake in the
other direction, answering a JSON export with protobuf; that is filed with them
separately. It is mentioned only so this is not read as singling one project
out.)
Why the record is conformant
opentelemetry-proto documents time_unix_nano as the time the event occurred,
with a value of 0 indicating that the timestamp is unknown or missing. It is not
a malformed field — it is the defined way to say "I do not know when this
happened", and an exporter with no event time emits exactly this.
Refusing it is a defensible choice. Refusing it without saying why is the part worth fixing, and four other stores accept the record.
Reproduction
docker run -d --name qw -p 7280:7280 \
-e QW_ENABLE_OTLP_ENDPOINT=true quickwit/quickwit:0.8.2 run
until curl -sf localhost:7280/health/livez >/dev/null; do sleep 1; done; sleep 10
# One record: severityText INFO, a body, attribute specmatrix.run=issue-tzero,
# observed_time_unix_nano set, and time_unix_nano = 0.
base64 -d > /tmp/tzero.pb <<'B64'
CoEBCh4KHAoMc2VydmljZS5uYW1lEgwKCnNwZWNtYXRyaXgSXwoMCgpzcGVjbWF0cml4Ek8QCRoESU5GTyobChlzcGVjbWF0cml4IHplcm8gdGltZXN0YW1wMh8KDnNwZWNtYXRyaXgucnVuEg0KC2lzc3VlLXR6ZXJvWQB2wF/KcdMY
B64
curl -s -D - -X POST localhost:7280/api/v1/otlp/v1/logs \
-H 'Content-Type: application/x-protobuf' --data-binary @/tmp/tzero.pb
sleep 10
curl -s 'localhost:7280/api/v1/otel-logs-v0_7/search?query=attributes.specmatrix.run:issue-tzero'Observed: HTTP 200, content-type: application/json, the partial_success body
quoted above, and num_hits: 0.
Where this came from
SpecMatrix, a conformance corpus for observability backends. The check is
cases/otlp-logs/timestamp-zero.yaml, which records rather than judges what a
store does with an unknown timestamp — Parseable files it at the epoch, three
others substitute their ingest time. It became a finding here because the
record is discarded and the reason is not given.
Found by SpecMatrix, a conformance corpus for observability backends. Happy to be told this is configuration or already known — the check will record whichever it turns out to be.
Source: quickwit-oss/quickwit