diff --git a/CHANGELOG.md b/CHANGELOG.md
index fa1e59bb0fac..6d80bcafbadd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@
### ⚠️ Breaking Changes
+- Rename `otel.experimental.javascript-snippet` to
+ `otel.instrumentation.servlet.experimental.javascript-snippet` to follow naming conventions
+ ([#15339](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/15339))
+
+### ⚠️ Breaking Changes
+
- ActiveMQ Classic JMX metrics: rename attributes and metrics to align
with semantic conventions (see PR description for specifics)
([#14996](https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/14996))
diff --git a/docs/advanced-configuration-options.md b/docs/advanced-configuration-options.md
index 537253406497..4ae0e075c5d1 100644
--- a/docs/advanced-configuration-options.md
+++ b/docs/advanced-configuration-options.md
@@ -45,9 +45,9 @@ This feature is designed for integrating client-side monitoring.
We plan to integrate OpenTelemetry's own client-side monitoring solution by default once it's available
(see the [browser instrumentation proposal](https://github.com/open-telemetry/community/blob/main/projects/browser-phase-1.md)).
-| System property | Environment variable | Purpose |
-|--------------------------------------|--------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| otel.experimental.javascript-snippet | OTEL_EXPERIMENTAL_JAVASCRIPT_SNIPPET | Experimental setting to inject a JavaScript snippet into HTML responses after the opening `
` tag. The value should be a complete JavaScript snippet including `"` |
+| System property | Environment variable | Purpose |
+|----------------------------------------------------------------|----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `otel.instrumentation.servlet.experimental.javascript-snippet` | `OTEL_INSTRUMENTATION_SERVLET_EXPERIMENTAL_JAVASCRIPT_SNIPPET` | Experimental setting to inject a JavaScript snippet into HTML responses after the opening `` tag. The value should be a complete JavaScript snippet including `"` |
**Important notes:**
diff --git a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/InstrumentationConfig.java b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/InstrumentationConfig.java
index ab1bb761aa22..8b1dd6626c64 100644
--- a/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/InstrumentationConfig.java
+++ b/instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/config/internal/InstrumentationConfig.java
@@ -132,7 +132,7 @@ default List getList(String name) {
/**
* Returns the {@link ConfigProvider} if declarative configuration is used.
*
- * @return the {@link ConfigProvider} or {@code null} if no provider is available
+ * @return the {@link ConfigProvider} or {@code null} if declarative configuration is not used
*/
@Nullable
ConfigProvider getConfigProvider();
diff --git a/instrumentation-api/build.gradle.kts b/instrumentation-api/build.gradle.kts
index 3b798b826de2..38dfcbf8a6a5 100644
--- a/instrumentation-api/build.gradle.kts
+++ b/instrumentation-api/build.gradle.kts
@@ -22,6 +22,7 @@ dependencies {
testImplementation("io.opentelemetry.javaagent:opentelemetry-testing-common")
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
testImplementation("io.opentelemetry:opentelemetry-exporter-common")
+ testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator")
testImplementation("org.junit-pioneer:junit-pioneer")
jmhImplementation(project(":instrumentation-api-incubator"))
diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java
index 422d357349a3..d8fbbae07973 100644
--- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java
+++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/instrumenter/InstrumenterBuilder.java
@@ -49,11 +49,6 @@ public final class InstrumenterBuilder {
private static final Logger logger = Logger.getLogger(InstrumenterBuilder.class.getName());
- private static final SpanSuppressionStrategy spanSuppressionStrategy =
- SpanSuppressionStrategy.fromConfig(
- ConfigPropertiesUtil.getString(
- "otel.instrumentation.experimental.span-suppression-strategy"));
-
final OpenTelemetry openTelemetry;
final String instrumentationName;
SpanNameExtractor super REQUEST> spanNameExtractor;
@@ -373,8 +368,17 @@ private String getSchemaUrl() {
}
SpanSuppressor buildSpanSuppressor() {
+ // otel.instrumentation.experimental.* doesn't fit the usual pattern of configuration properties
+ // for instrumentations, so we need to handle both declarative and non-declarative configs here
+ String value =
+ ConfigPropertiesUtil.isDeclarativeConfig(openTelemetry)
+ ? ConfigPropertiesUtil.getString(
+ openTelemetry, "common", "span_suppression_strategy/development")
+ .orElse(null)
+ : ConfigPropertiesUtil.getString(
+ "otel.instrumentation.experimental.span-suppression-strategy");
return new SpanSuppressors.ByContextKey(
- spanSuppressionStrategy.create(getSpanKeysFromAttributesExtractors()));
+ SpanSuppressionStrategy.fromConfig(value).create(getSpanKeysFromAttributesExtractors()));
}
private Set getSpanKeysFromAttributesExtractors() {
diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/ConfigPropertiesUtil.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/ConfigPropertiesUtil.java
index 862cd6b13588..318be4b24eb8 100644
--- a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/ConfigPropertiesUtil.java
+++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/ConfigPropertiesUtil.java
@@ -5,9 +5,16 @@
package io.opentelemetry.instrumentation.api.internal;
+import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;
+import static java.util.Collections.emptyList;
+
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.incubator.ExtendedOpenTelemetry;
+import io.opentelemetry.api.incubator.config.ConfigProvider;
+import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import java.util.Arrays;
import java.util.List;
-import java.util.Locale;
+import java.util.Optional;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
@@ -17,11 +24,51 @@
*/
public final class ConfigPropertiesUtil {
+ private static final boolean supportsDeclarativeConfig = supportsDeclarativeConfig();
+
+ private static boolean supportsDeclarativeConfig() {
+ try {
+ Class.forName("io.opentelemetry.api.incubator.ExtendedOpenTelemetry");
+ return true;
+ } catch (ClassNotFoundException e) {
+ // The incubator module is not available.
+ // This only happens in OpenTelemetry API instrumentation tests, where an older version of
+ // OpenTelemetry API is used that does not have ExtendedOpenTelemetry.
+ // Having the incubator module without ExtendedOpenTelemetry class should still return false
+ // for those tests to avoid a ClassNotFoundException.
+ return false;
+ }
+ }
+
+ /**
+ * Returns the boolean value of the given property name from system properties and environment
+ * variables.
+ *
+ *
It's recommended to use {@link #getBoolean(OpenTelemetry, String...)} instead to support
+ * Declarative Config.
+ */
public static boolean getBoolean(String propertyName, boolean defaultValue) {
String strValue = getString(propertyName);
return strValue == null ? defaultValue : Boolean.parseBoolean(strValue);
}
+ /**
+ * Returns the boolean value of the given property name from Declarative Config if available,
+ * otherwise falls back to system properties and environment variables.
+ */
+ public static Optional getBoolean(OpenTelemetry openTelemetry, String... propertyName) {
+ DeclarativeConfigProperties node = getDeclarativeConfigNode(openTelemetry, propertyName);
+ if (node != null) {
+ return Optional.ofNullable(node.getBoolean(leaf(propertyName)));
+ }
+ String strValue = getString(toSystemProperty(propertyName));
+ return strValue == null ? Optional.empty() : Optional.of(Boolean.parseBoolean(strValue));
+ }
+
+ /**
+ * Returns the int value of the given property name from system properties and environment
+ * variables.
+ */
public static int getInt(String propertyName, int defaultValue) {
String strValue = getString(propertyName);
if (strValue == null) {
@@ -34,26 +81,47 @@ public static int getInt(String propertyName, int defaultValue) {
}
}
+ /**
+ * Returns the string value of the given property name from system properties and environment
+ * variables.
+ *
+ *
It's recommended to use {@link #getString(OpenTelemetry, String...)} instead to support
+ * Declarative Config.
+ */
@Nullable
public static String getString(String propertyName) {
- String value = System.getProperty(propertyName);
- if (value != null) {
- return value;
- }
- return System.getenv(toEnvVarName(propertyName));
+ return ConfigUtil.getString(ConfigUtil.normalizePropertyKey(propertyName));
}
- public static String getString(String propertyName, String defaultValue) {
- String strValue = getString(propertyName);
- return strValue == null ? defaultValue : strValue;
+ /**
+ * Returns the string value of the given property name from Declarative Config if available,
+ * otherwise falls back to system properties and environment variables.
+ */
+ public static Optional getString(OpenTelemetry openTelemetry, String... propertyName) {
+ DeclarativeConfigProperties node = getDeclarativeConfigNode(openTelemetry, propertyName);
+ if (node != null) {
+ return Optional.ofNullable(node.getString(leaf(propertyName)));
+ }
+ return Optional.ofNullable(getString(toSystemProperty(propertyName)));
}
- public static List getList(String propertyName, List defaultValue) {
- String value = getString(propertyName);
- if (value == null) {
- return defaultValue;
+ /**
+ * Returns the list of strings value of the given property name from Declarative Config if
+ * available, otherwise falls back to system properties and environment variables.
+ */
+ public static List getList(OpenTelemetry openTelemetry, String... propertyName) {
+ DeclarativeConfigProperties node = getDeclarativeConfigNode(openTelemetry, propertyName);
+ if (node != null) {
+ return node.getScalarList(leaf(propertyName), String.class, emptyList());
}
- return filterBlanksAndNulls(value.split(","));
+ return Optional.ofNullable(getString(toSystemProperty(propertyName)))
+ .map(value -> filterBlanksAndNulls(value.split(",")))
+ .orElse(emptyList());
+ }
+
+ /** Returns true if the given OpenTelemetry instance supports Declarative Config. */
+ public static boolean isDeclarativeConfig(OpenTelemetry openTelemetry) {
+ return supportsDeclarativeConfig && openTelemetry instanceof ExtendedOpenTelemetry;
}
private static List filterBlanksAndNulls(String[] values) {
@@ -63,8 +131,45 @@ private static List filterBlanksAndNulls(String[] values) {
.collect(Collectors.toList());
}
- private static String toEnvVarName(String propertyName) {
- return propertyName.toUpperCase(Locale.ROOT).replace('-', '_').replace('.', '_');
+ private static String leaf(String[] propertyName) {
+ return propertyName[propertyName.length - 1];
+ }
+
+ @Nullable
+ private static DeclarativeConfigProperties getDeclarativeConfigNode(
+ OpenTelemetry openTelemetry, String... propertyName) {
+ if (isDeclarativeConfig(openTelemetry)) {
+ ExtendedOpenTelemetry extendedOpenTelemetry = (ExtendedOpenTelemetry) openTelemetry;
+ ConfigProvider configProvider = extendedOpenTelemetry.getConfigProvider();
+ return getConfigProperties(configProvider, propertyName);
+ }
+ return null;
+ }
+
+ /** Returns the DeclarativeConfigProperties node for the given property name parts. */
+ public static DeclarativeConfigProperties getConfigProperties(
+ ConfigProvider configProvider, String[] propertyName) {
+ DeclarativeConfigProperties instrumentationConfig = configProvider.getInstrumentationConfig();
+ if (instrumentationConfig == null) {
+ return empty();
+ }
+ DeclarativeConfigProperties node = instrumentationConfig.getStructured("java", empty());
+ // last part is the leaf property
+ for (int i = 0; i < propertyName.length - 1; i++) {
+ node = node.getStructured(propertyName[i], empty());
+ }
+ return node;
+ }
+
+ public static String toSystemProperty(String[] propertyName) {
+ for (int i = 0; i < propertyName.length; i++) {
+ String node = propertyName[i];
+ if (node.endsWith("/development")) {
+ String prefix = node.contains("experimental") ? "" : "experimental.";
+ propertyName[i] = prefix + node.substring(0, node.length() - 12);
+ }
+ }
+ return "otel.instrumentation." + String.join(".", propertyName).replace('_', '-');
}
private ConfigPropertiesUtil() {}
diff --git a/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/ConfigUtil.java b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/ConfigUtil.java
new file mode 100644
index 000000000000..701545845fba
--- /dev/null
+++ b/instrumentation-api/src/main/java/io/opentelemetry/instrumentation/api/internal/ConfigUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.instrumentation.api.internal;
+
+import java.util.ConcurrentModificationException;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import javax.annotation.Nullable;
+
+/**
+ * Configuration utilities.
+ *
+ *
This class is internal and is hence not for public use. Its APIs are unstable and can change
+ * at any time.
+ *
+ *
Copied from SDK
+ * because some tests target an SDK version where this class does not exist.
+ */
+final class ConfigUtil {
+
+ private ConfigUtil() {}
+
+ /**
+ * Returns a copy of system properties which is safe to iterate over.
+ *
+ *
In java 8 and android environments, iterating through system properties may trigger {@link
+ * ConcurrentModificationException}. This method ensures callers can iterate safely without risk
+ * of exception. See https://github.com/open-telemetry/opentelemetry-java/issues/6732 for details.
+ */
+ public static Properties safeSystemProperties() {
+ return (Properties) System.getProperties().clone();
+ }
+
+ /**
+ * Return the system property or environment variable for the {@code key}.
+ *
+ *
Normalize the {@code key} using {@link #normalizePropertyKey(String)}. Match to system
+ * property keys also normalized with {@link #normalizePropertyKey(String)}. Match to environment
+ * variable keys normalized with {@link #normalizeEnvironmentVariableKey(String)}. System
+ * properties take priority over environment variables.
+ *
+ * @param key the property key
+ * @return the system property if not null, or the environment variable if not null, or {@code
+ * null}
+ */
+ @Nullable
+ public static String getString(String key) {
+ String normalizedKey = normalizePropertyKey(key);
+
+ for (Map.Entry