Skip to content

Commit 4559fdf

Browse files
committed
Add InstrumentationDefaults helper to declarative-config-bridge
Provides a utility for distributions to define instrumentation property defaults once and have them work with both traditional property-based configuration (otel.instrumentation.*) and declarative configuration (YAML model under instrumentation/development.java). Extracted from grafana/grafana-opentelemetry-java#1226. Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
1 parent 0c89207 commit 4559fdf

3 files changed

Lines changed: 180 additions & 0 deletions

File tree

declarative-config-bridge/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ group = "io.opentelemetry.instrumentation"
88

99
dependencies {
1010
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
11+
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-incubator")
1112
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
1213
implementation("io.opentelemetry:opentelemetry-api-incubator")
1314

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.config.bridge;
7+
8+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalInstrumentationModel;
9+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel;
10+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationPropertyModel;
11+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
12+
import java.util.HashMap;
13+
import java.util.LinkedHashMap;
14+
import java.util.Map;
15+
16+
/**
17+
* Defines instrumentation defaults that work with both traditional property-based configuration and
18+
* declarative configuration.
19+
*
20+
* <p>Usage:
21+
*
22+
* <pre>{@code
23+
* InstrumentationDefaults defaults = new InstrumentationDefaults();
24+
* defaults.setDefault("micrometer", "base_time_unit", "s");
25+
* defaults.setDefault("log4j_appender", "experimental_log_attributes", "true");
26+
*
27+
* // Declarative config mode: inject into model
28+
* customizer.addModelCustomizer(model -> defaults.applyToModel(model));
29+
*
30+
* // Traditional mode: translate to ConfigProperties
31+
* autoConfiguration.addPropertiesSupplier(defaults::toConfigProperties);
32+
* }</pre>
33+
*/
34+
public final class InstrumentationDefaults {
35+
36+
private final Map<String, Map<String, String>> instrumentations = new LinkedHashMap<>();
37+
38+
/**
39+
* Sets a default value for an instrumentation property. Keys use underscore notation (e.g. {@code
40+
* base_time_unit}); they are translated to hyphen notation when producing property keys.
41+
*
42+
* @return {@code this} for chaining
43+
*/
44+
public InstrumentationDefaults setDefault(String instrumentation, String key, String value) {
45+
instrumentations.computeIfAbsent(instrumentation, k -> new LinkedHashMap<>()).put(key, value);
46+
return this;
47+
}
48+
49+
/** Translates defaults to {@code otel.instrumentation.*} keys for auto-configuration. */
50+
public Map<String, String> toConfigProperties() {
51+
HashMap<String, String> map = new HashMap<>();
52+
instrumentations.forEach(
53+
(instrumentation, properties) ->
54+
properties.forEach(
55+
(key, value) ->
56+
map.put(
57+
"otel.instrumentation."
58+
+ instrumentation.replace('_', '-')
59+
+ "."
60+
+ key.replace('_', '-'),
61+
value)));
62+
return map;
63+
}
64+
65+
/**
66+
* Applies defaults to the declarative configuration model under {@code
67+
* instrumentation/development.java}. Existing values in the model take precedence; defaults are
68+
* only set for properties not already present.
69+
*/
70+
public OpenTelemetryConfigurationModel applyToModel(OpenTelemetryConfigurationModel model) {
71+
if (instrumentations.isEmpty()) {
72+
return model;
73+
}
74+
75+
ExperimentalInstrumentationModel instrumentation = model.getInstrumentationDevelopment();
76+
if (instrumentation == null) {
77+
instrumentation = new ExperimentalInstrumentationModel();
78+
model.withInstrumentationDevelopment(instrumentation);
79+
}
80+
ExperimentalLanguageSpecificInstrumentationModel java = instrumentation.getJava();
81+
if (java == null) {
82+
java = new ExperimentalLanguageSpecificInstrumentationModel();
83+
instrumentation.withJava(java);
84+
}
85+
86+
Map<String, ExperimentalLanguageSpecificInstrumentationPropertyModel> props =
87+
java.getAdditionalProperties();
88+
89+
for (Map.Entry<String, Map<String, String>> entry : instrumentations.entrySet()) {
90+
String name = entry.getKey();
91+
Map<String, String> defaults = entry.getValue();
92+
93+
ExperimentalLanguageSpecificInstrumentationPropertyModel propModel = props.get(name);
94+
if (propModel == null) {
95+
propModel = new ExperimentalLanguageSpecificInstrumentationPropertyModel();
96+
props.put(name, propModel);
97+
}
98+
99+
for (Map.Entry<String, String> defaultEntry : defaults.entrySet()) {
100+
propModel
101+
.getAdditionalProperties()
102+
.putIfAbsent(defaultEntry.getKey(), defaultEntry.getValue());
103+
}
104+
}
105+
106+
return model;
107+
}
108+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.config.bridge;
7+
8+
import static org.assertj.core.api.Assertions.assertThat;
9+
10+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
11+
import java.util.Map;
12+
import org.junit.jupiter.api.Test;
13+
14+
class InstrumentationDefaultsTest {
15+
16+
@Test
17+
void toConfigProperties() {
18+
InstrumentationDefaults defaults = new InstrumentationDefaults();
19+
defaults.setDefault("micrometer", "base_time_unit", "s");
20+
defaults.setDefault("log4j_appender", "experimental_log_attributes", "true");
21+
22+
Map<String, String> props = defaults.toConfigProperties();
23+
24+
assertThat(props)
25+
.containsEntry("otel.instrumentation.micrometer.base-time-unit", "s")
26+
.containsEntry("otel.instrumentation.log4j-appender.experimental-log-attributes", "true")
27+
.hasSize(2);
28+
}
29+
30+
@Test
31+
void applyToModel() {
32+
InstrumentationDefaults defaults = new InstrumentationDefaults();
33+
defaults.setDefault("micrometer", "base_time_unit", "s");
34+
35+
OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel();
36+
defaults.applyToModel(model);
37+
38+
assertThat(
39+
model
40+
.getInstrumentationDevelopment()
41+
.getJava()
42+
.getAdditionalProperties()
43+
.get("micrometer")
44+
.getAdditionalProperties())
45+
.containsEntry("base_time_unit", "s");
46+
}
47+
48+
@Test
49+
void applyToModelDoesNotOverrideExisting() {
50+
InstrumentationDefaults defaults = new InstrumentationDefaults();
51+
defaults.setDefault("micrometer", "base_time_unit", "s");
52+
53+
// Pre-populate model with a different value
54+
OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel();
55+
new InstrumentationDefaults()
56+
.setDefault("micrometer", "base_time_unit", "ms")
57+
.applyToModel(model);
58+
59+
// Apply defaults — should not override
60+
defaults.applyToModel(model);
61+
62+
assertThat(
63+
model
64+
.getInstrumentationDevelopment()
65+
.getJava()
66+
.getAdditionalProperties()
67+
.get("micrometer")
68+
.getAdditionalProperties())
69+
.containsEntry("base_time_unit", "ms");
70+
}
71+
}

0 commit comments

Comments
 (0)