feat: correlate logs with real OpenTelemetry traces - #439
feat: correlate logs with real OpenTelemetry traces#439helleengebakken wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the GCP Logback Logstash encoder’s OpenTelemetry MDC handling to align log trace correlation fields with the OpenTelemetry Java Agent (snake_case MDC keys) and GCP’s expected logging.googleapis.com/* structured logging fields.
Changes:
- Read OpenTelemetry MDC keys
trace_idandspan_idinstead of camelCase variants. - Emit
logging.googleapis.com/traceusing theprojects/{projectId}/traces/{traceId}format (when project id is available). - Emit
logging.googleapis.com/spanIdfrom the OpenTelemetry span id.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (1)
gcp/logback-logstash-encoder-gcp/src/main/java/no/entur/logging/cloud/gcp/logback/logstash/StackdriverOpenTelemetryTraceMdcJsonProvider.java:13
- The Javadoc says this provider "renames" trace_id/span_id to the GCP fields, but the implementation also emits the original MDC entries in the later loop (so it effectively copies to logging.googleapis.com/*). Rewording the Javadoc will better match the actual behavior and avoid confusion for consumers.
* A simple MDC provider. Renames MDC field name trace_id and span_id to logging.googleapis.com/trace and logging.googleapis.com/spanId.
* Falls back to traceId/spanId (Micrometer Tracing's MDC convention) when the OpenTelemetry Java agent's
* snake_case keys are not present, since consumers of this library are not required to use the agent.
| traceId = mdcProperties.get("traceId"); | ||
| } | ||
| if(mdcProperties.get("logging.googleapis.com/trace") == null && traceId != null) { | ||
| String traceValue = projectId != null ? "projects/" + projectId + "/traces/" + traceId : traceId; |
There was a problem hiding this comment.
chore: As far I have understood per https://docs.cloud.google.com/trace/docs/trace-log-integration this format got deprecated earlier this year (in December 2025 that same page only mentioned the legacy format: https://web.archive.org/web/20251206003827/https://docs.cloud.google.com/trace/docs/trace-log-integration) and we could simply use the traceId as the value instead of this "legacy format", as stated in documentation:
To associate a log entry with a trace, set the trace field in the LogEntry object:
- Preferred format: TRACE_ID
- Legacy format: projects/PROJECT_ID/traces/TRACE_ID
Good to note that the table on the page https://docs.cloud.google.com/logging/docs/agent/logging/configuration#special-fields still mentions only the legacy format, I suspect they forgot to update that one:
| JSON log field | LogEntry field | Cloud Logging agent function | Example value |
|---|---|---|---|
logging.googleapis.com/trace |
trace |
Resource name of the trace associated with the log entry if any. For more information, see trace on the LogEntry page. | "logging.googleapis.com/trace":"projects/my-projectid/traces/0679686673a"Note: If not writing to stdout or stderr, the value of this field should be formatted as projects/[PROJECT-ID]/traces/[TRACE-ID], so it can be used by the Logs Explorer and the Trace Viewer to group log entries and display them in line with traces. If autoformat_stackdriver_trace is true and [V] matches the format of ResourceTrace traceId the LogEntry trace field has the value projects/[PROJECT-ID]/traces/[V]. |
|
continues in #450 |
Correlate logs with real OpenTelemetry traces, not fake correlation-id ones
StackdriverOpenTelemetryTraceMdcJsonProviderwas renaming the wrong MDC key (traceId) to a wrong field name (trace, missing the logging.googleapis.com/ prefix and the required projects//traces/ resource path), so it never actually produced a working Cloud Trace link. As well as searching for the Micrometer tracing variabletraceIDandspanID, nottrace_idandspan_idwhich is OpenTelemetry's naming convention - so it would not find the real traces. In addition,GcpTraceFilterandOrderedTraceIdGrpcMdcContextServerInterceptorunconditionally stamp a correlation-id-derived fake trace into MDC on every request, so even once the JSON provider was fixed, those two would claim the logging.googleapis.com/trace field first and mask the real trace that OpenTelemetry had already established.What changed
StackdriverOpenTelemetryTraceMdcJsonProvider(gcp/logback-logstash-encoder-gcp)OrderedTraceIdGrpcMdcContextServerInterceptor(gcp/correlation-id-trace-spring-boot-gcp-grpc)GcpTraceFilter(gcp/correlation-id-trace-spring-boot-gcp-web)StackdriverOpenTelemetryTraceMdcJsonProviderto derive the correctly project-prefixed value from it.Testing
StackdriverOpenTelemetryTraceMdcJsonProviderTest: snake_case and camelCase MDC keys, project-id prefixing (present/absent/blank), and no-duplicate-field guards when a value is already set.OrderedTraceIdGrpcMdcContextServerInterceptorTestandGcpTraceFilterTest: verify the correlation-id fallback still fires when no real trace exists, and is skipped (no MDC/GrpcMdcContext writes) when one does.Out of scope and needs follow-up
This PR does not remove GcpTraceFilter or OrderedTraceIdGrpcMdcContextServerInterceptor, they're still needed as a fallback for services not yet instrumented with OpenTelemetry. Once the OTel Java agent rollout is verified end-to-end in Cloud Logging/Trace, retiring those two classes for good is the next step, already called out in the README roadmap.