diff --git a/README.md b/README.md
index 1fc6c348..5bfb055e 100644
--- a/README.md
+++ b/README.md
@@ -64,12 +64,12 @@ All in all, you should do the following:
4. Adjust your logging configuration accordingly.
Let's say you want to make use of the *servlet filter* feature, then you need to add the following dependency to your
-POM with property `cf-logging-version` referring to the latest nexus version (currently `4.0.0`):
+POM with property `cf-logging-version` referring to the latest nexus version (currently `4.0.1`):
```xml
- 4.0.0
+ 4.0.1
```
diff --git a/cf-java-logging-support-core/pom.xml b/cf-java-logging-support-core/pom.xml
index 4c2c6a24..ab24b0ed 100644
--- a/cf-java-logging-support-core/pom.xml
+++ b/cf-java-logging-support-core/pom.xml
@@ -25,7 +25,7 @@
com.sap.hcp.cf.logging
cf-java-logging-support-parent
- 4.0.0
+ 4.0.1
../pom.xml
diff --git a/cf-java-logging-support-log4j2/pom.xml b/cf-java-logging-support-log4j2/pom.xml
index 3cc47476..a1f3d894 100644
--- a/cf-java-logging-support-log4j2/pom.xml
+++ b/cf-java-logging-support-log4j2/pom.xml
@@ -11,7 +11,7 @@
../pom.xml
com.sap.hcp.cf.logging
cf-java-logging-support-parent
- 4.0.0
+ 4.0.1
diff --git a/cf-java-logging-support-logback/pom.xml b/cf-java-logging-support-logback/pom.xml
index ffe0a3aa..20b85824 100644
--- a/cf-java-logging-support-logback/pom.xml
+++ b/cf-java-logging-support-logback/pom.xml
@@ -10,7 +10,7 @@
../pom.xml
com.sap.hcp.cf.logging
cf-java-logging-support-parent
- 4.0.0
+ 4.0.1
diff --git a/cf-java-logging-support-opentelemetry-agent-extension/pom.xml b/cf-java-logging-support-opentelemetry-agent-extension/pom.xml
index b54f5108..e3098351 100644
--- a/cf-java-logging-support-opentelemetry-agent-extension/pom.xml
+++ b/cf-java-logging-support-opentelemetry-agent-extension/pom.xml
@@ -8,13 +8,57 @@
jar
cf-java-logging-support-opentelemetry-agent-extension
-
+ OpenTelemetry Java Agent Extension for auto-configuration of SAP BTP services.
cf-java-logging-support-parent
com.sap.hcp.cf.logging
- 4.0.0
+ 4.0.1
+ ../pom.xml
+ https://github.com/SAP/cf-java-logging-support
+
+ SAP SE
+ https://www.sap.com
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+
+ KarstenSchnitter
+ Karsten Schnitter
+ k.schnitter@sap.com
+ SAP SE
+ https://github.com/SAP
+
+
+ WolfgangTheilmann
+ Wolfgang Theilmann
+ wolfgang.theilmann@sap.com
+ SAP SE
+ https://github.com/SAP
+
+
+ HariG
+ Hariharan Gandhi
+ hariharan.gandhi@sap.com
+ SAP SE
+ https://github.com/SAP
+
+
+
+
+ scm:git:git@github.com:SAP/cf-java-logging-support.git
+ scm:git:git@github.com:SAP/cf-java-logging-support.git
+ git@github.com:SAP/cf-java-logging-support.git
+
+
1.55.0
diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/CloudLoggingConfigurationCustomizerProvider.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/CloudLoggingConfigurationCustomizerProvider.java
index e3507d32..037770da 100644
--- a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/CloudLoggingConfigurationCustomizerProvider.java
+++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/CloudLoggingConfigurationCustomizerProvider.java
@@ -1,6 +1,7 @@
package com.sap.hcf.cf.logging.opentelemetry.agent.ext;
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudLoggingBindingPropertiesSupplier;
+import com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter.SanitizeSpanExporterCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
@@ -9,14 +10,13 @@
public class CloudLoggingConfigurationCustomizerProvider implements AutoConfigurationCustomizerProvider {
private static final Logger LOG = Logger.getLogger(CloudLoggingConfigurationCustomizerProvider.class.getName());
- private static final String VERSION = "4.0.0";
+ private static final String VERSION = "4.0.1";
@Override
public void customize(AutoConfigurationCustomizer autoConfiguration) {
LOG.info("Initializing SAP BTP Observability extension " + VERSION);
autoConfiguration.addPropertiesSupplier(new CloudLoggingBindingPropertiesSupplier());
-
- // ConfigurableLogRecordExporterProvider
+ autoConfiguration.addSpanExporterCustomizer(new SanitizeSpanExporterCustomizer());
}
}
diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/SanitizeSpanExporterCustomizer.java b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/SanitizeSpanExporterCustomizer.java
new file mode 100644
index 00000000..0690833e
--- /dev/null
+++ b/cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/SanitizeSpanExporterCustomizer.java
@@ -0,0 +1,86 @@
+package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import io.opentelemetry.sdk.common.CompletableResultCode;
+import io.opentelemetry.sdk.trace.data.DelegatingSpanData;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.opentelemetry.sdk.trace.export.SpanExporter;
+
+import java.util.Collection;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+import static io.opentelemetry.api.common.AttributeKey.stringKey;
+
+public class SanitizeSpanExporterCustomizer implements BiFunction {
+
+ private static final String PROPERTY_ENABLED_KEY = "sap.cf.integration.otel.extension.sanitizer.enabled";
+ private static final AttributeKey DB_QUERY_TEXT = stringKey("db.query.text");
+ //@Deprecated
+ private static final AttributeKey DB_STATEMENT = stringKey("db.statement");
+
+ @Override
+ public SpanExporter apply(SpanExporter delegate, ConfigProperties config) {
+ if (config != null && !config.getBoolean(PROPERTY_ENABLED_KEY, true)) {
+ return delegate;
+ }
+ return new SpanExporter() {
+ @Override
+ public CompletableResultCode export(Collection spans) {
+ return delegate.export(spans.stream().map(this::sanitizeSpanData).collect(Collectors.toList()));
+ }
+
+ private SpanData sanitizeSpanData(SpanData spanData) {
+ Attributes attributes = spanData.getAttributes();
+ if (attributes == null) {
+ return spanData;
+ }
+ String dbQueryText = attributes.get(DB_QUERY_TEXT);
+ String dbStatement = attributes.get(DB_STATEMENT);
+ if (isClean(dbQueryText) && isClean(dbStatement)) {
+ return spanData;
+ }
+ AttributesBuilder sanitized = attributes.toBuilder();
+ if (!isClean(dbQueryText)) {
+ sanitized.put(DB_QUERY_TEXT, dbQueryText.substring(0, 7) + " [REDACTED]");
+ }
+ if (!isClean(dbStatement)) {
+ sanitized.put(DB_STATEMENT, dbStatement.substring(0, 7) + " [REDACTED]");
+ }
+ return new SanitizedSpanData(spanData, sanitized.build());
+ }
+
+ private boolean isClean(String query) {
+ return query == null || !query.toLowerCase().startsWith("connect");
+ }
+
+ @Override
+ public CompletableResultCode flush() {
+ return delegate.flush();
+ }
+
+ @Override
+ public CompletableResultCode shutdown() {
+ return delegate.shutdown();
+ }
+ };
+ }
+
+ private static class SanitizedSpanData extends DelegatingSpanData {
+
+ private final Attributes filteredAttributes;
+
+ protected SanitizedSpanData(SpanData delegate, Attributes filteredAttributes) {
+ super(delegate);
+ this.filteredAttributes = filteredAttributes;
+ }
+
+ @Override
+ public Attributes getAttributes() {
+ return filteredAttributes;
+ }
+ }
+}
diff --git a/cf-java-logging-support-opentelemetry-agent-extension/src/test/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/SanitizeSpanExporterCustomizerTest.java b/cf-java-logging-support-opentelemetry-agent-extension/src/test/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/SanitizeSpanExporterCustomizerTest.java
new file mode 100644
index 00000000..802a9dd7
--- /dev/null
+++ b/cf-java-logging-support-opentelemetry-agent-extension/src/test/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/exporter/SanitizeSpanExporterCustomizerTest.java
@@ -0,0 +1,150 @@
+package com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter;
+
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.opentelemetry.sdk.trace.export.SpanExporter;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class SanitizeSpanExporterCustomizerTest {
+
+ @Mock(strictness = Mock.Strictness.LENIENT)
+ private SpanData spanData;
+
+ @Mock
+ private SpanExporter delegateExporter;
+
+ @Captor
+ private ArgumentCaptor> spanDataCaptor;
+
+ private SpanExporter sanitizeExporter;
+
+ @BeforeEach
+ void setUp() {
+ when(spanData.getName()).thenReturn("test-span");
+ this.sanitizeExporter = new SanitizeSpanExporterCustomizer().apply(delegateExporter, null);
+ }
+
+ @Test
+ void forwardsSpanWithoutAttributes() {
+ List spans = List.of(spanData);
+ sanitizeExporter.export(spans);
+
+ verify(delegateExporter).export(spans);
+ }
+
+ @Test
+ void forwardsSpanWithEmptyAttributes() {
+ List spans = List.of(spanData);
+ when(spanData.getAttributes()).thenReturn(Attributes.empty());
+ sanitizeExporter.export(spans);
+
+ verify(delegateExporter).export(spans);
+ }
+
+ @Test
+ void forwardsSpanWithoutSensitiveAttributeKey() {
+ Attributes attributes = Attributes.builder().put("some.key", "some value").build();
+ when(spanData.getAttributes()).thenReturn(attributes);
+ List spans = List.of(spanData);
+ sanitizeExporter.export(spans);
+
+ verify(delegateExporter).export(spans);
+ }
+
+ @Test
+ void forwardsSpanWithSensitiveAttributeKeyButWithoutSensitiveValue() {
+ Attributes attributes = Attributes.builder().put("db.query.text", "some safe value").build();
+ when(spanData.getAttributes()).thenReturn(attributes);
+ List spans = List.of(spanData);
+ sanitizeExporter.export(spans);
+
+ verify(delegateExporter).export(spans);
+ }
+
+ @Test
+ void redactsSensitiveDbQueryTextValue() {
+ Attributes attributes = Attributes.builder().put("db.query.text", "Connect somewhere").build();
+ when(spanData.getAttributes()).thenReturn(attributes);
+ List spans = List.of(spanData);
+ sanitizeExporter.export(spans);
+
+ verify(delegateExporter).export(spanDataCaptor.capture());
+ SpanData sanitizedSpan = spanDataCaptor.getValue().get(0);
+ assertThat(sanitizedSpan).extracting(SpanData::getName).isEqualTo("test-span");
+ assertThat(sanitizedSpan).extracting(SpanData::getAttributes)
+ .extracting(attrs -> attrs.get(AttributeKey.stringKey("db.query.text")))
+ .isEqualTo("Connect [REDACTED]");
+ }
+
+ @Test
+ void redactsSensitiveDbStatementValue() {
+ Attributes attributes = Attributes.builder().put("db.statement", "CONNECT somewhere").build();
+ when(spanData.getAttributes()).thenReturn(attributes);
+ List spans = List.of(spanData);
+ sanitizeExporter.export(spans);
+
+ verify(delegateExporter).export(spanDataCaptor.capture());
+ SpanData sanitizedSpan = spanDataCaptor.getValue().get(0);
+ assertThat(sanitizedSpan).extracting(SpanData::getName).isEqualTo("test-span");
+ assertThat(sanitizedSpan).extracting(SpanData::getAttributes)
+ .extracting(attrs -> attrs.get(AttributeKey.stringKey("db.statement")))
+ .isEqualTo("CONNECT [REDACTED]");
+ }
+
+ @Test
+ void keepsOtherAttributesOnRedaction() {
+ Attributes attributes =
+ Attributes.builder().put("db.query.text", "connect somewhere").put("some.key", "some.value").build();
+ when(spanData.getAttributes()).thenReturn(attributes);
+ List spans = List.of(spanData);
+ sanitizeExporter.export(spans);
+
+ verify(delegateExporter).export(spanDataCaptor.capture());
+ SpanData sanitizedSpan = spanDataCaptor.getValue().get(0);
+ assertThat(sanitizedSpan).extracting(SpanData::getName).isEqualTo("test-span");
+ assertThat(sanitizedSpan).extracting(SpanData::getAttributes)
+ .extracting(attrs -> attrs.get(AttributeKey.stringKey("db.query.text")))
+ .isEqualTo("connect [REDACTED]");
+ assertThat(sanitizedSpan).extracting(SpanData::getAttributes)
+ .extracting(attrs -> attrs.get(AttributeKey.stringKey("some.key")))
+ .isEqualTo("some.value");
+ }
+
+ @Test
+ void canBeDisabledViaConfig() {
+ Map configEntries = new HashMap<>();
+ configEntries.put("sap.cf.integration.otel.extension.sanitizer.enabled", "false");
+ DefaultConfigProperties configProperties = DefaultConfigProperties.createFromMap(configEntries);
+ SpanExporter spanExporter = new SanitizeSpanExporterCustomizer().apply(delegateExporter, configProperties);
+ assertThat(spanExporter).isSameAs(delegateExporter);
+ }
+
+ @Test
+ void delegatesFlush() {
+ sanitizeExporter.flush();
+ verify(delegateExporter).flush();
+ }
+
+ @Test
+ void delegatesShutdown() {
+ sanitizeExporter.shutdown();
+ verify(delegateExporter).shutdown();
+ }
+}
diff --git a/cf-java-logging-support-servlet-dynlog-jwt/pom.xml b/cf-java-logging-support-servlet-dynlog-jwt/pom.xml
index 5f1c34d3..d55d89b7 100644
--- a/cf-java-logging-support-servlet-dynlog-jwt/pom.xml
+++ b/cf-java-logging-support-servlet-dynlog-jwt/pom.xml
@@ -11,7 +11,7 @@
com.sap.hcp.cf.logging
cf-java-logging-support-parent
- 4.0.0
+ 4.0.1
../pom.xml
diff --git a/cf-java-logging-support-servlet/pom.xml b/cf-java-logging-support-servlet/pom.xml
index 29487ac3..5ff57931 100644
--- a/cf-java-logging-support-servlet/pom.xml
+++ b/cf-java-logging-support-servlet/pom.xml
@@ -9,7 +9,7 @@
com.sap.hcp.cf.logging
cf-java-logging-support-parent
- 4.0.0
+ 4.0.1
../pom.xml
diff --git a/pom.xml b/pom.xml
index fb168675..c7e3b063 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
com.sap.hcp.cf.logging
cf-java-logging-support-parent
- 4.0.0
+ 4.0.1
pom
Cloud Foundry Java logging support components
diff --git a/sample-spring-boot/manifest-otel-javaagent.yml b/sample-spring-boot/manifest-otel-javaagent.yml
index a8c41027..8d9c03c6 100644
--- a/sample-spring-boot/manifest-otel-javaagent.yml
+++ b/sample-spring-boot/manifest-otel-javaagent.yml
@@ -5,7 +5,7 @@ applications:
#
- name: otel-sample-app
instances: 1
- path: target/sample-app-spring-boot-4.0.0.jar
+ path: target/sample-app-spring-boot-4.0.1.jar
buildpack: sap_java_buildpack_jakarta
memory: 256M
random-route: true
@@ -16,7 +16,7 @@ applications:
LOG_REFERER: false
JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']"
JBP_CONFIG_SAP_MACHINE_JRE: '{ use_offline_repository: false, version: 17.+ }'
- JBP_CONFIG_JAVA_OPTS: '[from_environment: false, java_opts: ''-javaagent:BOOT-INF/lib/opentelemetry-javaagent-2.21.0.jar -Dotel.javaagent.extensions=BOOT-INF/lib/cf-java-logging-support-opentelemetry-agent-extension-4.0.0.jar -Dotel.logs.exporter=cloud-logging -Dotel.metrics.exporter=cloud-logging,dynatrace -Dotel.traces.exporter=cloud-logging -Dotel.instrumentation.logback-appender.experimental.capture-mdc-attributes=* -Dotel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes=true -Dotel.instrumentation.logback-appender.experimental.capture-code-attributes=true -Dotel.instrumentation.logback-appender.experimental-log-attributes=true -Dotel.experimental.resource.disabled-keys=process.command_line,process.command_args,process.executable.path -Dotel.javaagent.extension.sap.cf.binding.dynatrace.metrics.token-name=ingest_token'']'
+ JBP_CONFIG_JAVA_OPTS: '[from_environment: false, java_opts: ''-javaagent:BOOT-INF/lib/opentelemetry-javaagent-2.21.0.jar -Dotel.javaagent.extensions=BOOT-INF/lib/cf-java-logging-support-opentelemetry-agent-extension-4.0.1.jar -Dotel.logs.exporter=cloud-logging -Dotel.metrics.exporter=cloud-logging,dynatrace -Dotel.traces.exporter=cloud-logging -Dotel.instrumentation.logback-appender.experimental.capture-mdc-attributes=* -Dotel.instrumentation.logback-appender.experimental.capture-key-value-pair-attributes=true -Dotel.instrumentation.logback-appender.experimental.capture-code-attributes=true -Dotel.instrumentation.logback-appender.experimental-log-attributes=true -Dotel.experimental.resource.disabled-keys=process.command_line,process.command_args,process.executable.path -Dotel.javaagent.extension.sap.cf.binding.dynatrace.metrics.token-name=ingest_token'']'
services:
- cloud-logging
- dynatrace-service
diff --git a/sample-spring-boot/manifest.yml b/sample-spring-boot/manifest.yml
index 429bbda3..cacdc08d 100644
--- a/sample-spring-boot/manifest.yml
+++ b/sample-spring-boot/manifest.yml
@@ -5,7 +5,7 @@ applications:
#
- name: logging-sample-app
instances: 1
- path: target/sample-app-spring-boot-4.0.0.jar
+ path: target/sample-app-spring-boot-4.0.1.jar
buildpack: sap_java_buildpack
env:
# Set LOG_*: true to activate logging of respective field
diff --git a/sample-spring-boot/pom.xml b/sample-spring-boot/pom.xml
index ef2f249e..62433ee9 100644
--- a/sample-spring-boot/pom.xml
+++ b/sample-spring-boot/pom.xml
@@ -9,7 +9,7 @@
com.sap.hcp.cf.logging
cf-java-logging-support-parent
- 4.0.0
+ 4.0.1
../pom.xml