From 05432f2590e986129fc80d4c9d1495287c9024ae Mon Sep 17 00:00:00 2001 From: Mike Goldsmth Date: Wed, 24 Apr 2024 16:10:08 +0100 Subject: [PATCH] Add baggage span processor --- .github/component_owners.yml | 2 + baggage-processor/README.md | 49 +++++++++++++ baggage-processor/build.gradle.kts | 15 ++++ baggage-processor/gradle.properties | 2 + .../processor/BaggageSpanProcessor.java | 56 +++++++++++++++ .../processor/BaggageSpanProcessorTest.java | 68 +++++++++++++++++++ settings.gradle.kts | 1 + 7 files changed, 193 insertions(+) create mode 100644 baggage-processor/README.md create mode 100644 baggage-processor/build.gradle.kts create mode 100644 baggage-processor/gradle.properties create mode 100644 baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessor.java create mode 100644 baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorTest.java diff --git a/.github/component_owners.yml b/.github/component_owners.yml index 6c8223cd9..b052ee74c 100644 --- a/.github/component_owners.yml +++ b/.github/component_owners.yml @@ -19,6 +19,8 @@ components: aws-xray-propagator: - wangzlei - srprash + baggage-procesor: + - mikegoldsmith compressors: - jack-berg consistent-sampling: diff --git a/baggage-processor/README.md b/baggage-processor/README.md new file mode 100644 index 000000000..76f50ccc8 --- /dev/null +++ b/baggage-processor/README.md @@ -0,0 +1,49 @@ +# OpenTelemetry Baggage Span Processor + +The BaggageSpanProcessor reads entries stored in Baggage from the parent context +and adds the baggage keys and values to the `Span` as attributes on start. + +Add this span processor to a tracer provider. + +Warning! + +To repeat: a consequence of adding data to Baggage is that the keys and values +will appear in all outgoing trace context headers from the application. + +Do not put sensitive information in Baggage. + +## Usage + +Add the span processor when configuring the tracer provider. + +To configure the span processor to copy all baggage entries during configuration: + +```java +import io.opentelemetry.contrib.baggage.processor; +// ... + +Tracer tracer = SdkTracerProvider.builder() + .addSpanProcessor(new BaggageSpanProcessor(BaggageSpanProcessor.allowAllBaggageKeys)) + .build() +``` + +Alternatively, you can provide a custom baggage key predicate to select which baggage keys you want to copy. + +For example, to only copy baggage entries that start with `my-key`: + +```java +new BaggageSpanProcessor(baggageKey -> baggageKey.startsWith("my-key")) +``` + +For example, to only copy baggage entries that match the regex `^key.+`: + +```java +Pattern pattern = Pattern.compile("^key.+"); +new BaggageSpanProcessor(baggageKey -> pattern.matcher(baggageKey).matches()) +``` + +## Component owners + +- [Mike Golsmith](https://github.com/MikeGoldsmith), Honeycomb + +Learn more about component owners in [component_owners.yml](../.github/component_owners.yml). diff --git a/baggage-processor/build.gradle.kts b/baggage-processor/build.gradle.kts new file mode 100644 index 000000000..5d65a5158 --- /dev/null +++ b/baggage-processor/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("otel.java-conventions") + + id("otel.publish-conventions") +} + +description = "OpenTelemetry Baggage Span Processor" +otelJava.moduleName.set("io.opentelemetry.contrib.baggage.processor") + +dependencies { + api("io.opentelemetry:opentelemetry-api") + api("io.opentelemetry:opentelemetry-sdk") + + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") +} diff --git a/baggage-processor/gradle.properties b/baggage-processor/gradle.properties new file mode 100644 index 000000000..a0402e1e2 --- /dev/null +++ b/baggage-processor/gradle.properties @@ -0,0 +1,2 @@ +# TODO: uncomment when ready to mark as stable +# otel.stable=true diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessor.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessor.java new file mode 100644 index 000000000..941ac56b0 --- /dev/null +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessor.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.baggage.processor; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SpanProcessor; +import java.util.function.Predicate; + +/** + * This span processor copies attributes stored in {@link Baggage} into each newly created {@link + * io.opentelemetry.api.trace.Span}. + */ +public class BaggageSpanProcessor implements SpanProcessor { + private final Predicate baggageKeyPredicate; + + /** A {@link Predicate} that returns true for all baggage keys. */ + public static final Predicate allowAllBaggageKeys = baggageKey -> true; + + /** + * Creates a new {@link BaggageSpanProcessor} that copies only baggage entries with keys that pass + * the provided filter into the newly created {@link io.opentelemetry.api.trace.Span}. + */ + public BaggageSpanProcessor(Predicate baggageKeyPredicate) { + this.baggageKeyPredicate = baggageKeyPredicate; + } + + @Override + public void onStart(Context parentContext, ReadWriteSpan span) { + Baggage.fromContext(parentContext) + .forEach( + (s, baggageEntry) -> { + if (baggageKeyPredicate.test(s)) { + span.setAttribute(s, baggageEntry.getValue()); + } + }); + } + + @Override + public boolean isStartRequired() { + return true; + } + + @Override + public void onEnd(ReadableSpan span) {} + + @Override + public boolean isEndRequired() { + return false; + } +} diff --git a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorTest.java b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorTest.java new file mode 100644 index 000000000..6bcbd0e15 --- /dev/null +++ b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorTest.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.baggage.processor; + +import io.opentelemetry.api.baggage.Baggage; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import java.util.regex.Pattern; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class BaggageSpanProcessorTest { + + @Test + public void test_baggageSpanProcessor_adds_attributes_to_spans(@Mock ReadWriteSpan span) { + try (BaggageSpanProcessor processor = + new BaggageSpanProcessor(BaggageSpanProcessor.allowAllBaggageKeys)) { + try (Scope ignore = Baggage.current().toBuilder().put("key", "value").build().makeCurrent()) { + processor.onStart(Context.current(), span); + Mockito.verify(span).setAttribute("key", "value"); + } + } + } + + @Test + public void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_matches( + @Mock ReadWriteSpan span) { + try (BaggageSpanProcessor processor = new BaggageSpanProcessor(key -> key.startsWith("k"))) { + try (Scope ignore = + Baggage.current().toBuilder() + .put("key", "value") + .put("other", "value") + .build() + .makeCurrent()) { + processor.onStart(Context.current(), span); + Mockito.verify(span).setAttribute("key", "value"); + Mockito.verify(span, Mockito.never()).setAttribute("other", "value"); + } + } + } + + @Test + public void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_matches_regex( + @Mock ReadWriteSpan span) { + Pattern pattern = Pattern.compile("k.*"); + try (BaggageSpanProcessor processor = + new BaggageSpanProcessor(key -> pattern.matcher(key).matches())) { + try (Scope ignore = + Baggage.current().toBuilder() + .put("key", "value") + .put("other", "value") + .build() + .makeCurrent()) { + processor.onStart(Context.current(), span); + Mockito.verify(span).setAttribute("key", "value"); + Mockito.verify(span, Mockito.never()).setAttribute("other", "value"); + } + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 5df34be3b..9bff67d85 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -62,6 +62,7 @@ include(":all") include(":aws-resources") include(":aws-xray") include(":aws-xray-propagator") +include(":baggage-processor") include(":compressors:compressor-zstd") include(":consistent-sampling") include(":dependencyManagement")