Skip to content

Add OTLP logs endpoint implementation#147446

Merged
felixbarny merged 8 commits into
elastic:mainfrom
felixbarny:otlp-logs-align-otel-mode
Apr 29, 2026
Merged

Add OTLP logs endpoint implementation#147446
felixbarny merged 8 commits into
elastic:mainfrom
felixbarny:otlp-logs-align-otel-mode

Conversation

@felixbarny
Copy link
Copy Markdown
Member

@felixbarny felixbarny commented Apr 24, 2026

Summary

  • add the initial OTLP logs REST and transport action implementation with log document builders and focused test coverage
  • align OTLP log document serialization with the collector's otel mapping mode for timestamps, mixed-array bodies, and control attribute filtering
  • keep the implementation scoped to logs so metrics behavior remains unchanged

Follow-ups

The endpoint is still behind a feature flag.

Some leftovers
- Handling of geo ip fields
- Preserving nanosecond precision of timestamps
- Store trace flags
- bodymap mapping mode
Restore shared trace and span ID helpers needed by the rebased logs
implementation and keep the module formatting checks green.
Match the upstream OTLP log serializer for timestamps, mixed-array
body wrapping, and control-attribute omission so the initial logs
implementation stays compatible with the collector's otel mapping.
@felixbarny felixbarny requested a review from a team as a code owner April 24, 2026 15:38
@elasticsearchmachine elasticsearchmachine added needs:triage Requires assignment of a team area label external-contributor Pull request authored by a developer outside the Elasticsearch team v9.5.0 labels Apr 24, 2026
@felixbarny felixbarny added the :StorageEngine/Logs You know, for Logs label Apr 24, 2026
@elasticsearchmachine
Copy link
Copy Markdown
Collaborator

Pinging @elastic/es-storage-engine (Team:StorageEngine)

@elasticsearchmachine elasticsearchmachine removed the needs:triage Requires assignment of a team area label label Apr 24, 2026
@felixbarny
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Walkthrough

Introduces OpenTelemetry log indexing capabilities with REST integration tests validating batch indexing, field mapping, data stream routing, and scope handling. Enhances LogDocumentBuilder to derive timestamps, severity, event names, span/trace IDs, and handle complex body structures. Extends OTelDocumentBuilder for additional AnyValue variants and ID serialization helpers. Adds comprehensive unit test coverage.

Changes

Cohort / File(s) Summary
REST Integration Tests
x-pack/plugin/otel-data/src/javaRestTest/java/org/elasticsearch/xpack/oteldata/otlp/OTLPLogsIndexingRestIT.java
Adds four new test methods: testBatchLogIndexing() validates 100-log indexing volume, testLogWithAttributes() verifies body/severity/attribute field mapping, testDataStreamRouting() asserts data stream routing via attributes, and testScopeIsIndexed() checks scope name population.
Log Document Building
x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilder.java
Implements timestamp derivation (@timestamp, observed_timestamp) with fallback logic, conditionally emits severity_number, severity_text, event_name, span_id, trace_id. Restructures resource/scope/attributes output with schema URL and dropped attribute counts. Expands body handling for KVLIST (structured), text, and mixed array payloads.
OTel Document Building
x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/OTelDocumentBuilder.java
Removes exception throwing for unhandled AnyValue cases. Adds serialization for KVLIST_VALUE as JSON object, BYTES_VALUE as raw bytes, and VALUE_NOT_SET as null. Introduces protected helpers addSpanId() and addTraceId() for hex-formatted ID fields.
Unit Tests
x-pack/plugin/otel-data/src/test/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilderTests.java
New comprehensive test suite verifying document field mappings across AnyValue types, event name derivation rules, timestamp logic with fallback/precision, bytes encoding, attribute filtering, and omission of empty fields. Includes helpers for LogRecord construction with Resource and InstrumentationScope.

Sequence Diagram

sequenceDiagram
    participant Test as Test Runner
    participant Logger as OTLP Logger Provider
    participant Builder as LogDocumentBuilder
    participant ES as Elasticsearch

    Test->>Logger: emit log record (body, attributes, resource, scope)
    Logger->>Logger: accumulate in provider
    Test->>Test: call indexLogs()
    Test->>Builder: convert LogRecord to doc
    Builder->>Builder: extract & format timestamps
    Builder->>Builder: map severity, event_name, span_id, trace_id
    Builder->>Builder: handle body (text/kvlist/array)
    Builder->>Builder: flatten attributes & scope
    Test->>ES: index document
    ES->>ES: store fields in data stream
    Test->>ES: search via ObjectPath
    ES-->>Test: return indexed document
    Test->>Test: assert field values
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Through telemetry logs our whiskers twitch with glee,
Timestamps and severity, attributes dance so free,
Building structured bodies, scope and span align,
Elasticsearch awaits—observability so divine! ✨📊

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and clearly summarizes the main objective of the pull request: adding the initial OTLP logs endpoint implementation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly relates to the changeset: it describes adding OTLP logs endpoint implementation with log document builders, aligning serialization with collector's otel mapping mode, and keeping changes scoped to logs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilder.java (2)

68-73: Minor: redundant emptiness guards.

addSpanId / addTraceId already check length > 0, so the isEmpty() guards here are redundant. Dropping them also avoids the toByteArray() copy when the id is empty (though ByteString.toByteArray() is cheap for empty, the double-guard just adds noise).

♻️ Proposed simplification
-        if (logRecord.getSpanId().isEmpty() == false) {
-            addSpanId(builder, logRecord.getSpanId().toByteArray());
-        }
-        if (logRecord.getTraceId().isEmpty() == false) {
-            addTraceId(builder, logRecord.getTraceId().toByteArray());
-        }
+        addSpanId(builder, logRecord.getSpanId().toByteArray());
+        addTraceId(builder, logRecord.getTraceId().toByteArray());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilder.java`
around lines 68 - 73, In LogDocumentBuilder, remove the redundant isEmpty()
checks around logRecord.getSpanId() and logRecord.getTraceId() and call
addSpanId(builder, logRecord.getSpanId().toByteArray()) and addTraceId(builder,
logRecord.getTraceId().toByteArray()) unguarded; addSpanId/addTraceId already
ignore zero-length ids (length > 0), so this eliminates the unnecessary
double-guard and avoids an extra empty ByteString handling path while keeping
behavior unchanged.

88-124: Recommended: consolidate with the base class to avoid near-duplicate resource/scope/attributes helpers.

buildLogResource and buildLogScope are structural duplicates of OTelDocumentBuilder#buildResource and #buildScope; they only differ by calling buildLogAttributes instead of buildAttributes. Similarly, buildLogAttributes is buildAttributes with (a) an extra ignore predicate and (b) lazy opening of the "attributes" object.

Consider lifting this into the base class — e.g., by letting buildAttributes accept an IntPredicate/Predicate<String> of additional ignored keys and by making the lazy-open behavior the default. That removes the log-specific override path entirely and keeps metrics/traces aligned if they later need similar filtering.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilder.java`
around lines 88 - 124, The log-specific helpers buildLogResource, buildLogScope,
and buildLogAttributes duplicate
OTelDocumentBuilder#buildResource/#buildScope/#buildAttributes; refactor by
changing OTelDocumentBuilder#buildAttributes to accept an additional ignore
predicate (e.g., Predicate<String> or IntPredicate for attribute keys) and to do
lazy-opening of the "attributes" object, then have buildResource/buildScope call
this generalized buildAttributes (passing a no-op predicate for non-log callers)
and replace buildLogResource/buildLogScope to delegate to the base
implementations (or remove them); update any callers to pass the log-specific
ignore predicate (isIgnoredLogAttribute) so the log-only filtering behavior is
preserved without duplicating structure.
x-pack/plugin/otel-data/src/test/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilderTests.java (1)

40-333: Recommended: add unit coverage for span_id / trace_id emission.

The new addSpanId / addTraceId path is only exercised via callers in LogDocumentBuilder, but there's no unit test here that verifies (a) the hex encoding and (b) that the fields are omitted when the respective ByteString is empty. A small test with a non-empty setSpanId(ByteString.copyFrom(...)) / setTraceId(...) plus a negative assertion when absent would close this gap.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@x-pack/plugin/otel-data/src/test/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilderTests.java`
around lines 40 - 333, Add unit tests in LogDocumentBuilderTests that construct
LogRecord instances with non-empty span_id and trace_id bytes (use
ByteString.copyFrom with known bytes), call buildDocument(...) and assert the
emitted fields (e.g., "trace.id" and "span.id") contain the expected hex-encoded
strings; also add cases where
setSpanId(ByteString.EMPTY)/setTraceId(ByteString.EMPTY) are used and assert
those fields are omitted (null) from the ObjectPath. Locate test helpers
buildDocument(Resource, InstrumentationScope, LogRecord) and the class under
test LogDocumentBuilder to plug the new assertions; ensure assertions check hex
encoding exactly and negative assertions for absence.
x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/OTelDocumentBuilder.java (1)

129-139: Minor: cache HexFormat.of() as a constant and consider consolidating the two helpers.

HexFormat.of() is invoked on every call; while it returns a cached instance internally, binding it to a private static final HexFormat HEX = HexFormat.of(); is idiomatic and avoids repeated method dispatch on hot paths. addSpanId and addTraceId also differ only in the field name, so they could be reduced to a single helper.

♻️ Proposed refactor
+    private static final HexFormat HEX = HexFormat.of();
+
     protected void addSpanId(XContentBuilder builder, byte[] spanId) throws IOException {
-        if (spanId.length > 0) {
-            builder.field("span_id", HexFormat.of().formatHex(spanId));
-        }
+        addHexIdIfNotEmpty(builder, "span_id", spanId);
     }
 
     protected void addTraceId(XContentBuilder builder, byte[] traceId) throws IOException {
-        if (traceId.length > 0) {
-            builder.field("trace_id", HexFormat.of().formatHex(traceId));
-        }
+        addHexIdIfNotEmpty(builder, "trace_id", traceId);
+    }
+
+    private static void addHexIdIfNotEmpty(XContentBuilder builder, String field, byte[] id) throws IOException {
+        if (id.length > 0) {
+            builder.field(field, HEX.formatHex(id));
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/OTelDocumentBuilder.java`
around lines 129 - 139, Cache HexFormat.of() as a class constant and consolidate
the two near-identical helpers: add a private static final HexFormat HEX =
HexFormat.of() in OTelDocumentBuilder, replace HexFormat.of().formatHex(...)
calls with HEX.formatHex(...), and collapse addSpanId/addTraceId into a single
helper (e.g., addHexId(XContentBuilder builder, byte[] id, String fieldName))
that both original methods call (or replace them directly) to write the field
name ("span_id" / "trace_id") only when id.length > 0.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilder.java`:
- Around line 82-86: The addLogTimestampField method writes a millisecond epoch
plus a sub-millisecond nanoseconds remainder without zero-padding, corrupting
timestamps; fix addLogTimestampField by formatting nanosRemainder as a
zero-padded 6-digit decimal fraction before calling builder.field(fieldName,
...), e.g. convert nanosRemainder to a 6-digit string (use
org.elasticsearch.common.Strings.padLeft or equivalent padding) and concatenate
millis + "." + paddedRemainder so values like 1 or 123 become "000001" /
"000123"; update imports to include org.elasticsearch.common.Strings and add a
unit test case in testTimestampPreservesSubMillisecondPrecision that asserts
behavior for remainders 1 and 123.
- Around line 134-171: The buildBody method in LogDocumentBuilder treats an
empty ARRAY_VALUE as structured (allMaps stays true) and writes an empty array;
change it to short-circuit empty arrays the same way VALUE_NOT_SET does so the
body object is omitted. Specifically, in buildBody (where you inspect AnyValue
body and valueCase ARRAY_VALUE), detect if body.getArrayValue().getValuesCount()
== 0 and return immediately before starting the "body" object; keep existing
logic for non-empty arrays (calling buildStructuredBody or wrapping primitives
with buildAnyValue) and unchanged paths for KVLIST_VALUE and default
(buildTextBody).

In
`@x-pack/plugin/otel-data/src/test/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilderTests.java`:
- Around line 250-260: Add a second assertion in
testTimestampPreservesSubMillisecondPrecision that uses a LogRecord whose
timeUnixNano has a sub-millisecond remainder with fewer than 6 digits (e.g., use
1_000_000_001L or 1_000_000_123L), build the document via buildDocument and
assert `@timestamp` formats with zero-padded micro/nan digits (e.g., "1000.000001"
or "1000.000123"); this exercises addLogTimestampField’s padding logic to ensure
sub-millisecond remainders are left-padded to six digits and observed_timestamp
remains "0.0".

---

Nitpick comments:
In
`@x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilder.java`:
- Around line 68-73: In LogDocumentBuilder, remove the redundant isEmpty()
checks around logRecord.getSpanId() and logRecord.getTraceId() and call
addSpanId(builder, logRecord.getSpanId().toByteArray()) and addTraceId(builder,
logRecord.getTraceId().toByteArray()) unguarded; addSpanId/addTraceId already
ignore zero-length ids (length > 0), so this eliminates the unnecessary
double-guard and avoids an extra empty ByteString handling path while keeping
behavior unchanged.
- Around line 88-124: The log-specific helpers buildLogResource, buildLogScope,
and buildLogAttributes duplicate
OTelDocumentBuilder#buildResource/#buildScope/#buildAttributes; refactor by
changing OTelDocumentBuilder#buildAttributes to accept an additional ignore
predicate (e.g., Predicate<String> or IntPredicate for attribute keys) and to do
lazy-opening of the "attributes" object, then have buildResource/buildScope call
this generalized buildAttributes (passing a no-op predicate for non-log callers)
and replace buildLogResource/buildLogScope to delegate to the base
implementations (or remove them); update any callers to pass the log-specific
ignore predicate (isIgnoredLogAttribute) so the log-only filtering behavior is
preserved without duplicating structure.

In
`@x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/OTelDocumentBuilder.java`:
- Around line 129-139: Cache HexFormat.of() as a class constant and consolidate
the two near-identical helpers: add a private static final HexFormat HEX =
HexFormat.of() in OTelDocumentBuilder, replace HexFormat.of().formatHex(...)
calls with HEX.formatHex(...), and collapse addSpanId/addTraceId into a single
helper (e.g., addHexId(XContentBuilder builder, byte[] id, String fieldName))
that both original methods call (or replace them directly) to write the field
name ("span_id" / "trace_id") only when id.length > 0.

In
`@x-pack/plugin/otel-data/src/test/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilderTests.java`:
- Around line 40-333: Add unit tests in LogDocumentBuilderTests that construct
LogRecord instances with non-empty span_id and trace_id bytes (use
ByteString.copyFrom with known bytes), call buildDocument(...) and assert the
emitted fields (e.g., "trace.id" and "span.id") contain the expected hex-encoded
strings; also add cases where
setSpanId(ByteString.EMPTY)/setTraceId(ByteString.EMPTY) are used and assert
those fields are omitted (null) from the ObjectPath. Locate test helpers
buildDocument(Resource, InstrumentationScope, LogRecord) and the class under
test LogDocumentBuilder to plug the new assertions; ensure assertions check hex
encoding exactly and negative assertions for absence.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Enterprise

Run ID: 68bb2107-b8a9-458e-91a7-1d85ae4e6190

📥 Commits

Reviewing files that changed from the base of the PR and between 048b269 and f276568.

📒 Files selected for processing (4)
  • x-pack/plugin/otel-data/src/javaRestTest/java/org/elasticsearch/xpack/oteldata/otlp/OTLPLogsIndexingRestIT.java
  • x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilder.java
  • x-pack/plugin/otel-data/src/main/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/OTelDocumentBuilder.java
  • x-pack/plugin/otel-data/src/test/java/org/elasticsearch/xpack/oteldata/otlp/docbuilder/LogDocumentBuilderTests.java

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

❌ Failed to clone repository into sandbox. Please try again.

@felixbarny felixbarny mentioned this pull request Apr 24, 2026
3 tasks
Preserve sub-millisecond log timestamp precision and omit empty
array bodies so OTLP log documents stay aligned with upstream
serialization behavior.
Copy link
Copy Markdown
Member

@kkrik-es kkrik-es left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe someone familiar with OTel Logs can review as well?

Use the common OTLP document builder to filter shared control
attributes so logs and future signals serialize them consistently.
@felixbarny felixbarny requested a review from axw April 28, 2026 07:46
Share timestamp formatting between log and span builders,
use numeric epoch millis when possible, and serialize
unspecified span kind explicitly.
Copy link
Copy Markdown
Member

@axw axw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@felixbarny felixbarny merged commit a59ce53 into elastic:main Apr 29, 2026
37 checks passed
@felixbarny felixbarny deleted the otlp-logs-align-otel-mode branch April 29, 2026 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contributor Pull request authored by a developer outside the Elasticsearch team >non-issue :StorageEngine/Logs You know, for Logs Team:StorageEngine v9.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants