Skip to content

Keep the trace and span an OTLP log was written in, and query logs by an OTLP or Zipkin span - #14213

Merged
wu-sheng merged 1 commit into
masterfrom
log-otlp-span-id
Nov 13, 2026
Merged

wu-sheng merged 1 commit into
masterfrom
log-otlp-span-id

Conversation

@wu-sheng

@wu-sheng wu-sheng commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

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_id and span_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 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, 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 at 966fb2c) reads the logs one OTLP or Zipkin span wrote. BanyanDB, Elasticsearch and JDBC DAOs filter by it; the debugging query API takes it too. relatedTrace is one of three forms: traceId alone, traceId + segmentId + spanId for a native span, or traceId + otlpSpanId for an OTLP or Zipkin span. LogQueryService refuses any other mix before storage is asked — a native span mixed with an OTLP one, and also segmentId without spanId or spanId without segmentId, which were accepted before (noted in changes.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 refuses segmentId, spanId or otlpSpanId without traceId, which it ignored before.

  • LAL: otlpSpanId is 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.otlpSpanId reads it.

  • OTLP/HTTP JSON logs: IDs are hex in OTLP/JSON, so the log JSON handler converts them with OTLPValues.hexIdsToBase64 before 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: LogRecord opts into allowBootReshape, so an existing BanyanDB log stream gains the tag at boot; Elasticsearch adds the field to the mapping. Breaking Change for JDBC (in changes.md): only the current day's log table gains the column, so existing log tables need ALTER 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.

    • Unit: LogQueryService reads the three forms and the empty condition, and refuses every other mix (both span kinds, half a native span, any span field without traceId) 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 writes otlpSpanId; the JDBC SQL carries the filter.
    • E2E: otlp-traces/native (BanyanDB, Elasticsearch, MySQL) posts an OTLP/JSON log with a fixed trace ID and span ID, reads it back by relatedTrace { 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).
    • Upgrade: booted this branch over a BanyanDB written by the previous OAP; the log stream was reshaped at boot (otlp_span_id added), 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 CHANGES log.

@wu-sheng
wu-sheng requested a balanced review from Copilot November 13, 2026 09:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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
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_id on logs and persist OTLP/Zipkin span_id in a new otlp_span_id field; propagate via LAL output fields.
  • Add log query filtering by TraceScopeCondition.otlpSpanId in 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 "";
}
@wu-sheng wu-sheng added this to the 11.2.0 milestone Nov 13, 2026
@wu-sheng wu-sheng added enhancement Enhancement on performance or codes logging Log collecting and analysis labels Nov 13, 2026
@wu-sheng
wu-sheng requested a review from wankai123 November 13, 2026 12:23
wankai123
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
wu-sheng merged commit edf43f3 into master Nov 13, 2026
482 of 487 checks passed
@wu-sheng
wu-sheng deleted the log-otlp-span-id branch November 13, 2026 14:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Enhancement on performance or codes logging Log collecting and analysis

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants