diff --git a/README.md b/README.md
index 68a888e3e..7ce685279 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ If you are using Maven without the BOM, add this to your dependencies:
If you are using Gradle 5.x or later, add this to your dependencies:
```Groovy
-implementation platform('com.google.cloud:libraries-bom:26.39.0')
+implementation platform('com.google.cloud:libraries-bom:26.40.0')
implementation 'com.google.cloud:google-cloud-logging'
```
diff --git a/google-cloud-logging/pom.xml b/google-cloud-logging/pom.xml
index a28ed7e14..0f4190bfa 100644
--- a/google-cloud-logging/pom.xml
+++ b/google-cloud-logging/pom.xml
@@ -17,6 +17,14 @@
google-cloud-logging
+
+ io.opentelemetry
+ opentelemetry-api
+
+
+ io.opentelemetry
+ opentelemetry-context
+
com.google.guava
guava
@@ -133,6 +141,23 @@
grpc-google-cloud-logging-v2
test
+
+
+ io.opentelemetry
+ opentelemetry-sdk
+ test
+
+
+ io.opentelemetry
+ opentelemetry-sdk-testing
+ test
+
+
+ io.opentelemetry
+ opentelemetry-sdk-trace
+ test
+
+
com.google.api
diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/Context.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/Context.java
index 109edfafc..3466ecd2c 100644
--- a/google-cloud-logging/src/main/java/com/google/cloud/logging/Context.java
+++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/Context.java
@@ -22,6 +22,8 @@
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
@@ -34,15 +36,19 @@ public class Context {
private static final Pattern W3C_TRACE_CONTEXT_FORMAT =
Pattern.compile(
"^00-(?!00000000000000000000000000000000)[0-9a-f]{32}-(?!0000000000000000)[0-9a-f]{16}-[0-9a-f]{2}$");
+ // Trace sampled flag for bit masking
+ // see https://www.w3.org/TR/trace-context/#trace-flags for details
+ private static final byte FLAG_SAMPLED = 1; // 00000001
private final HttpRequest request;
private final String traceId;
private final String spanId;
-
+ private final boolean traceSampled;
/** A builder for {@see Context} objects. */
public static final class Builder {
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
private String traceId;
private String spanId;
+ private boolean traceSampled;
Builder() {}
@@ -50,6 +56,7 @@ public static final class Builder {
this.requestBuilder = context.request.toBuilder();
this.traceId = context.traceId;
this.spanId = context.spanId;
+ this.traceSampled = context.traceSampled;
}
/** Sets the HTTP request. */
@@ -118,10 +125,18 @@ public Builder setSpanId(String spanId) {
return this;
}
+ /** Sets the boolean as trace sampled flag. */
+ @CanIgnoreReturnValue
+ public Builder setTraceSampled(boolean traceSampled) {
+ this.traceSampled = traceSampled;
+ return this;
+ }
+
/**
- * Sets the trace id and span id values by parsing the string which represents xCloud Trace
- * Context. The Cloud Trace Context is passed as {@code x-cloud-trace-context} header (can be in
- * Pascal case format). The string format is TRACE_ID/SPAN_ID;o=TRACE_TRUE
.
+ * Sets the trace id, span id and trace sampled flag values by parsing the string which
+ * represents xCloud Trace Context. The Cloud Trace Context is passed as {@code
+ * x-cloud-trace-context} header (can be in Pascal case format). The string format is
+ * TRACE_ID/SPAN_ID;o=TRACE_TRUE
.
*
* @see Cloud Trace header
* format.
@@ -129,6 +144,9 @@ public Builder setSpanId(String spanId) {
@CanIgnoreReturnValue
public Builder loadCloudTraceContext(String cloudTrace) {
if (cloudTrace != null) {
+ if (cloudTrace.indexOf("o=") >= 0) {
+ setTraceSampled(Iterables.get(Splitter.on("o=").split(cloudTrace), 1).equals("1"));
+ }
cloudTrace = Iterables.get(Splitter.on(';').split(cloudTrace), 0);
int split = cloudTrace.indexOf('/');
if (split >= 0) {
@@ -149,10 +167,11 @@ public Builder loadCloudTraceContext(String cloudTrace) {
}
/**
- * Sets the trace id and span id values by parsing the string which represents the standard W3C
- * trace context propagation header. The context propagation header is passed as {@code
- * traceparent} header. The method currently supports ONLY version {@code "00"}. The string
- * format is 00-TRACE_ID-SPAN_ID-FLAGS
. field of the {@code version-format} value.
+ * Sets the trace id, span id and trace sampled flag values by parsing the string which
+ * represents the standard W3C trace context propagation header. The context propagation header
+ * is passed as {@code traceparent} header. The method currently supports ONLY version {@code
+ * "00"}. The string format is 00-TRACE_ID-SPAN_ID-FLAGS
. field of the {@code
+ * version-format} value.
*
* @see traceparent header
@@ -171,7 +190,27 @@ public Builder loadW3CTraceParentContext(String traceParent) {
List fields = Splitter.on('-').splitToList(traceParent);
setTraceId(fields.get(1));
setSpanId(fields.get(2));
- // fields[3] contains flag(s)
+ boolean sampled = (Integer.parseInt(fields.get(3), 16) & FLAG_SAMPLED) == FLAG_SAMPLED;
+ setTraceSampled(sampled);
+ }
+ return this;
+ }
+
+ /**
+ * Sets the trace id, span id and trace sampled flag values by parsing detected OpenTelemetry
+ * span context.
+ *
+ * @see OpenTelemetry
+ * SpanContext.
+ */
+ @CanIgnoreReturnValue
+ public Builder loadOpenTelemetryContext() {
+ io.opentelemetry.context.Context currentContext = io.opentelemetry.context.Context.current();
+ SpanContext spanContext = Span.fromContext(currentContext).getSpanContext();
+ if (spanContext != null && spanContext.isValid()) {
+ setTraceId(spanContext.getTraceId());
+ setSpanId(spanContext.getSpanId());
+ setTraceSampled(spanContext.isSampled());
}
return this;
}
@@ -191,6 +230,7 @@ public Context build() {
}
this.traceId = builder.traceId;
this.spanId = builder.spanId;
+ this.traceSampled = builder.traceSampled;
}
public HttpRequest getHttpRequest() {
@@ -205,6 +245,10 @@ public String getSpanId() {
return this.spanId;
}
+ public boolean getTraceSampled() {
+ return this.traceSampled;
+ }
+
@Override
public int hashCode() {
return Objects.hash(request, traceId, spanId);
@@ -216,6 +260,7 @@ public String toString() {
.add("request", request)
.add("traceId", traceId)
.add("spanId", spanId)
+ .add("traceSampled", traceSampled)
.toString();
}
@@ -230,7 +275,8 @@ public boolean equals(Object obj) {
Context other = (Context) obj;
return Objects.equals(request, other.request)
&& Objects.equals(traceId, other.traceId)
- && Objects.equals(spanId, other.spanId);
+ && Objects.equals(spanId, other.spanId)
+ && Objects.equals(traceSampled, other.traceSampled);
}
/** Returns a builder for this object. */
diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/ContextHandler.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/ContextHandler.java
index 8af084f27..54b7b1854 100644
--- a/google-cloud-logging/src/main/java/com/google/cloud/logging/ContextHandler.java
+++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/ContextHandler.java
@@ -18,7 +18,17 @@
/** Class provides a per-thread storage of the {@see Context} instances. */
public class ContextHandler {
+
+ public enum ContextPriority {
+ NO_INPUT,
+ XCLOUD_HEADER,
+ W3C_HEADER,
+ OTEL_EXTRACTED
+ }
+
private static final ThreadLocal contextHolder = initContextHolder();
+ private static final ThreadLocal currentPriority =
+ ThreadLocal.withInitial(() -> ContextPriority.NO_INPUT);
/**
* Initializes the context holder to {@link InheritableThreadLocal} if {@link LogManager}
@@ -41,10 +51,45 @@ public Context getCurrentContext() {
}
public void setCurrentContext(Context context) {
- contextHolder.set(context);
+ setCurrentContext(context, ContextPriority.NO_INPUT);
+ }
+
+ public ContextPriority getCurrentContextPriority() {
+ return currentPriority.get();
+ }
+
+ /**
+ * Sets the context based on the priority. Overrides traceId, spanId and TraceSampled if the
+ * passed priority is higher. HttpRequest values will be retrieved and combined from existing
+ * context if HttpRequest in the new context is empty .
+ */
+ public void setCurrentContext(Context context, ContextPriority priority) {
+ if (priority != null && priority.compareTo(currentPriority.get()) >= 0 && context != null) {
+ Context.Builder combinedContextBuilder =
+ Context.newBuilder()
+ .setTraceId(context.getTraceId())
+ .setSpanId(context.getSpanId())
+ .setTraceSampled(context.getTraceSampled());
+ Context currentContext = getCurrentContext();
+
+ if (context.getHttpRequest() != null) {
+ combinedContextBuilder.setRequest(context.getHttpRequest());
+ }
+ // Combines HttpRequest from the existing context if HttpRequest in new context is empty.
+ else if (currentContext != null && currentContext.getHttpRequest() != null) {
+ combinedContextBuilder.setRequest(currentContext.getHttpRequest());
+ }
+
+ contextHolder.set(combinedContextBuilder.build());
+ currentPriority.set(priority);
+ }
}
public void removeCurrentContext() {
contextHolder.remove();
}
+
+ public void removeCurrentContextPriority() {
+ currentPriority.remove();
+ }
}
diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java
index 06108a303..d1e56762a 100644
--- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java
+++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingHandler.java
@@ -171,7 +171,7 @@ public enum LogTarget {
private final WriteOption[] defaultWriteOptions;
- /** Creates an handler that publishes messages to Cloud Logging. */
+ /** Creates a handler that publishes messages to Cloud Logging. */
public LoggingHandler() {
this(null, null, null);
}
diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java
index d1e3b0ae9..20bf4b507 100644
--- a/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java
+++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java
@@ -41,6 +41,7 @@
import com.google.cloud.MonitoredResourceDescriptor;
import com.google.cloud.PageImpl;
import com.google.cloud.Tuple;
+import com.google.cloud.logging.ContextHandler.ContextPriority;
import com.google.cloud.logging.spi.v2.LoggingRpc;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ascii;
@@ -89,6 +90,7 @@
import com.google.logging.v2.WriteLogEntriesResponse;
import com.google.protobuf.Empty;
import com.google.protobuf.util.Durations;
+import io.opentelemetry.api.trace.Span;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
@@ -822,7 +824,7 @@ public Iterable populateMetadata(
customResource == null
? MonitoredResourceUtil.getResource(getOptions().getProjectId(), null)
: customResource;
- final Context context = new ContextHandler().getCurrentContext();
+
final ArrayList populatedLogEntries = Lists.newArrayList();
// populate empty metadata fields of log entries before calling write API
@@ -834,6 +836,15 @@ public Iterable populateMetadata(
if (resourceMetadata != null && entry.getResource() == null) {
entityBuilder.setResource(resourceMetadata);
}
+
+ ContextHandler contextHandler = new ContextHandler();
+ // Populate trace/span ID from OpenTelemetry span context to logging context.
+ if (Span.current().getSpanContext().isValid()) {
+ Context.Builder contextBuilder = Context.newBuilder().loadOpenTelemetryContext();
+ contextHandler.setCurrentContext(contextBuilder.build(), ContextPriority.OTEL_EXTRACTED);
+ }
+
+ Context context = contextHandler.getCurrentContext();
if (context != null && entry.getHttpRequest() == null) {
entityBuilder.setHttpRequest(context.getHttpRequest());
}
@@ -841,6 +852,7 @@ public Iterable populateMetadata(
MonitoredResource resource =
entry.getResource() != null ? entry.getResource() : resourceMetadata;
entityBuilder.setTrace(getFormattedTrace(context.getTraceId(), resource));
+ entityBuilder.setTraceSampled(context.getTraceSampled());
}
if (context != null && Strings.isNullOrEmpty(entry.getSpanId())) {
entityBuilder.setSpanId(context.getSpanId());
diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/TraceLoggingEnhancer.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/TraceLoggingEnhancer.java
index 8b7b4aea7..834e3b735 100644
--- a/google-cloud-logging/src/main/java/com/google/cloud/logging/TraceLoggingEnhancer.java
+++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/TraceLoggingEnhancer.java
@@ -24,6 +24,8 @@ public TraceLoggingEnhancer() {}
public TraceLoggingEnhancer(String prefix) {}
private static final ThreadLocal traceId = new ThreadLocal<>();
+ private static final ThreadLocal spanId = new ThreadLocal<>();
+ private static final ThreadLocal traceSampled = new ThreadLocal();
/**
* Set the Trace ID associated with any logging done by the current thread.
@@ -38,20 +40,72 @@ public static void setCurrentTraceId(String id) {
}
}
+ /**
+ * Set the Span ID associated with any logging done by the current thread.
+ *
+ * @param id The spanID
+ */
+ public static void setCurrentSpanId(String id) {
+ if (id == null) {
+ spanId.remove();
+ } else {
+ spanId.set(id);
+ }
+ }
+
+ /**
+ * Set the trace sampled flag associated with any logging done by the current thread.
+ *
+ * @param isTraceSampled The traceSampled flag
+ */
+ public static void setCurrentTraceSampled(Boolean isTraceSampled) {
+ if (isTraceSampled == null) {
+ traceSampled.remove();
+ } else {
+ traceSampled.set(isTraceSampled);
+ }
+ }
+
/**
* Get the Trace ID associated with any logging done by the current thread.
*
- * @return id The traceID
+ * @return id The trace ID
*/
public static String getCurrentTraceId() {
return traceId.get();
}
+ /**
+ * Get the Span ID associated with any logging done by the current thread.
+ *
+ * @return id The span ID
+ */
+ public static String getCurrentSpanId() {
+ return spanId.get();
+ }
+
+ /**
+ * Get the trace sampled flag associated with any logging done by the current thread.
+ *
+ * @return traceSampled The traceSampled flag
+ */
+ public static Boolean getCurrentTraceSampled() {
+ return traceSampled.get();
+ }
+
@Override
public void enhanceLogEntry(LogEntry.Builder builder) {
String traceId = getCurrentTraceId();
if (traceId != null) {
builder.setTrace(traceId);
}
+ String spanId = getCurrentSpanId();
+ if (spanId != null) {
+ builder.setSpanId(spanId);
+ }
+ Boolean isTraceSampled = getCurrentTraceSampled();
+ if (isTraceSampled != null) {
+ builder.setTraceSampled(isTraceSampled);
+ }
}
}
diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/AutoPopulateMetadataTests.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/AutoPopulateMetadataTests.java
index f415f8c4c..7972ca7bf 100644
--- a/google-cloud-logging/src/test/java/com/google/cloud/logging/AutoPopulateMetadataTests.java
+++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/AutoPopulateMetadataTests.java
@@ -22,8 +22,7 @@
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.newCapture;
import static org.easymock.EasyMock.replay;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.*;
import com.google.api.core.ApiFutures;
import com.google.cloud.MonitoredResource;
@@ -74,6 +73,7 @@ public class AutoPopulateMetadataTests {
private static final String FORMATTED_TRACE_ID =
String.format(LoggingImpl.RESOURCE_NAME_FORMAT, RESOURCE_PROJECT_ID, TRACE_ID);
private static final String SPAN_ID = "1";
+ private static final boolean TRACE_SAMPLED = true;
private LoggingRpcFactory mockedRpcFactory;
private LoggingRpc mockedRpc;
@@ -111,15 +111,21 @@ public void teardown() {
new ContextHandler().removeCurrentContext();
}
- private void mockCurrentContext(HttpRequest request, String traceId, String spanId) {
+ private void mockCurrentContext(
+ HttpRequest request, String traceId, String spanId, boolean traceSampled) {
Context mockedContext =
- Context.newBuilder().setRequest(request).setTraceId(traceId).setSpanId(spanId).build();
+ Context.newBuilder()
+ .setRequest(request)
+ .setTraceId(traceId)
+ .setSpanId(spanId)
+ .setTraceSampled(traceSampled)
+ .build();
new ContextHandler().setCurrentContext(mockedContext);
}
@Test
public void testAutoPopulationEnabledInLoggingOptions() {
- mockCurrentContext(HTTP_REQUEST, TRACE_ID, SPAN_ID);
+ mockCurrentContext(HTTP_REQUEST, TRACE_ID, SPAN_ID, TRACE_SAMPLED);
logging.write(ImmutableList.of(SIMPLE_LOG_ENTRY));
@@ -127,6 +133,7 @@ public void testAutoPopulationEnabledInLoggingOptions() {
assertEquals(HTTP_REQUEST, actual.getHttpRequest());
assertEquals(FORMATTED_TRACE_ID, actual.getTrace());
assertEquals(SPAN_ID, actual.getSpanId());
+ assertEquals(TRACE_SAMPLED, actual.getTraceSampled());
assertEquals(RESOURCE, actual.getResource());
}
@@ -136,7 +143,7 @@ public void testAutoPopulationEnabledInWriteOptionsAndDisabledInLoggingOptions()
LoggingOptions options =
logging.getOptions().toBuilder().setAutoPopulateMetadata(false).build();
logging = options.getService();
- mockCurrentContext(HTTP_REQUEST, TRACE_ID, SPAN_ID);
+ mockCurrentContext(HTTP_REQUEST, TRACE_ID, SPAN_ID, TRACE_SAMPLED);
logging.write(ImmutableList.of(SIMPLE_LOG_ENTRY), WriteOption.autoPopulateMetadata(true));
@@ -144,12 +151,13 @@ public void testAutoPopulationEnabledInWriteOptionsAndDisabledInLoggingOptions()
assertEquals(HTTP_REQUEST, actual.getHttpRequest());
assertEquals(FORMATTED_TRACE_ID, actual.getTrace());
assertEquals(SPAN_ID, actual.getSpanId());
+ assertEquals(TRACE_SAMPLED, actual.getTraceSampled());
assertEquals(RESOURCE, actual.getResource());
}
@Test
public void testAutoPopulationDisabledInWriteOptions() {
- mockCurrentContext(HTTP_REQUEST, TRACE_ID, SPAN_ID);
+ mockCurrentContext(HTTP_REQUEST, TRACE_ID, SPAN_ID, TRACE_SAMPLED);
logging.write(ImmutableList.of(SIMPLE_LOG_ENTRY), WriteOption.autoPopulateMetadata(false));
@@ -157,6 +165,7 @@ public void testAutoPopulationDisabledInWriteOptions() {
assertNull(actual.getHttpRequest());
assertNull(actual.getTrace());
assertNull(actual.getSpanId());
+ assertFalse(actual.getTraceSampled());
assertNull(actual.getResource());
}
@@ -174,7 +183,7 @@ public void testSourceLocationPopulation() {
@Test
public void testNotFormattedTraceId() {
- mockCurrentContext(HTTP_REQUEST, TRACE_ID, SPAN_ID);
+ mockCurrentContext(HTTP_REQUEST, TRACE_ID, SPAN_ID, TRACE_SAMPLED);
final MonitoredResource expectedResource = MonitoredResource.newBuilder("custom").build();
@@ -186,7 +195,7 @@ public void testNotFormattedTraceId() {
@Test
public void testMonitoredResourcePopulationInWriteOptions() {
- mockCurrentContext(HTTP_REQUEST, TRACE_ID, SPAN_ID);
+ mockCurrentContext(HTTP_REQUEST, TRACE_ID, SPAN_ID, TRACE_SAMPLED);
final MonitoredResource expectedResource = MonitoredResource.newBuilder("custom").build();
diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/ContextHandlerTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/ContextHandlerTest.java
new file mode 100644
index 000000000..a47ef9a2b
--- /dev/null
+++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/ContextHandlerTest.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.logging;
+
+import static org.junit.Assert.*;
+
+import com.google.cloud.logging.ContextHandler.ContextPriority;
+import com.google.cloud.logging.HttpRequest.RequestMethod;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ContextHandlerTest {
+ private static final HttpRequest OLD_HTTP_REQUEST =
+ HttpRequest.newBuilder()
+ .setRequestMethod(RequestMethod.POST)
+ .setRequestUrl("https://old.com")
+ .setUserAgent("Test User Agent")
+ .build();
+ private static final HttpRequest HTTP_REQUEST =
+ HttpRequest.newBuilder()
+ .setRequestMethod(RequestMethod.GET)
+ .setRequestUrl("https://example.com")
+ .setUserAgent("Test User Agent")
+ .build();
+ private static final String OLD_TRACE_ID = "10100101010101010101010101010101";
+ private static final String OLD_SPAN_ID = "0";
+ private static final boolean OLD_TRACE_SAMPLED = false;
+ private static final String TRACE_ID = "01010101010101010101010101010101";
+ private static final String SPAN_ID = "1";
+ private static final boolean TRACE_SAMPLED = true;
+
+ @After
+ public void teardown() {
+ new ContextHandler().removeCurrentContext();
+ new ContextHandler().removeCurrentContextPriority();
+ }
+
+ @Test
+ public void testDefaultSetContext() {
+ Context newContext =
+ Context.newBuilder()
+ .setRequest(HTTP_REQUEST)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setTraceSampled(TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(newContext);
+ Context currentContext = new ContextHandler().getCurrentContext();
+ assertEquals(HTTP_REQUEST, currentContext.getHttpRequest());
+ assertEquals(TRACE_ID, currentContext.getTraceId());
+ assertEquals(SPAN_ID, currentContext.getSpanId());
+ assertEquals(TRACE_SAMPLED, currentContext.getTraceSampled());
+ assertEquals(ContextPriority.NO_INPUT, new ContextHandler().getCurrentContextPriority());
+ }
+
+ @Test
+ public void testSetContextWithPriorityFromNoInput() {
+ Context newContext =
+ Context.newBuilder()
+ .setRequest(HTTP_REQUEST)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setTraceSampled(TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(newContext, ContextPriority.NO_INPUT);
+ Context currentContext = new ContextHandler().getCurrentContext();
+ assertEquals(ContextPriority.NO_INPUT, new ContextHandler().getCurrentContextPriority());
+ assertEquals(HTTP_REQUEST, currentContext.getHttpRequest());
+ assertEquals(TRACE_ID, currentContext.getTraceId());
+ assertEquals(SPAN_ID, currentContext.getSpanId());
+ assertEquals(TRACE_SAMPLED, currentContext.getTraceSampled());
+ }
+
+ @Test
+ public void testSetContextWithPriorityFromW3CHeader() {
+ Context newContext =
+ Context.newBuilder()
+ .setRequest(HTTP_REQUEST)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setTraceSampled(TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(newContext, ContextPriority.W3C_HEADER);
+ Context currentContext = new ContextHandler().getCurrentContext();
+ assertEquals(ContextPriority.W3C_HEADER, new ContextHandler().getCurrentContextPriority());
+ assertEquals(HTTP_REQUEST, currentContext.getHttpRequest());
+ assertEquals(TRACE_ID, currentContext.getTraceId());
+ assertEquals(SPAN_ID, currentContext.getSpanId());
+ assertEquals(TRACE_SAMPLED, currentContext.getTraceSampled());
+ }
+
+ @Test
+ public void testSetContextFromXCloudHeader() {
+ Context newContext =
+ Context.newBuilder()
+ .setRequest(HTTP_REQUEST)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setTraceSampled(TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(newContext, ContextPriority.XCLOUD_HEADER);
+ Context currentContext = new ContextHandler().getCurrentContext();
+ assertEquals(ContextPriority.XCLOUD_HEADER, new ContextHandler().getCurrentContextPriority());
+ assertEquals(HTTP_REQUEST, currentContext.getHttpRequest());
+ assertEquals(TRACE_ID, currentContext.getTraceId());
+ assertEquals(SPAN_ID, currentContext.getSpanId());
+ assertEquals(TRACE_SAMPLED, currentContext.getTraceSampled());
+ }
+
+ @Test
+ public void testSetContextFromOpenTelemetry() {
+ Context newContext =
+ Context.newBuilder()
+ .setRequest(HTTP_REQUEST)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setTraceSampled(TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(newContext, ContextPriority.OTEL_EXTRACTED);
+ Context currentContext = new ContextHandler().getCurrentContext();
+ assertEquals(ContextPriority.OTEL_EXTRACTED, new ContextHandler().getCurrentContextPriority());
+ assertEquals(HTTP_REQUEST, currentContext.getHttpRequest());
+ assertEquals(TRACE_ID, currentContext.getTraceId());
+ assertEquals(SPAN_ID, currentContext.getSpanId());
+ assertEquals(TRACE_SAMPLED, currentContext.getTraceSampled());
+ }
+
+ @Test
+ public void testOverrideW3CContextFromOpenTelemetry() {
+ Context oldContext =
+ Context.newBuilder()
+ .setRequest(OLD_HTTP_REQUEST)
+ .setTraceId(OLD_TRACE_ID)
+ .setSpanId(OLD_SPAN_ID)
+ .setTraceSampled(OLD_TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(oldContext, ContextPriority.W3C_HEADER);
+ Context newContext =
+ Context.newBuilder()
+ .setRequest(HTTP_REQUEST)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setTraceSampled(TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(newContext, ContextPriority.OTEL_EXTRACTED);
+ // Expects context being overridden when context was set with higher priority.
+ assertEquals(ContextPriority.OTEL_EXTRACTED, new ContextHandler().getCurrentContextPriority());
+ Context currentContext = new ContextHandler().getCurrentContext();
+ assertEquals(HTTP_REQUEST, currentContext.getHttpRequest());
+ assertEquals(TRACE_ID, currentContext.getTraceId());
+ assertEquals(SPAN_ID, currentContext.getSpanId());
+ assertEquals(TRACE_SAMPLED, currentContext.getTraceSampled());
+ }
+
+ @Test
+ public void testOverrideXCTCContextFromOpenTelemetry() {
+ Context oldContext =
+ Context.newBuilder()
+ .setRequest(OLD_HTTP_REQUEST)
+ .setTraceId(OLD_TRACE_ID)
+ .setSpanId(OLD_SPAN_ID)
+ .setTraceSampled(OLD_TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(oldContext, ContextPriority.XCLOUD_HEADER);
+
+ Context newContext =
+ Context.newBuilder()
+ .setRequest(HTTP_REQUEST)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setTraceSampled(TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(newContext, ContextPriority.OTEL_EXTRACTED);
+ // Expects context being overridden when context was set with higher priority.
+ assertEquals(ContextPriority.OTEL_EXTRACTED, new ContextHandler().getCurrentContextPriority());
+ Context currentContext = new ContextHandler().getCurrentContext();
+ assertEquals(HTTP_REQUEST, currentContext.getHttpRequest());
+ assertEquals(TRACE_ID, currentContext.getTraceId());
+ assertEquals(SPAN_ID, currentContext.getSpanId());
+ assertEquals(TRACE_SAMPLED, currentContext.getTraceSampled());
+ }
+
+ @Test
+ public void testOverrideOtelContextFromDefaultSetContext() {
+ Context oldContext =
+ Context.newBuilder()
+ .setRequest(OLD_HTTP_REQUEST)
+ .setTraceId(OLD_TRACE_ID)
+ .setSpanId(OLD_SPAN_ID)
+ .setTraceSampled(OLD_TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(oldContext, ContextPriority.OTEL_EXTRACTED);
+ Context newContext =
+ Context.newBuilder()
+ .setRequest(HTTP_REQUEST)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setTraceSampled(TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(newContext);
+ // Expects open telemetry context not being overridden when context was set with lower priority.
+ assertEquals(ContextPriority.OTEL_EXTRACTED, new ContextHandler().getCurrentContextPriority());
+ Context currentContext = new ContextHandler().getCurrentContext();
+ assertEquals(OLD_HTTP_REQUEST, currentContext.getHttpRequest());
+ assertEquals(OLD_TRACE_ID, currentContext.getTraceId());
+ assertEquals(OLD_SPAN_ID, currentContext.getSpanId());
+ assertEquals(OLD_TRACE_SAMPLED, currentContext.getTraceSampled());
+ }
+
+ @Test
+ public void testOverrideOtelContextFromW3C() {
+ Context oldContext =
+ Context.newBuilder()
+ .setRequest(OLD_HTTP_REQUEST)
+ .setTraceId(OLD_TRACE_ID)
+ .setSpanId(OLD_SPAN_ID)
+ .setTraceSampled(OLD_TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(oldContext, ContextPriority.OTEL_EXTRACTED);
+ Context newContext =
+ Context.newBuilder()
+ .setRequest(HTTP_REQUEST)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setTraceSampled(TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(newContext, ContextPriority.W3C_HEADER);
+ // Expects open telemetry context not being overridden when context was set with lower priority.
+ assertEquals(ContextPriority.OTEL_EXTRACTED, new ContextHandler().getCurrentContextPriority());
+ Context currentContext = new ContextHandler().getCurrentContext();
+ assertEquals(OLD_HTTP_REQUEST, currentContext.getHttpRequest());
+ assertEquals(OLD_TRACE_ID, currentContext.getTraceId());
+ assertEquals(OLD_SPAN_ID, currentContext.getSpanId());
+ assertEquals(OLD_TRACE_SAMPLED, currentContext.getTraceSampled());
+ }
+
+ @Test
+ public void testOverrideOtelContextFromXCTC() {
+ Context oldContext =
+ Context.newBuilder()
+ .setRequest(OLD_HTTP_REQUEST)
+ .setTraceId(OLD_TRACE_ID)
+ .setSpanId(OLD_SPAN_ID)
+ .setTraceSampled(OLD_TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(oldContext, ContextPriority.OTEL_EXTRACTED);
+ Context newContext =
+ Context.newBuilder()
+ .setRequest(HTTP_REQUEST)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setTraceSampled(TRACE_SAMPLED)
+ .build();
+ new ContextHandler().setCurrentContext(newContext, ContextPriority.XCLOUD_HEADER);
+ // Expects open telemetry context not being overridden when context was set with lower priority.
+ assertEquals(ContextPriority.OTEL_EXTRACTED, new ContextHandler().getCurrentContextPriority());
+ Context currentContext = new ContextHandler().getCurrentContext();
+ assertEquals(OLD_HTTP_REQUEST, currentContext.getHttpRequest());
+ assertEquals(OLD_TRACE_ID, currentContext.getTraceId());
+ assertEquals(OLD_SPAN_ID, currentContext.getSpanId());
+ assertEquals(OLD_TRACE_SAMPLED, currentContext.getTraceSampled());
+ }
+}
diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/ContextTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/ContextTest.java
index 512c99aa8..7ef8f90de 100644
--- a/google-cloud-logging/src/test/java/com/google/cloud/logging/ContextTest.java
+++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/ContextTest.java
@@ -21,6 +21,14 @@
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
+import io.opentelemetry.api.trace.*;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.SpanProcessor;
+import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -37,6 +45,7 @@ public class ContextTest {
// DO NOT use dash in trace and span id because W3C traceparent format uses dash as a delimieter
private static final String TEST_TRACE_ID = "test_trace_id";
private static final String TEST_SPAN_ID = "test_span_id";
+ private static final boolean TEST_TRACE_SAMPLED = true;
private static final HttpRequest REQUEST =
HttpRequest.newBuilder()
@@ -68,6 +77,7 @@ public class ContextTest {
.setRequest(PARTIAL_REQUEST)
.setTraceId(TEST_TRACE_ID)
.setSpanId(TEST_SPAN_ID)
+ .setTraceSampled(TEST_TRACE_SAMPLED)
.build();
@Test
@@ -87,6 +97,7 @@ public void testCompareContexts() {
.setServerIp(SERVER_IP)
.setTraceId(TEST_TRACE_ID)
.setSpanId(TEST_SPAN_ID)
+ .setTraceSampled(TEST_TRACE_SAMPLED)
.build();
assertNotEquals(TEST_CONTEXT, context1);
@@ -103,9 +114,11 @@ public void testContextBuilder() {
assertEquals(PARTIAL_REQUEST, TEST_CONTEXT.getHttpRequest());
assertEquals(TEST_TRACE_ID, TEST_CONTEXT.getTraceId());
assertEquals(TEST_SPAN_ID, TEST_CONTEXT.getSpanId());
+ assertEquals(TEST_TRACE_SAMPLED, TEST_CONTEXT.getTraceSampled());
assertNull(emptyContext.getHttpRequest());
assertNull(emptyContext.getTraceId());
assertNull(emptyContext.getSpanId());
+ assertFalse(emptyContext.getTraceSampled());
assertEquals(TEST_CONTEXT, anotherContext);
}
@@ -114,40 +127,78 @@ public void testParsingCloudTraceContext() {
final String X_CLOUD_TRACE_NO_TRACE = "/SPAN_ID;o=TRACE_TRUE";
final String X_CLOUD_TRACE_ONLY = TEST_TRACE_ID;
final String X_CLOUD_TRACE_WITH_SPAN = TEST_TRACE_ID + "/" + TEST_SPAN_ID;
- final String X_CLOUD_TRACE_FULL = TEST_TRACE_ID + "/" + TEST_SPAN_ID + ";o=TRACE_TRUE";
+ final String X_CLOUD_TRACE_FULL = TEST_TRACE_ID + "/" + TEST_SPAN_ID + ";o=1";
Context.Builder builder = Context.newBuilder();
builder.loadCloudTraceContext(null);
- assertTraceAndSpan(builder.build(), null, null);
+ assertTraceSpanAndSampled(builder.build(), null, null, false);
builder.loadCloudTraceContext("");
- assertTraceAndSpan(builder.build(), null, null);
+ assertTraceSpanAndSampled(builder.build(), null, null, false);
builder.loadCloudTraceContext(X_CLOUD_TRACE_NO_TRACE);
- assertTraceAndSpan(builder.build(), null, null);
+ assertTraceSpanAndSampled(builder.build(), null, null, false);
builder.loadCloudTraceContext(X_CLOUD_TRACE_ONLY);
- assertTraceAndSpan(builder.build(), TEST_TRACE_ID, null);
+ assertTraceSpanAndSampled(builder.build(), TEST_TRACE_ID, null, false);
builder.loadCloudTraceContext(X_CLOUD_TRACE_WITH_SPAN);
- assertTraceAndSpan(builder.build(), TEST_TRACE_ID, TEST_SPAN_ID);
+ assertTraceSpanAndSampled(builder.build(), TEST_TRACE_ID, TEST_SPAN_ID, false);
builder.loadCloudTraceContext(X_CLOUD_TRACE_FULL);
- assertTraceAndSpan(builder.build(), TEST_TRACE_ID, TEST_SPAN_ID);
+ assertTraceSpanAndSampled(builder.build(), TEST_TRACE_ID, TEST_SPAN_ID, TEST_TRACE_SAMPLED);
}
@Test
public void testParsingW3CTraceParent() {
final String W3C_TEST_TRACE_ID = "12345678901234567890123456789012";
final String W3C_TEST_SPAN_ID = "1234567890123456";
- final String W3C_TRACE_CONTEXT = "00-" + W3C_TEST_TRACE_ID + "-" + W3C_TEST_SPAN_ID + "-00";
+ final String W3C_TEST_TRACE_SAMPLED = "0f";
+ final String W3C_TRACE_CONTEXT =
+ "00-" + W3C_TEST_TRACE_ID + "-" + W3C_TEST_SPAN_ID + "-" + W3C_TEST_TRACE_SAMPLED;
Context.Builder builder = Context.newBuilder();
builder.loadW3CTraceParentContext(null);
- assertTraceAndSpan(builder.build(), null, null);
+ assertTraceSpanAndSampled(builder.build(), null, null, false);
builder.loadW3CTraceParentContext(W3C_TRACE_CONTEXT);
- assertTraceAndSpan(builder.build(), W3C_TEST_TRACE_ID, W3C_TEST_SPAN_ID);
+ assertTraceSpanAndSampled(builder.build(), W3C_TEST_TRACE_ID, W3C_TEST_SPAN_ID, true);
}
- private void assertTraceAndSpan(Context context, String expectedTraceId, String expectedSpanId) {
+ @Test
+ public void testParsingOpenTelemetryContext() {
+ InMemorySpanExporter testExporter = InMemorySpanExporter.create();
+ SpanProcessor inMemorySpanProcessor = SimpleSpanProcessor.create(testExporter);
+ OpenTelemetrySdk openTelemetrySdk =
+ OpenTelemetrySdk.builder()
+ .setTracerProvider(
+ SdkTracerProvider.builder().addSpanProcessor(inMemorySpanProcessor).build())
+ .buildAndRegisterGlobal();
+
+ Tracer tracer = openTelemetrySdk.getTracer("ContextTest");
+ Span otelSpan = tracer.spanBuilder("Example Span Attributes").startSpan();
+ SpanContext currentOtelContext;
+ Context.Builder builder = Context.newBuilder();
+ try (Scope scope = otelSpan.makeCurrent()) {
+ otelSpan.setAttribute("Attribute 1", "first attribute value");
+ currentOtelContext = otelSpan.getSpanContext();
+ builder.loadOpenTelemetryContext();
+ assertTraceSpanAndSampled(
+ builder.build(),
+ currentOtelContext.getTraceId(),
+ currentOtelContext.getSpanId(),
+ currentOtelContext.isSampled());
+ } catch (Throwable t) {
+ otelSpan.recordException(t);
+ throw t;
+ } finally {
+ otelSpan.end();
+ }
+ }
+
+ private void assertTraceSpanAndSampled(
+ Context context,
+ String expectedTraceId,
+ String expectedSpanId,
+ boolean expectedTraceSampled) {
assertEquals(expectedTraceId, context.getTraceId());
assertEquals(expectedSpanId, context.getSpanId());
+ assertEquals(expectedTraceSampled, context.getTraceSampled());
}
}
diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java
index c6267e22e..abd88053e 100644
--- a/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java
+++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/LoggingHandlerTest.java
@@ -165,6 +165,8 @@ public class LoggingHandlerTest {
.addLabel("levelName", "FINEST")
.addLabel("levelValue", String.valueOf(Level.FINEST.intValue()))
.setTrace("projects/projectId/traces/traceId")
+ .setSpanId("test_span_id")
+ .setTraceSampled(true)
.setTimestamp(123456789L)
.build();
private static final LogEntry DIAGNOSTIC_ENTRY =
@@ -454,6 +456,8 @@ public void testTraceEnhancedLogEntry() {
replay(options, logging);
LoggingEnhancer enhancer = new TraceLoggingEnhancer();
TraceLoggingEnhancer.setCurrentTraceId("projects/projectId/traces/traceId");
+ TraceLoggingEnhancer.setCurrentSpanId("test_span_id");
+ TraceLoggingEnhancer.setCurrentTraceSampled(true);
Handler handler =
new LoggingHandler(LOG_NAME, options, DEFAULT_RESOURCE, ImmutableList.of(enhancer));
handler.setLevel(Level.ALL);
diff --git a/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITTracingLogsTest.java b/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITTracingLogsTest.java
new file mode 100644
index 000000000..cdd52af39
--- /dev/null
+++ b/google-cloud-logging/src/test/java/com/google/cloud/logging/it/ITTracingLogsTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.logging.it;
+
+import static com.google.cloud.logging.testing.RemoteLoggingHelper.formatForTest;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.*;
+
+import com.google.cloud.MonitoredResource;
+import com.google.cloud.logging.*;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.logging.v2.LogName;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.SpanProcessor;
+import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
+import java.util.Iterator;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ITTracingLogsTest extends BaseSystemTest {
+
+ private static final String LOG_ID = formatForTest("test-write-log-entries-log");
+ private static final Payload.StringPayload STRING_PAYLOAD =
+ Payload.StringPayload.of("stringPayload");
+ private static final Payload.JsonPayload OTEL_PAYLOAD =
+ Payload.JsonPayload.of(ImmutableMap.of("jsonKey", "jsonValue"));
+
+ private static final MonitoredResource GLOBAL_RESOURCE =
+ MonitoredResource.newBuilder("global").build();
+ private static final MonitoredResource[] MONITORED_RESOURCES_IN_TEST =
+ new MonitoredResource[] {GLOBAL_RESOURCE};
+
+ private static final ContextHandler contextHandler = new ContextHandler();
+
+ private static final String W3C_TEST_TRACE_ID = "12345678901234567890123456789012";
+ private static final String W3C_TEST_SPAN_ID = "1234567890123456";
+ private static final String W3C_TEST_TRACE_SAMPLED = "0f";
+ private static final String W3C_TRACE_CONTEXT =
+ "00-" + W3C_TEST_TRACE_ID + "-" + W3C_TEST_SPAN_ID + "-" + W3C_TEST_TRACE_SAMPLED;
+
+ private static final String XCTC_TEST_TRACE_ID = "98765432101234569876543210123456";
+ private static final String XCTC_TEST_SPAN_ID = "9876543210123456";
+ private static final String X_CLOUD_TRACE_CONTEXT =
+ XCTC_TEST_TRACE_ID + "/" + XCTC_TEST_SPAN_ID + ";o=1";
+
+ private static String otelTraceId;
+ private static String otelSpanId;
+ private static boolean isSampled;
+ private static Tracer tracer;
+ private static LogEntry w3cEntry;
+ private static LogEntry xctcEntry;
+ private static LogEntry otelEntry;
+ private static LogName logName;
+
+ @BeforeClass
+ public static void prepareLogs() throws InterruptedException {
+ LoggingOptions loggingOptions = logging.getOptions();
+ logName = LogName.ofProjectLogName(loggingOptions.getProjectId(), LOG_ID);
+ logging.setWriteSynchronicity(Synchronicity.SYNC);
+ w3cEntry =
+ LogEntry.newBuilder(STRING_PAYLOAD)
+ .setLogName(LOG_ID)
+ .addLabel("tracing_source", "w3c")
+ .setHttpRequest(HttpRequest.newBuilder().setStatus(500).build())
+ .setResource(GLOBAL_RESOURCE)
+ .build();
+ xctcEntry =
+ LogEntry.newBuilder(STRING_PAYLOAD)
+ .setLogName(LOG_ID)
+ .addLabel("tracing_source", "xctc")
+ .setHttpRequest(HttpRequest.newBuilder().setRequestUrl("www.google.com").build())
+ .setResource(GLOBAL_RESOURCE)
+ .build();
+ otelEntry =
+ LogEntry.newBuilder(OTEL_PAYLOAD)
+ .addLabel("tracing_source", "otel")
+ .setLogName(LOG_ID)
+ .setResource(GLOBAL_RESOURCE)
+ .build();
+
+ // Initializes open telemetry SDK
+ InMemorySpanExporter testExporter = InMemorySpanExporter.create();
+ SpanProcessor inMemorySpanProcessor = SimpleSpanProcessor.create(testExporter);
+ OpenTelemetrySdk openTelemetrySdk =
+ OpenTelemetrySdk.builder()
+ .setTracerProvider(
+ SdkTracerProvider.builder().addSpanProcessor(inMemorySpanProcessor).build())
+ .build();
+ tracer = openTelemetrySdk.getTracer("ContextTest");
+ }
+
+ @After
+ public void cleanUpLogs() throws InterruptedException {
+ assertTrue(cleanupLog(LOG_ID));
+ }
+
+ @Test(timeout = 600_000)
+ public void testDetectW3CTraceId() throws InterruptedException {
+ // Loads w3c tracing context and writes a log entry
+ Context.Builder builder = Context.newBuilder();
+ builder.loadW3CTraceParentContext(W3C_TRACE_CONTEXT);
+ contextHandler.setCurrentContext(builder.build());
+ logging.write(ImmutableList.of(w3cEntry));
+ logging.flush();
+
+ // Find the log name and wait until we have at least 1 entry
+ Iterator iterator = waitForLogs(logName, MONITORED_RESOURCES_IN_TEST, 1);
+ assertThat(iterator.hasNext()).isTrue();
+
+ LogEntry entry = iterator.next();
+ assertEquals(LOG_ID, entry.getLogName());
+ assertEquals(ImmutableMap.of("tracing_source", "w3c"), entry.getLabels());
+ assertEquals(HttpRequest.newBuilder().setStatus(500).build(), entry.getHttpRequest());
+ assertEquals(W3C_TEST_TRACE_ID, entry.getTrace());
+ assertEquals(W3C_TEST_SPAN_ID, entry.getSpanId());
+ assertEquals(true, entry.getTraceSampled());
+ }
+
+ @Test(timeout = 600_000)
+ public void testDetectXCTCTraceId() throws InterruptedException {
+ // Loads cloud trace context and writes a log entry
+ Context.Builder builder = Context.newBuilder();
+ builder.loadCloudTraceContext(X_CLOUD_TRACE_CONTEXT);
+ contextHandler.setCurrentContext(builder.build());
+ logging.write(ImmutableList.of(xctcEntry));
+ logging.flush();
+
+ // Find the log name and wait until we have at least 1 entry
+ Iterator iterator = waitForLogs(logName, MONITORED_RESOURCES_IN_TEST, 1);
+ assertThat(iterator.hasNext()).isTrue();
+
+ LogEntry entry = iterator.next();
+ assertEquals(LOG_ID, entry.getLogName());
+ assertEquals(ImmutableMap.of("tracing_source", "xctc"), entry.getLabels());
+ assertEquals(
+ HttpRequest.newBuilder().setRequestUrl("www.google.com").build(), entry.getHttpRequest());
+ assertEquals(XCTC_TEST_TRACE_ID, entry.getTrace());
+ assertEquals(XCTC_TEST_SPAN_ID, entry.getSpanId());
+ assertEquals(true, entry.getTraceSampled());
+ }
+
+ @Test(timeout = 600_000)
+ public void testDetectOtelTraceId() throws InterruptedException {
+ // Writes a log entry in open telemetry context
+ writeLogEntryWithOtelContext(otelEntry);
+
+ // Find the log name and wait until we have at least 1 entry
+ Iterator iterator = waitForLogs(logName, MONITORED_RESOURCES_IN_TEST, 1);
+ assertThat(iterator.hasNext()).isTrue();
+
+ LogEntry entry = iterator.next();
+ assertEquals(LOG_ID, entry.getLogName());
+ assertEquals(OTEL_PAYLOAD, entry.getPayload());
+ assertEquals(ImmutableMap.of("tracing_source", "otel"), entry.getLabels());
+ assertNull(entry.getHttpRequest());
+ assertEquals(otelTraceId, entry.getTrace());
+ assertEquals(otelSpanId, entry.getSpanId());
+ assertEquals(isSampled, entry.getTraceSampled());
+ }
+
+ @Test(timeout = 600_000)
+ public void testW3CTraceIdWithOtelContext() throws InterruptedException {
+ // Writes a log entry with W3C context and Open Telemetry context
+ Context.Builder builder = Context.newBuilder();
+ builder.loadW3CTraceParentContext(W3C_TRACE_CONTEXT);
+ contextHandler.setCurrentContext(builder.build());
+ writeLogEntryWithOtelContext(w3cEntry);
+
+ // Find the log name and wait until we have at least 1 entry
+ Iterator iterator = waitForLogs(logName, MONITORED_RESOURCES_IN_TEST, 1);
+ assertThat(iterator.hasNext()).isTrue();
+
+ LogEntry entry = iterator.next();
+ assertEquals(LOG_ID, entry.getLogName());
+ assertEquals(HttpRequest.newBuilder().setStatus(500).build(), entry.getHttpRequest());
+ // Expect to get trace Id, span Id and isSampled flag from Open Telemetry context when it
+ // exists.
+ assertEquals(otelTraceId, entry.getTrace());
+ assertEquals(otelSpanId, entry.getSpanId());
+ assertEquals(isSampled, entry.getTraceSampled());
+ }
+
+ @Test(timeout = 600_000)
+ public void testXCTCTraceIdWithOtelContext() throws InterruptedException {
+ // Writes a log entry with cloud trace context and Open Telemetry context
+ Context.Builder builder = Context.newBuilder();
+ builder.loadCloudTraceContext(X_CLOUD_TRACE_CONTEXT);
+ contextHandler.setCurrentContext(builder.build());
+ writeLogEntryWithOtelContext(xctcEntry);
+
+ // Find the log name and wait until we have at least 1 entry
+ Iterator iterator = waitForLogs(logName, MONITORED_RESOURCES_IN_TEST, 1);
+ assertThat(iterator.hasNext()).isTrue();
+
+ LogEntry entry = iterator.next();
+ assertEquals(LOG_ID, entry.getLogName());
+ // Expect to get trace Id, span Id and isSampled flag from Open telemetry context when it
+ // exists.
+ assertEquals(otelTraceId, entry.getTrace());
+ assertEquals(otelSpanId, entry.getSpanId());
+ assertEquals(isSampled, entry.getTraceSampled());
+ }
+
+ // Writes a log entry with otel context
+ private static void writeLogEntryWithOtelContext(LogEntry entry) throws InterruptedException {
+ Span otelSpan = tracer.spanBuilder("Example Span").startSpan();
+ SpanContext currentOtelContext;
+ try (Scope scope = otelSpan.makeCurrent()) {
+ currentOtelContext = otelSpan.getSpanContext();
+ otelTraceId = currentOtelContext.getTraceId();
+ otelSpanId = currentOtelContext.getSpanId();
+ isSampled = currentOtelContext.isSampled();
+ logging.write(ImmutableList.of(entry));
+ } catch (Throwable t) {
+ otelSpan.recordException(t);
+ throw t;
+ } finally {
+ otelSpan.end();
+ }
+ logging.flush();
+ }
+}
diff --git a/pom.xml b/pom.xml
index 51e2242ea..a1359a7ec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,8 +56,27 @@
google-cloud-logging-parent
+
+
+ io.opentelemetry
+ opentelemetry-bom
+ 1.38.0
+ pom
+ import
+
+
+
+
+
+
+
+
+
+
+
+
com.google.api.grpc
proto-google-cloud-logging-v2
@@ -117,6 +136,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ io.opentelemetry
+ opentelemetry-semconv
+ 1.1.0-alpha
+ test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ com.google.cloud.opentelemetry
+ exporter-trace
+ 0.15.0
+ test
+
+