diff --git a/baggage-processor/README.md b/baggage-processor/README.md index 0f0f99179..4dc306114 100644 --- a/baggage-processor/README.md +++ b/baggage-processor/README.md @@ -1,30 +1,35 @@ # 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. +The `BaggageSpanProcessor` and `BaggageLogRecordPRocessor` read entries stored in Baggage from the +parent context and adds the baggage keys and values to the `Span`, respectively `LogRecord`, as +attributes on start, respectively emit. -Add this span processor to a tracer provider. +Add these span and log processors to the tracer and logger providers. 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. +will appear in all outgoing trace and log context headers from the application. Do not put sensitive information in Baggage. ## Usage -Add the span processor when configuring the tracer provider. +Add the span and log processor when configuring the tracer and logger providers. -To configure the span processor to copy all baggage entries during configuration: +To configure the span and log processors to copy all baggage entries during configuration: ```java import io.opentelemetry.contrib.baggage.processor; // ... -Tracer tracer = SdkTracerProvider.builder() - .addSpanProcessor(new BaggageSpanProcessor(BaggageSpanProcessor.allowAllBaggageKeys)) - .build() +TracerProvider tracerProvider = SdkTracerProvider.builder() + .addSpanProcessor(BaggageSpanProcessor.allowAllBaggageKeys()) + .build(); + +LoggerProvider loggerProvider = SdkLoggerProvider.builder() + .addLogRecordProcessor(BaggageLogRecordProcessor.allowAllBaggageKeys()) + .build(); ``` Alternatively, you can provide a custom baggage key predicate to select which baggage keys you want to copy. @@ -32,24 +37,27 @@ Alternatively, you can provide a custom baggage key predicate to select which ba For example, to only copy baggage entries that start with `my-key`: ```java -new BaggageSpanProcessor(baggageKey -> baggageKey.startsWith("my-key")) +new BaggageSpanProcessor(baggageKey -> baggageKey.startsWith("my-key")); +new BaggageLogRecordProcessor(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()) +new BaggageSpanProcessor(baggageKey -> pattern.matcher(baggageKey).matches()); +new BaggageLogRecordProcessor(baggageKey -> pattern.matcher(baggageKey).matches()); ``` ## Usage with SDK auto-configuration -If you are using the OpenTelemetry SDK auto-configuration, you can add the span processor this -library to configure the span processor. +If you are using the OpenTelemetry SDK auto-configuration, you can add the span and log baggage +processors through configuration. -| Property | Description | -|------------------------------------------------------------------|---------------------------------------------------------------------------------------------------| -| otel.java.experimental.span-attributes.copy-from-baggage.include | Add baggage entries as span attributes, e.g. `key1,key2` or `*` to add all baggage items as keys. | +| Property | Description | +|--------------------------------------------------------------------|---------------------------------------------------------------------------------------------------| +| `otel.java.experimental.span-attributes.copy-from-baggage.include` | Add baggage entries as span attributes, e.g. `key1,key2` or `*` to add all baggage items as keys. | +| `otel.java.experimental.log-attributes.copy-from-baggage.include` | Add baggage entries as log attributes, e.g. `key1,key2` or `*` to add all baggage items as keys. | ## Component owners diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java new file mode 100644 index 000000000..4e8c91505 --- /dev/null +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java @@ -0,0 +1,49 @@ +/* + * 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.api.common.AttributeKey; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.logs.LogRecordProcessor; +import io.opentelemetry.sdk.logs.ReadWriteLogRecord; +import java.util.function.Predicate; + +/** + * This log record processor copies attributes stored in {@link Baggage} into each newly created log + * record. + */ +public class BaggageLogRecordProcessor implements LogRecordProcessor { + + /** + * Creates a new {@link BaggageLogRecordProcessor} that copies all baggage entries into the newly + * created log record. + */ + public static BaggageLogRecordProcessor allowAllBaggageKeys() { + return new BaggageLogRecordProcessor(baggageKey -> true); + } + + private final Predicate baggageKeyPredicate; + + /** + * Creates a new {@link BaggageLogRecordProcessor} that copies only baggage entries with keys that + * pass the provided filter into the newly created log record. + */ + public BaggageLogRecordProcessor(Predicate baggageKeyPredicate) { + this.baggageKeyPredicate = baggageKeyPredicate; + } + + @Override + public void onEmit(Context context, ReadWriteLogRecord logRecord) { + Baggage.fromContext(context) + .forEach( + (s, baggageEntry) -> { + if (baggageKeyPredicate.test(s)) { + logRecord.setAttribute(AttributeKey.stringKey(s), baggageEntry.getValue()); + } + }); + } +} diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java new file mode 100644 index 000000000..da35512a3 --- /dev/null +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.baggage.processor; + +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder; +import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import java.util.List; + +public class BaggageProcessorCustomizer implements AutoConfigurationCustomizerProvider { + @Override + public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) { + autoConfigurationCustomizer + .addTracerProviderCustomizer( + (sdkTracerProviderBuilder, config) -> { + addSpanProcessor(sdkTracerProviderBuilder, config); + return sdkTracerProviderBuilder; + }) + .addLoggerProviderCustomizer( + (sdkLoggerProviderBuilder, config) -> { + addLogRecordProcessor(sdkLoggerProviderBuilder, config); + return sdkLoggerProviderBuilder; + }); + } + + private static void addSpanProcessor( + SdkTracerProviderBuilder sdkTracerProviderBuilder, ConfigProperties config) { + List keys = + config.getList("otel.java.experimental.span-attributes.copy-from-baggage.include"); + + if (keys.isEmpty()) { + return; + } + + sdkTracerProviderBuilder.addSpanProcessor(createBaggageSpanProcessor(keys)); + } + + static BaggageSpanProcessor createBaggageSpanProcessor(List keys) { + if (keys.size() == 1 && keys.get(0).equals("*")) { + return BaggageSpanProcessor.allowAllBaggageKeys(); + } + return new BaggageSpanProcessor(keys::contains); + } + + private static void addLogRecordProcessor( + SdkLoggerProviderBuilder sdkLoggerProviderBuilder, ConfigProperties config) { + List keys = + config.getList("otel.java.experimental.log-attributes.copy-from-baggage.include"); + + if (keys.isEmpty()) { + return; + } + + sdkLoggerProviderBuilder.addLogRecordProcessor(createBaggageLogRecordProcessor(keys)); + } + + static BaggageLogRecordProcessor createBaggageLogRecordProcessor(List keys) { + if (keys.size() == 1 && keys.get(0).equals("*")) { + return BaggageLogRecordProcessor.allowAllBaggageKeys(); + } + return new BaggageLogRecordProcessor(keys::contains); + } +} diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorCustomizer.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorCustomizer.java deleted file mode 100644 index 59de7f94f..000000000 --- a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorCustomizer.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.baggage.processor; - -import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; -import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; -import java.util.List; - -public class BaggageSpanProcessorCustomizer implements AutoConfigurationCustomizerProvider { - @Override - public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) { - autoConfigurationCustomizer.addTracerProviderCustomizer( - (sdkTracerProviderBuilder, config) -> { - addSpanProcessor(sdkTracerProviderBuilder, config); - return sdkTracerProviderBuilder; - }); - } - - private static void addSpanProcessor( - SdkTracerProviderBuilder sdkTracerProviderBuilder, ConfigProperties config) { - List keys = - config.getList("otel.java.experimental.span-attributes.copy-from-baggage.include"); - - if (keys.isEmpty()) { - return; - } - - sdkTracerProviderBuilder.addSpanProcessor(createProcessor(keys)); - } - - static BaggageSpanProcessor createProcessor(List keys) { - if (keys.size() == 1 && keys.get(0).equals("*")) { - return BaggageSpanProcessor.allowAllBaggageKeys(); - } - return new BaggageSpanProcessor(keys::contains); - } -} diff --git a/baggage-processor/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider b/baggage-processor/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider index 6cd88e910..8eb4afb06 100644 --- a/baggage-processor/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider +++ b/baggage-processor/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider @@ -1 +1 @@ -io.opentelemetry.contrib.baggage.processor.BaggageSpanProcessorCustomizer +io.opentelemetry.contrib.baggage.processor.BaggageProcessorCustomizer diff --git a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorCustomizerTest.java b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java similarity index 51% rename from baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorCustomizerTest.java rename to baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java index c6e7b13c2..eb0d82172 100644 --- a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorCustomizerTest.java +++ b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.contrib.baggage.processor; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.verify; @@ -20,14 +21,19 @@ import io.opentelemetry.sdk.autoconfigure.internal.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.logs.ConfigurableLogRecordExporterProvider; import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; -import io.opentelemetry.sdk.testing.assertj.SpanDataAssert; -import io.opentelemetry.sdk.testing.assertj.TracesAssert; +import io.opentelemetry.sdk.logs.ReadWriteLogRecord; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.time.Duration; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -38,57 +44,75 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class BaggageSpanProcessorCustomizerTest { +class BaggageProcessorCustomizerTest { private static final String MEMORY_EXPORTER = "memory"; @Test void test_customizer() { - assertCustomizer(Collections.emptyMap(), span -> span.hasTotalAttributeCount(0)); assertCustomizer( - Collections.singletonMap( - "otel.java.experimental.span-attributes.copy-from-baggage.include", "key"), - span -> span.hasAttribute(AttributeKey.stringKey("key"), "value")); + Collections.emptyMap(), + span -> assertThat(span).hasTotalAttributeCount(0), + logRecord -> assertThat(logRecord).hasTotalAttributeCount(0)); + Map properties = new HashMap<>(); + properties.put("otel.java.experimental.span-attributes.copy-from-baggage.include", "key"); + properties.put("otel.java.experimental.log-attributes.copy-from-baggage.include", "key"); + assertCustomizer( + properties, + span -> assertThat(span.getAttributes()).containsEntry("key", "value"), + logRecord -> assertThat(logRecord.getAttributes()).containsEntry("key", "value")); } private static void assertCustomizer( - Map properties, Consumer spanDataAssertConsumer) { + Map properties, + Consumer spanDataRequirements, + Consumer logRecordRequirements) { InMemorySpanExporter spanExporter = InMemorySpanExporter.create(); + InMemoryLogRecordExporter logExporter = InMemoryLogRecordExporter.create(); - OpenTelemetrySdk sdk = getOpenTelemetrySdk(properties, spanExporter); + OpenTelemetrySdk sdk = getOpenTelemetrySdk(properties, spanExporter, logExporter); try (Scope ignore = Baggage.current().toBuilder().put("key", "value").build().makeCurrent()) { sdk.getTracer("test").spanBuilder("test").startSpan().end(); + sdk.getLogsBridge().get("test").logRecordBuilder().setBody("test").emit(); } + await() .atMost(Duration.ofSeconds(1)) .untilAsserted( - () -> - TracesAssert.assertThat(spanExporter.getFinishedSpanItems()) - .hasTracesSatisfyingExactly( - trace -> trace.hasSpansSatisfyingExactly(spanDataAssertConsumer))); + () -> { + assertThat(spanExporter.getFinishedSpanItems()) + .hasSize(1) + .allSatisfy(spanDataRequirements); + assertThat(logExporter.getFinishedLogRecordItems()) + .hasSize(1) + .allSatisfy(logRecordRequirements); + }); } private static OpenTelemetrySdk getOpenTelemetrySdk( - Map properties, InMemorySpanExporter spanExporter) { - SpiHelper spiHelper = - SpiHelper.create(BaggageSpanProcessorCustomizerTest.class.getClassLoader()); + Map properties, + InMemorySpanExporter spanExporter, + InMemoryLogRecordExporter logRecordExporter) { + SpiHelper spiHelper = SpiHelper.create(BaggageProcessorCustomizerTest.class.getClassLoader()); AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder() .addPropertiesSupplier( () -> ImmutableMap.of( - // We set the export interval of the spans to 100 ms. The default value is 5 + // We set the export interval of the spans to 10 ms. The default value is 5 // seconds. - "otel.bsp.schedule.delay", + "otel.bsp.schedule.delay", // span exporter + "10", + "otel.blrp.schedule.delay", // log exporter "10", "otel.traces.exporter", MEMORY_EXPORTER, "otel.metrics.exporter", "none", "otel.logs.exporter", - "none")) + MEMORY_EXPORTER)) .addPropertiesSupplier(() -> properties); AutoConfigureUtil.setComponentLoader( sdkBuilder, @@ -105,6 +129,20 @@ public SpanExporter createExporter(ConfigProperties configProperties) { return spanExporter; } + @Override + public String getName() { + return MEMORY_EXPORTER; + } + }); + } else if (spiClass == ConfigurableLogRecordExporterProvider.class) { + return Collections.singletonList( + (T) + new ConfigurableLogRecordExporterProvider() { + @Override + public LogRecordExporter createExporter(ConfigProperties configProperties) { + return logRecordExporter; + } + @Override public String getName() { return MEMORY_EXPORTER; @@ -120,7 +158,7 @@ public String getName() { @Test public void test_baggageSpanProcessor_adds_attributes_to_spans(@Mock ReadWriteSpan span) { try (BaggageSpanProcessor processor = - BaggageSpanProcessorCustomizer.createProcessor(Collections.singletonList("*"))) { + BaggageProcessorCustomizer.createBaggageSpanProcessor(Collections.singletonList("*"))) { try (Scope ignore = Baggage.current().toBuilder().put("key", "value").build().makeCurrent()) { processor.onStart(Context.current(), span); verify(span).setAttribute("key", "value"); @@ -132,7 +170,7 @@ public void test_baggageSpanProcessor_adds_attributes_to_spans(@Mock ReadWriteSp public void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_matches( @Mock ReadWriteSpan span) { try (BaggageSpanProcessor processor = - BaggageSpanProcessorCustomizer.createProcessor(Collections.singletonList("key"))) { + BaggageProcessorCustomizer.createBaggageSpanProcessor(Collections.singletonList("key"))) { try (Scope ignore = Baggage.current().toBuilder() .put("key", "value") @@ -145,4 +183,36 @@ public void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_m } } } + + @Test + public void test_baggageLogRecordProcessor_adds_attributes_to_logRecord( + @Mock ReadWriteLogRecord logRecord) { + try (BaggageLogRecordProcessor processor = + BaggageProcessorCustomizer.createBaggageLogRecordProcessor( + Collections.singletonList("*"))) { + try (Scope ignore = Baggage.current().toBuilder().put("key", "value").build().makeCurrent()) { + processor.onEmit(Context.current(), logRecord); + verify(logRecord).setAttribute(AttributeKey.stringKey("key"), "value"); + } + } + } + + @Test + public void test_baggageLogRecordProcessor_adds_attributes_to_spans_when_key_filter_matches( + @Mock ReadWriteLogRecord logRecord) { + try (BaggageLogRecordProcessor processor = + BaggageProcessorCustomizer.createBaggageLogRecordProcessor( + Collections.singletonList("key"))) { + try (Scope ignore = + Baggage.current().toBuilder() + .put("key", "value") + .put("other", "value") + .build() + .makeCurrent()) { + processor.onEmit(Context.current(), logRecord); + verify(logRecord).setAttribute(AttributeKey.stringKey("key"), "value"); + verify(logRecord, Mockito.never()).setAttribute(AttributeKey.stringKey("other"), "value"); + } + } + } }