Skip to content

feat: correlate logs with real OpenTelemetry traces - #439

Closed
helleengebakken wants to merge 7 commits into
mainfrom
feat/tracing-vars
Closed

feat: correlate logs with real OpenTelemetry traces#439
helleengebakken wants to merge 7 commits into
mainfrom
feat/tracing-vars

Conversation

@helleengebakken

@helleengebakken helleengebakken commented Aug 4, 2026

Copy link
Copy Markdown

Correlate logs with real OpenTelemetry traces, not fake correlation-id ones

StackdriverOpenTelemetryTraceMdcJsonProvider was 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 variable traceID and spanID, not trace_id and span_id which is OpenTelemetry's naming convention - so it would not find the real traces. In addition, GcpTraceFilter and OrderedTraceIdGrpcMdcContextServerInterceptor unconditionally 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)

  • Reads trace_id/span_id (the OpenTelemetry Java agent's default MDC keys) instead of the old traceId.
  • Falls back to traceId/spanId (Micrometer Tracing's convention) when the snake_case keys aren't present, since this is a shared library and not every consumer will have instrumented via the agent.
  • Writes the fully-qualified logging.googleapis.com/trace / logging.googleapis.com/spanId field names (previously just trace), and formats the trace value as projects//traces/ as GCP requires for the Logs Explorer trace link to actually resolve.
  • Resolves the project id from GOOGLE_CLOUD_PROJECT, falling back to GCP_PROJECT_ID; a blank env var is treated as unset rather than producing projects//traces/....

OrderedTraceIdGrpcMdcContextServerInterceptor (gcp/correlation-id-trace-spring-boot-gcp-grpc)

  • Now defers to a real trace/span already established by OpenTelemetry before falling back to the correlation-id-derived one. Checks the classic SLF4J MDC directly (trace_id/traceId, span_id/spanId) rather than GrpcMdcContext. GrpcMdcContext is a separate store built on io.grpc.Context for surviving gRPC's thread-hopping, and OTel never writes into it, so checking it wouldn't have detected a real trace at all.

GcpTraceFilter (gcp/correlation-id-trace-spring-boot-gcp-web)

  • Same fix for the HTTP path: if a real trace already exists in MDC, the filter does nothing at all, leaving StackdriverOpenTelemetryTraceMdcJsonProvider to 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.
  • New OrderedTraceIdGrpcMdcContextServerInterceptorTest and GcpTraceFilterTest: 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.

Copilot AI lite review requested due to automatic review settings August 4, 2026 10:49
@helleengebakken
helleengebakken requested review from a team as code owners August 4, 2026 10:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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_id and span_id instead of camelCase variants.
  • Emit logging.googleapis.com/trace using the projects/{projectId}/traces/{traceId} format (when project id is available).
  • Emit logging.googleapis.com/spanId from the OpenTelemetry span id.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@helleengebakken helleengebakken changed the title feat: from camelCase to snake_case, and setting spanId. feat: correlate logs with real OpenTelemetry traces Aug 6, 2026
@helleengebakken
helleengebakken requested a lite review from Copilot August 6, 2026 10:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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].

@skjolber skjolber mentioned this pull request Aug 14, 2026
@skjolber

Copy link
Copy Markdown
Contributor

continues in #450

@skjolber skjolber closed this Aug 18, 2026
@helleengebakken
helleengebakken deleted the feat/tracing-vars branch September 4, 2026 06:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants