Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
51a532f
add system properties bridge
zeitlinger Nov 17, 2025
68d64c8
add system properties bridge
zeitlinger Nov 17, 2025
2acda70
add tests
zeitlinger Nov 17, 2025
edc6d1c
add list
zeitlinger Nov 18, 2025
a78bf62
refactor
zeitlinger Nov 18, 2025
ac66918
refactor
zeitlinger Nov 18, 2025
91b03f3
string
zeitlinger Nov 18, 2025
6aa96f1
string
zeitlinger Nov 18, 2025
fff5430
string
zeitlinger Nov 18, 2025
25f2ea3
string
zeitlinger Nov 18, 2025
1946fd5
string
zeitlinger Nov 18, 2025
6abc0e3
string
zeitlinger Nov 18, 2025
9bec486
string
zeitlinger Nov 18, 2025
74f7221
string
zeitlinger Nov 18, 2025
ee0046e
javadoc
zeitlinger Nov 18, 2025
4128283
add contract
zeitlinger Nov 18, 2025
69d5bc6
add contract
zeitlinger Nov 18, 2025
2db4921
remove contract
zeitlinger Nov 18, 2025
4d5340d
remove contract
zeitlinger Nov 18, 2025
05d3ede
inline method to avoid class not found exception for DeclarativeConfi…
zeitlinger Nov 18, 2025
501a769
pr review
zeitlinger Nov 25, 2025
5c30b44
use optional instead of providing default for boolean
zeitlinger Nov 25, 2025
c5c34ed
use optional instead of providing default for int
zeitlinger Nov 25, 2025
7ca08d7
use optional instead of providing default for list
zeitlinger Nov 25, 2025
1d72e01
use optional instead of providing default for string
zeitlinger Nov 25, 2025
669236a
fix
zeitlinger Nov 25, 2025
e7072ba
fix
zeitlinger Nov 25, 2025
d921bab
fix
zeitlinger Nov 25, 2025
6724cb7
split pr
zeitlinger Nov 27, 2025
d1a9784
split pr
zeitlinger Nov 27, 2025
41aac72
fix
zeitlinger Nov 27, 2025
9245a86
fix
zeitlinger Nov 27, 2025
278259e
pr review
zeitlinger Dec 3, 2025
a3fbf23
revert aws test
zeitlinger Dec 3, 2025
93adb59
use "/development" instead of "experimental" in declarative config
zeitlinger Dec 3, 2025
7c81df5
use "/development" instead of "experimental" in declarative config
zeitlinger Dec 3, 2025
7bc8ed9
fix
zeitlinger Dec 3, 2025
91b0050
docs
zeitlinger Dec 3, 2025
d9f56ba
avoid double bridge
zeitlinger Dec 4, 2025
b542ea1
normalize system props before lookup to avoid ambiguity of "experimen…
zeitlinger Dec 4, 2025
6c889a7
normalize system props before lookup to avoid ambiguity of "experimen…
zeitlinger Dec 4, 2025
2cc9cf0
normalize system props before lookup to avoid ambiguity of "experimen…
zeitlinger Dec 5, 2025
fe2886b
normalize system props before lookup to avoid ambiguity of "experimen…
zeitlinger Dec 5, 2025
c9d0e19
Merge branch 'main' into system-properties-bridge
zeitlinger Dec 5, 2025
d7d0ba8
rename js snippet property
zeitlinger Dec 5, 2025
d2f1b6b
normalize system props before lookup to avoid ambiguity of "experimen…
zeitlinger Dec 5, 2025
037aebb
add changelog
zeitlinger Dec 7, 2025
b0079bd
format
zeitlinger Dec 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

### ⚠️ 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))

## Version 2.22.0 (2025-11-20)

### ⚠️ Breaking Changes
Expand Down
6 changes: 3 additions & 3 deletions docs/advanced-configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<head>` tag. The value should be a complete JavaScript snippet including `<script>` tags if needed, e.g. `-Dotel.experimental.javascript-snippet="<script>console.log('Hello world!');</script>"` |
| 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 `<head>` tag. The value should be a complete JavaScript snippet including `<script>` tags if needed, e.g. `-Dotel.instrumentation.servlet.experimental.javascript-snippet="<script>console.log('Hello world!');</script>"` |

**Important notes:**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ default List<String> 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();
Expand Down
1 change: 1 addition & 0 deletions instrumentation-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@ public final class InstrumenterBuilder<REQUEST, RESPONSE> {

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;
Expand Down Expand Up @@ -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");
Comment on lines +374 to +379
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be unified now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can if we accept another breaking change (the common part)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, I missed that, this one may be more used, can you open an issue to change it in next major version bump (3.0)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return new SpanSuppressors.ByContextKey(
spanSuppressionStrategy.create(getSpanKeysFromAttributesExtractors()));
SpanSuppressionStrategy.fromConfig(value).create(getSpanKeysFromAttributesExtractors()));
}

private Set<SpanKey> getSpanKeysFromAttributesExtractors() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,21 @@

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.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

Expand All @@ -17,11 +29,70 @@
*/
public final class ConfigPropertiesUtil {

private static final boolean supportsDeclarativeConfig = supportsDeclarativeConfig();

private static final Map<String, String> config = load();

public static Map<String, String> load() {
Map<String, String> config = new HashMap<>();
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
config.put(normalizeEnvironmentVariableKey(entry.getKey()), entry.getValue());
}
for (Map.Entry<Object, Object> entry : safeSystemProperties().entrySet()) {
config.put(normalizePropertyKey(entry.getKey().toString()), entry.getValue().toString());
}
return config;
}

/** Resets the cached config for testing purposes. */
public static void resetForTest() {
config.clear();
config.putAll(load());
}

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.
*
* <p>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<Boolean> 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) {
Expand All @@ -34,26 +105,47 @@ public static int getInt(String propertyName, int defaultValue) {
}
}

/**
* Returns the string value of the given property name from system properties and environment
* variables.
*
* <p>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 config.get(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<String> 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<String> getList(String propertyName, List<String> 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<String> 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<String> filterBlanksAndNulls(String[] values) {
Expand All @@ -63,8 +155,67 @@ private static List<String> 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++) {
if (propertyName[i].endsWith("/development")) {
propertyName[i] =
"experimental." + propertyName[i].substring(0, propertyName[i].length() - 12);
}
}
return "otel.instrumentation." + String.join(".", propertyName).replace('_', '-');
}

/**
* Normalize an environment variable key by converting to lower case and replacing "_" with ".".
*/
public static String normalizeEnvironmentVariableKey(String key) {
return key.toLowerCase(Locale.ROOT).replace("_", ".");
}

/** Normalize a property key by converting to lower case and replacing "-" with ".". */
public static String normalizePropertyKey(String key) {
return key.toLowerCase(Locale.ROOT).replace("-", ".");
}

/**
* Returns a copy of system properties which is safe to iterate over.
*
* <p>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();
}

private ConfigPropertiesUtil() {}
Expand Down
Loading
Loading