diff --git a/gcp/logback-logstash-encoder-gcp/src/main/java/no/entur/logging/cloud/gcp/logback/logstash/StackdriverLogstashEncoder.java b/gcp/logback-logstash-encoder-gcp/src/main/java/no/entur/logging/cloud/gcp/logback/logstash/StackdriverLogstashEncoder.java index 7026f3ba..d089b6d3 100644 --- a/gcp/logback-logstash-encoder-gcp/src/main/java/no/entur/logging/cloud/gcp/logback/logstash/StackdriverLogstashEncoder.java +++ b/gcp/logback-logstash-encoder-gcp/src/main/java/no/entur/logging/cloud/gcp/logback/logstash/StackdriverLogstashEncoder.java @@ -63,13 +63,43 @@ protected AbstractCompositeJsonFormatter createFormatter() { return formatter; } + private static final String OTEL_GCP_PROJECT_ID_ATTRIBUTE = "gcp.project_id"; + + /** + * Resolves the GCP project that traces are stored in, so it can be embedded in the + * 'projects/{projectId}/traces/{traceId}' value written to the log entry. + * + * On Kubernetes, traces are exported to the cluster's host project (ent-kub-), + * which differs from the workload's own project. That destination project is carried in the + * 'gcp.project_id' OpenTelemetry resource attribute, so it takes precedence. Runtimes that + * still export traces to their own project (e.g. Cloud Run, Firebase) fall back to + * 'GOOGLE_CLOUD_PROJECT'. + */ private static String resolveProjectId() { - for (String envName : new String[]{"GOOGLE_CLOUD_PROJECT", "GCP_PROJECT_ID"}) { - String projectId = System.getenv(envName); - if (projectId != null && !projectId.isBlank()) { - return projectId; - } - } - return null; + String projectId = resolveOtelResourceAttribute(OTEL_GCP_PROJECT_ID_ATTRIBUTE); + if (projectId != null) { + return projectId; + } + String fallback = System.getenv("GOOGLE_CLOUD_PROJECT"); + return (fallback == null || fallback.isBlank()) ? null : fallback; } + + private static String resolveOtelResourceAttribute(String attributeName) { + String otelResourceAttributes = System.getenv("OTEL_RESOURCE_ATTRIBUTES"); + if (otelResourceAttributes == null || otelResourceAttributes.isBlank()) { + return null; + } + for (String pair : otelResourceAttributes.split(",")) { + int separatorIndex = pair.indexOf('='); + if (separatorIndex < 0) { + continue; + } + if (pair.substring(0, separatorIndex).trim().equals(attributeName)) { + String value = pair.substring(separatorIndex + 1).trim(); + return value.isBlank() ? null : value; + } + } + return null; + } + }