Repository navigation
Keep the trace and span an OTLP log was written in, and query logs by an OTLP or Zipkin span - #14213
Merged
Merged
Conversation
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
5 open issues
Log.toJson()always emitsotlpSpanId, whileLogBuilder.outputToJson()only emits it when… · New The E2E case relies on a fixedsleep 5, which makes the test timing-sensitive and can be flaky… · New The comment states OTLP/Zipkinspan_idis 16 lowercase hex characters, but the storage column… · New Import ordering is inconsistent here (com.google...is placed betweenio.opentelemetry...… · NewidOfis only used insideOpenTelemetryLogHandlerin the shown diff, but it is package-visible… · New
What changed in this PR
This PR preserves OTLP/Zipkin trace/span context on ingested OTLP logs and enables querying logs by relatedTrace { traceId, otlpSpanId } across BanyanDB/Elasticsearch/JDBC, with accompanying docs and test coverage.
Changes:
- Store OTLP log record
trace_idon logs and persist OTLP/Zipkinspan_idin a newotlp_span_idfield; propagate via LAL output fields. - Add log query filtering by
TraceScopeCondition.otlpSpanIdin JDBC/ES/BanyanDB and expose it via the debugging query API. - Add unit + E2E coverage, plus documentation and upgrade notes.
| File | Description |
|---|---|
| test/e2e-v2/cases/otlp-traces/native/mysql/e2e.yaml | Includes shared log-cases.yaml in MySQL native OTLP trace E2E verification. |
| test/e2e-v2/cases/otlp-traces/native/es/e2e.yaml | Includes shared log-cases.yaml in ES native OTLP trace E2E verification. |
| test/e2e-v2/cases/otlp-traces/native/banyandb/e2e.yaml | Includes shared log-cases.yaml in BanyanDB native OTLP trace E2E verification. |
| test/e2e-v2/cases/otlp-traces/native/log-cases.yaml | Adds E2E case that posts an OTLP/JSON log and queries it by relatedTrace.otlpSpanId. |
| test/e2e-v2/cases/otlp-traces/native/expected/logs-by-otlp-span.yml | Adds expected output for querying logs by OTLP span ID. |
| oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/main/java/.../JDBCLogQueryDAO.java | Adds SQL filter for otlp_span_id in JDBC log queries. |
| oap-server/server-storage-plugin/storage-jdbc-hikaricp-plugin/src/test/java/.../JDBCLogQueryDAOTest.java | Adds unit test ensuring JDBC SQL includes otlp_span_id predicate. |
| oap-server/server-storage-plugin/storage-elasticsearch-plugin/src/main/java/.../LogQueryEsDAO.java | Adds ES term filter for otlpSpanId. |
| oap-server/server-storage-plugin/storage-banyandb-plugin/src/main/java/.../BanyanDBLogQueryDAO.java | Adds BanyanDB query condition for otlpSpanId. |
| oap-server/server-receiver-plugin/receiver-proto/src/main/java/.../OTLPValues.java | Updates documentation to include log bodies for hex->base64 ID rewriting. |
| oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/.../OpenTelemetryLogHTTPHandler.java | Converts OTLP/JSON hex IDs to base64 before protobuf JSON parsing. |
| oap-server/server-receiver-plugin/otel-receiver-plugin/src/main/java/.../OpenTelemetryLogHandler.java | Preserves OTLP log record trace/span IDs and normalizes trace ID for Zipkin trace storage. |
| oap-server/server-receiver-plugin/otel-receiver-plugin/src/test/java/.../OpenTelemetryLogHandlerTraceContextTest.java | Adds unit tests covering trace/span ID propagation and OTLP/JSON path behavior. |
| oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol | Bumps query-protocol submodule for otlpSpanId support. |
| oap-server/server-core/src/main/java/.../source/LogMetadata.java | Extends log trace context with otlpSpanId. |
| oap-server/server-core/src/main/java/.../source/LogBuilder.java | Adds otlpSpanId as a default output field and includes it in output JSON/log mapping. |
| oap-server/server-core/src/main/java/.../source/Log.java | Emits otlpSpanId in JSON serialization. |
| oap-server/server-core/src/main/java/.../source/AbstractLog.java | Adds otlpSpanId field to log source model. |
| oap-server/server-core/src/main/java/.../query/input/TraceScopeCondition.java | Adds otlpSpanId to log trace scope query condition. |
| oap-server/server-core/src/main/java/.../analysis/manual/log/LogRecordDispatcher.java | Propagates otlpSpanId from Log source to storage record. |
| oap-server/server-core/src/main/java/.../analysis/manual/log/LogRecord.java | Enables allowBootReshape for log record stream. |
| oap-server/server-core/src/main/java/.../analysis/manual/log/AbstractLogRecord.java | Adds otlp_span_id column and maps it in/out of storage. |
| oap-server/server-admin/status/src/main/java/.../DebuggingHTTPHandler.java | Adds otlpSpanId parameter to debugging log query endpoint. |
| oap-server/analyzer/log-analyzer/src/main/java/.../LALCodegenHelper.java | Exposes log.traceContext.otlpSpanId to LAL scripts. |
| oap-server/analyzer/log-analyzer/src/test/java/.../LALScriptExecutionTest.java | Injects otlpSpanId into metadata to test LAL read/write behavior. |
| oap-server/analyzer/log-analyzer/src/test/resources/.../execution-basic.yaml | Adds LAL feature cases for reading/writing otlpSpanId. |
| oap-server/analyzer/log-analyzer/src/test/resources/.../execution-basic.data.yaml | Adds input/expectations for new LAL otlpSpanId cases. |
| docs/en/setup/backend/log-otlp.md | Documents storage/query semantics for OTLP trace/span IDs on logs. |
| docs/en/debugging/query-tracing.md | Documents otlpSpanId parameter in debugging log query API. |
| docs/en/concepts-and-designs/lal.md | Documents otlpSpanId extractor/output field usage. |
| docs/en/changes/changes.md | Adds upgrade/breaking change note for JDBC schema change and new capability. |
Review effort: Lite
| obj.addProperty("traceId", getTraceId()); | ||
| obj.addProperty("traceSegmentId", getTraceSegmentId()); | ||
| obj.addProperty("spanId", getSpanId()); | ||
| obj.addProperty("otlpSpanId", getOtlpSpanId()); |
Comment on lines
+23
to
+31
| sleep 5 | ||
| logs_of_span() { | ||
| curl -s -X POST http://${oap_host}:${oap_12800}/graphql -H 'Content-Type: application/json' \ | ||
| -d '{"query":"query($c: LogQueryCondition){ queryLogs(condition: $c){ logs { serviceName traceId content } } }","variables":{"c":{"relatedTrace":{"traceId":"0af7651916cd43dd8448eb211c80319c","otlpSpanId":"'"$1"'"},"paging":{"pageNum":1,"pageSize":10}}}}' | ||
| } | ||
| jq -n --argjson span "$(logs_of_span b7ad6b7169203331)" --argjson other "$(logs_of_span 00f067aa0ba902b7)" \ | ||
| '{errors: ((($span.errors // []) + ($other.errors // [])) | length), | ||
| bySpanCount: (($span.data.queryLogs.logs // []) | length), bySpan: ($span.data.queryLogs.logs // []), | ||
| byOtherSpan: (($other.data.queryLogs.logs // []) | length)}' | yq -P |
| */ | ||
| @Setter | ||
| @Getter | ||
| @Column(name = OTLP_SPAN_ID, length = 64) |
Comment on lines
24
to
28
| import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse; | ||
| import io.opentelemetry.proto.collector.logs.v1.LogsServiceGrpc; | ||
| import io.opentelemetry.proto.common.v1.KeyValue; | ||
| import com.google.protobuf.ByteString; | ||
| import io.opentelemetry.proto.logs.v1.LogRecord; |
Comment on lines
+198
to
+205
| static String idOf(final ByteString id) { | ||
| for (int i = 0; i < id.size(); i++) { | ||
| if (id.byteAt(i) != 0) { | ||
| return OTLPValues.hexId(id); | ||
| } | ||
| } | ||
| return ""; | ||
| } |
Copilot stopped reviewing on behalf of
wu-sheng due to an error
November 13, 2026 10:20
wu-sheng
force-pushed
the
log-otlp-span-id
branch
from
November 13, 2026 10:49
c8da427 to
499f42b
Compare
wankai123
previously approved these changes
Nov 13, 2026
… an OTLP or Zipkin span The OTLP log receiver dropped a record's own trace_id and span_id, so an OTLP log could not be found from its trace. It now stores the trace ID as the log's trace ID and the span ID in a new otlp_span_id column: the numeric span_id column is a SkyWalking span's number within its segment and cannot hold a 16-hex OTLP or Zipkin span ID. TraceScopeCondition.otlpSpanId (apache/skywalking-query-protocol#181) reads the logs one OTLP or Zipkin span wrote, filtered in the BanyanDB, Elasticsearch and JDBC DAOs and the debugging query API. A related trace is traceId alone, traceId with segmentId and spanId, or traceId with otlpSpanId; any other mix is refused, including a segment or a span number alone, which were accepted before. LAL sets it with the otlpSpanId output field of the default output type and reads it as log.traceContext.otlpSpanId. OTLP/JSON log IDs are converted from hex before parsing, and with otlpTraceStorage: zipkin a trace ID whose upper 64 bits are zero is written in Zipkin's 16-character form. An existing BanyanDB log stream gains the tag at boot (allowBootReshape); JDBC log tables need the column added by hand, recorded as a breaking change.
wu-sheng
force-pushed
the
log-otlp-span-id
branch
from
November 13, 2026 13:15
499f42b to
f96e328
Compare
wankai123
approved these changes
Nov 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Keep the trace and span an OTLP log was written in, and query logs by an OTLP or Zipkin span
The OTLP log receiver dropped a record's own
trace_idandspan_id, so an OTLP log could not be found from its trace. It now stores both: the trace ID as the log's trace ID, and the span ID in a newotlp_span_idcolumn. The numericspan_idcolumn is a SkyWalking span's number within its segment and cannot hold a 16-hex OTLP or Zipkin span ID, hence the new column.Query:
TraceScopeCondition.otlpSpanId(Add otlpSpanId to TraceScopeCondition for the logs of an OTLP or Zipkin span skywalking-query-protocol#181, merged; the submodule points at966fb2c) reads the logs one OTLP or Zipkin span wrote. BanyanDB, Elasticsearch and JDBC DAOs filter by it; the debugging query API takes it too.relatedTraceis one of three forms:traceIdalone,traceId+segmentId+spanIdfor a native span, ortraceId+otlpSpanIdfor an OTLP or Zipkin span.LogQueryServicerefuses any other mix before storage is asked — a native span mixed with an OTLP one, and alsosegmentIdwithoutspanIdorspanIdwithoutsegmentId, which were accepted before (noted inchanges.md). An empty condition, which the LogQL handler passes when the query has no trace ID label, stays "no trace filter". The debugging query API refusessegmentId,spanIdorotlpSpanIdwithouttraceId, which it ignored before.LAL:
otlpSpanIdis an output field of the default output type (LogBuilder.setOtlpSpanId), so a rule can set it from parsed text, e.g. a Zipkin logging context;log.traceContext.otlpSpanIdreads it.OTLP/HTTP JSON logs: IDs are hex in OTLP/JSON, so the log JSON handler converts them with
OTLPValues.hexIdsToBase64before parsing, as the trace JSON handler already does. An absent or all-zero ID is no ID.With
otlpTraceStorage: zipkin, a trace ID whose upper 64 bits are zero is written as its lower 16 hex characters, the form the Zipkin span of that trace is stored with.Upgrade:
LogRecordopts intoallowBootReshape, so an existing BanyanDB log stream gains the tag at boot; Elasticsearch adds the field to the mapping. Breaking Change for JDBC (inchanges.md): only the current day's log table gains the column, so existing log tables needALTER TABLE log_<yyyyMMdd> ADD COLUMN otlp_span_id VARCHAR(64);or dropping.If this is non-trivial feature, paste the links/URLs to the design doc.
Update the documentation to include this new feature.
Tests(including UT, IT, E2E) are added to verify the new feature.
LogQueryServicereads the three forms and the empty condition, and refuses every other mix (both span kinds, half a native span, any span field withouttraceId) without asking storage; the OTLP handler stores the record's trace and span IDs (gRPC and OTLP/JSON paths), treats absent / all-zero IDs as none without touching the shared empty context, and writes a 64-bit trace ID per trace storage; LAL reads and writesotlpSpanId; the JDBC SQL carries the filter.otlp-traces/native(BanyanDB, Elasticsearch, MySQL) posts an OTLP/JSON log with a fixed trace ID and span ID, reads it back byrelatedTrace { traceId, otlpSpanId }, finds nothing for another span of the same trace, and gets an error for a condition naming an OTLP span and a native span at once. Run locally with an image built from this branch: the read and other-span checks passed on all three storages, and the refusal check, added last, on BanyanDB (it is decided before any storage is asked). Against the image before this change the case fails (the field is rejected).logstream was reshaped at boot (otlp_span_idadded), the earlier logs stayed readable, and new OTLP logs were found by their span through Horizon UI.If it's UI related, attach the screenshots below.
If this pull request closes/resolves/fixes an existing issue, replace the issue number. Closes #.
Update the
CHANGESlog.