Skip to content

Commit 171042d

Browse files
authored
provide experimental base declarative config support (#1089)
* add minimal config for inferred spans * move span processors order + add baggage opt-in * fix declarative config * disable user-agent customization * test more declarative config * remove implicit spring args * remove filter as it's not required * make config minimal + update test * add rule-based-sampler config * fix the rule based sampler default 100% rate * disable example rules as they don't work as expected * add changelog * restore filter until upstream does it by default * re-enable rule-based examples * remove user-agent from declarative config * fix things
1 parent 947aca7 commit 171042d

5 files changed

Lines changed: 109 additions & 200 deletions

File tree

custom/src/main/java/co/elastic/otel/declarativeconfig/ElasticDeclarativeConfigurationCustomizer.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ public void customize(DeclarativeConfigurationCustomizer customizer) {
6767
}
6868

6969
customizeResources(model);
70-
customizeUserAgent(model);
7170
return model;
7271
});
7372
}

custom/src/main/resources/co/elastic/otel/config.yaml

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,27 @@ propagator:
3131

3232
tracer_provider:
3333
processors:
34+
35+
# Add some baggage entries as span attributes, wildcards are supported.
36+
# While both included/excluded can be used, we recommend using 'included' to opt-in only for relevant baggage entries.
37+
- baggage:
38+
included: [ 'example-baggage-key-*' ]
39+
40+
# Requires the span-stacktrace extension (included in EDOT)
41+
# https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/span-stacktrace
42+
- stacktrace/development:
43+
# TODO: remove filtering once https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2803 is merged and released
44+
filter: co.elastic.otel.SpanStackTraceFilter
45+
46+
# Requires the inferred-spans extension (included in EDOT)
47+
# https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/inferred-spans
48+
- inferred_spans/development:
49+
# disabled by default, opt-in
50+
enabled: ${OTEL_INFERRED_SPANS_ENABLED:-false}
51+
# avoid log messages about missing JVM symbols by default
52+
logging_enabled: ${OTEL_INFERRED_SPANS_LOGGING_ENABLED:-false}
53+
54+
# Batch span exporter, must come last
3455
- batch:
3556
exporter:
3657
# get endpoint from OTEL_EXPORTER_OTLP_ENDPOINT environment variable if set, otherwise fallback to local collector
@@ -42,24 +63,43 @@ tracer_provider:
4263
# otlp_grpc:
4364
# endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}
4465
# headers_list: ${OTEL_EXPORTER_OTLP_HEADERS}
45-
- stacktrace/development:
46-
min_duration: 5 # minimal duration in ms, default is 5
47-
filter: co.elastic.otel.SpanStackTraceFilter
48-
# sampler, parent-based with 100% sampling rate by default
66+
67+
# Sampler, parent-based with 100% sampling rate by default
4968
sampler:
50-
parent_based:
51-
root:
52-
# using the consistent sampler implementation
53-
probability/development:
54-
ratio: 1.0
55-
remote_parent_sampled:
56-
always_on:
57-
remote_parent_not_sampled:
58-
always_off:
59-
local_parent_sampled:
60-
always_on:
61-
local_parent_not_sampled:
62-
always_off:
69+
composite/development:
70+
rule_based:
71+
rules:
72+
# FIXME those examples have an extra 'dummy-work-around' exclusion to avoid issue fixed in
73+
# https://github.com/open-telemetry/opentelemetry-java/pull/8356
74+
#
75+
# filter-out noisy spans from span attributes provided on span start
76+
# example 1: filter on user-agent header
77+
- attribute_patterns:
78+
key: user_agent.original
79+
included: [ 'example-noisy-user-agent/*']
80+
excluded: [ 'dummy-work-around']
81+
sampler:
82+
always_off:
83+
# example 2: filter on HTTP request path
84+
- attribute_patterns:
85+
key: url.path
86+
included: [ '/healthcheck-to-ignore-*']
87+
excluded: [ 'dummy-work-around']
88+
sampler:
89+
always_off:
90+
# example 3: filter on messaging destination
91+
- attribute_patterns:
92+
key: messaging.destination.name
93+
included: [ 'example-noisy-destination/*']
94+
excluded: [ 'dummy-work-around']
95+
sampler:
96+
always_off:
97+
# default sampler after rules filtering: parent-based with 100% sampling rate
98+
- sampler:
99+
parent_threshold:
100+
root:
101+
probability:
102+
ratio: 1.0
63103

64104
meter_provider:
65105
readers:
@@ -79,6 +119,13 @@ meter_provider:
79119

80120
logger_provider:
81121
processors:
122+
123+
# Add some baggage entries as log attributes, wildcards are supported
124+
# While both included/excluded can be used, we recommend using 'include' to opt-in only for relevant baggage entries.
125+
- baggage:
126+
included: [ 'example-baggage-key-*' ]
127+
128+
# Batch log exporter, must come last
82129
- batch:
83130
exporter:
84131
# get endpoint from OTEL_EXPORTER_OTLP_ENDPOINT environment variable if set, otherwise fallback to local collector

custom/src/test/java/co/elastic/otel/declarativeconfig/DefaultDeclarativeConfigTest.java

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@
2525

2626
import io.opentelemetry.javaagent.tooling.resources.ResourceCustomizerProvider;
2727
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration;
28+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableRuleBasedSamplerRuleModel;
2829
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel;
2930
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
3031
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SamplerModel;
3132
import java.io.InputStream;
33+
import java.util.List;
3234
import java.util.function.Consumer;
3335
import net.javacrumbs.jsonunit.assertj.JsonListAssert;
3436
import org.junit.jupiter.api.Test;
@@ -68,25 +70,35 @@ void testDefaults() {
6870
json("{\"service\":{}}"),
6971
json("{\"host\":{}}"));
7072

73+
// propagators
7174
assertThat(config.getPropagator()).describedAs("missing propagator").isNotNull();
7275
assertThatJson(json(config.getPropagator()))
7376
.inPath("composite")
7477
.isArray()
7578
.describedAs("propagators should contain tracecontext and baggage by default")
7679
.containsExactlyInAnyOrder(json("{\"tracecontext\":{}}"), json("{\"baggage\":{}}"));
7780

81+
// spans
7882
assertThat(config.getTracerProvider()).isNotNull();
79-
assertThat(config.getTracerProvider().getProcessors()).hasSize(2);
80-
assertThatJson(json((config.getTracerProvider().getProcessors().get(0))))
81-
.inPath("batch.exporter.otlp_http")
83+
assertThat(config.getTracerProvider().getProcessors()).hasSize(4);
84+
85+
// we don't check baggage configuration, just its presence by default
86+
assertThatJson(json(config.getTracerProvider().getProcessors().get(0)))
87+
.inPath("baggage")
88+
.isObject();
89+
90+
assertThatJson(json(config.getTracerProvider().getProcessors().get(2)))
91+
.inPath("inferred_spans/development")
8292
.isObject()
83-
.containsEntry("endpoint", "http://localhost:4318/v1/traces");
93+
.containsEntry("enabled", false) // opt-in, disabled by default
94+
.containsEntry("logging_enabled", false); // silent by default
8495

85-
assertThatJson(json((config.getTracerProvider().getProcessors().get(1))))
86-
.inPath("stacktrace/development")
96+
assertThatJson(json(config.getTracerProvider().getProcessors().get(3)))
97+
.inPath("batch.exporter.otlp_http")
8798
.isObject()
88-
.containsEntry("filter", "co.elastic.otel.SpanStackTraceFilter");
99+
.containsEntry("endpoint", "http://localhost:4318/v1/traces");
89100

101+
// metrics
90102
assertThat(config.getMeterProvider()).isNotNull();
91103
assertThat(config.getMeterProvider().getReaders()).hasSize(1);
92104
assertThatJson(json(config.getMeterProvider().getReaders().get(0)))
@@ -95,32 +107,32 @@ void testDefaults() {
95107
.containsEntry("endpoint", "http://localhost:4318/v1/metrics")
96108
.containsEntry("temporality_preference", "delta");
97109

110+
// logs
98111
assertThat(config.getLoggerProvider()).isNotNull();
99-
assertThat(config.getLoggerProvider().getProcessors()).hasSize(1);
112+
assertThat(config.getLoggerProvider().getProcessors()).hasSize(2);
113+
114+
// we don't check baggage configuration, just its presence by default
100115
assertThatJson(json((config.getLoggerProvider().getProcessors().get(0))))
116+
.inPath("baggage")
117+
.isObject();
118+
119+
assertThatJson(json((config.getLoggerProvider().getProcessors().get(1))))
101120
.inPath("batch.exporter.otlp_http")
102121
.isObject()
103122
.containsEntry("endpoint", "http://localhost:4318/v1/logs");
104123

105124
SamplerModel sampler = config.getTracerProvider().getSampler();
106125
assertThat(sampler).isNotNull();
107126

108-
assertThatJson(json(sampler))
109-
.inPath("parent_based.root.probability/development.ratio")
127+
// we only check that we are using the rule-based sampler and that the default (last)
128+
// is the one we expect with 100% sampling
129+
assertThatJson(json(sampler)).inPath("composite/development.rule_based.rules").isArray();
130+
List<ExperimentalComposableRuleBasedSamplerRuleModel> rules =
131+
sampler.getCompositeDevelopment().getRuleBased().getRules();
132+
assertThatJson(json(rules.get(rules.size() - 1)))
133+
.inPath("sampler.parent_threshold.root.probability.ratio")
110134
.isNumber()
111135
.isEqualByComparingTo("1.0");
112-
assertThatJson(json(sampler))
113-
.inPath("parent_based.remote_parent_sampled.always_on")
114-
.isObject();
115-
assertThatJson(json(sampler))
116-
.inPath("parent_based.remote_parent_not_sampled.always_off")
117-
.isObject();
118-
assertThatJson(json(sampler))
119-
.inPath("parent_based.local_parent_sampled.always_on")
120-
.isObject();
121-
assertThatJson(json(sampler))
122-
.inPath("parent_based.local_parent_not_sampled.always_off")
123-
.isObject();
124136

125137
assertThat(config.getInstrumentationDevelopment()).isNotNull();
126138
ExperimentalLanguageSpecificInstrumentationModel java =

custom/src/test/java/co/elastic/otel/declarativeconfig/ElasticDeclarativeConfigurationCustomizerTest.java

Lines changed: 0 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,16 @@
2323
import static org.assertj.core.api.Assertions.assertThat;
2424

2525
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
26-
import io.opentelemetry.javaagent.tooling.AgentVersion;
2726
import io.opentelemetry.javaagent.tooling.resources.ResourceCustomizerProvider;
2827
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizer;
2928
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider;
30-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel;
31-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessorModel;
32-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel;
33-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessorModel;
34-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LoggerProviderModel;
35-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MeterProviderModel;
36-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReaderModel;
37-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.NameStringValuePairModel;
3829
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
39-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpGrpcExporterModel;
40-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpGrpcMetricExporterModel;
41-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpHttpExporterModel;
42-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpHttpMetricExporterModel;
43-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReaderModel;
44-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PushMetricExporterModel;
45-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SimpleLogRecordProcessorModel;
46-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SimpleSpanProcessorModel;
47-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanExporterModel;
48-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SpanProcessorModel;
49-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.TracerProviderModel;
5030
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
5131
import io.opentelemetry.sdk.metrics.export.MetricExporter;
5232
import io.opentelemetry.sdk.trace.export.SpanExporter;
53-
import java.util.Arrays;
54-
import java.util.Collections;
55-
import java.util.HashMap;
56-
import java.util.Map;
5733
import java.util.concurrent.atomic.AtomicReference;
5834
import java.util.function.BiFunction;
5935
import java.util.function.Function;
60-
import org.jetbrains.annotations.NotNull;
6136
import org.junit.jupiter.api.Test;
6237
import org.junit.jupiter.params.ParameterizedTest;
6338
import org.junit.jupiter.params.provider.ValueSource;
@@ -109,141 +84,6 @@ void distributionResourceProvider(boolean elasticFirst) {
10984
json("{\"elastic_distribution\":null}]}"));
11085
}
11186

112-
@ParameterizedTest
113-
@ValueSource(strings = {"grpc", "http"})
114-
void userAgent(String protocol) {
115-
// setup tracer provider with single + batch exporter
116-
// check that the user-agent value is set as expected
117-
OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel();
118-
119-
SpanExporterModel spanExporter = new SpanExporterModel();
120-
PushMetricExporterModel metricExporter = new PushMetricExporterModel();
121-
LogRecordExporterModel logExporter = new LogRecordExporterModel();
122-
123-
switch (protocol) {
124-
case "grpc":
125-
spanExporter.withOtlpGrpc(new OtlpGrpcExporterModel());
126-
metricExporter.withOtlpGrpc(new OtlpGrpcMetricExporterModel());
127-
logExporter.withOtlpGrpc(new OtlpGrpcExporterModel());
128-
break;
129-
case "http":
130-
spanExporter.withOtlpHttp(new OtlpHttpExporterModel());
131-
metricExporter.withOtlpHttp(new OtlpHttpMetricExporterModel());
132-
logExporter.withOtlpHttp(new OtlpHttpExporterModel());
133-
break;
134-
default:
135-
throw new IllegalArgumentException("Unsupported protocol: " + protocol);
136-
}
137-
138-
// tracer provider
139-
SpanProcessorModel spanSimpleProcessor =
140-
new SpanProcessorModel()
141-
.withSimple(new SimpleSpanProcessorModel().withExporter(spanExporter));
142-
SpanProcessorModel spanBatchProcessor =
143-
new SpanProcessorModel()
144-
.withBatch(new BatchSpanProcessorModel().withExporter(spanExporter));
145-
model.withTracerProvider(
146-
new TracerProviderModel()
147-
.withProcessors(Arrays.asList(spanSimpleProcessor, spanBatchProcessor)));
148-
149-
// meter provider
150-
model.withMeterProvider(
151-
new MeterProviderModel()
152-
.withReaders(
153-
Collections.singletonList(
154-
new MetricReaderModel()
155-
.withPeriodic(
156-
new PeriodicMetricReaderModel().withExporter(metricExporter)))));
157-
158-
// logger provider
159-
LogRecordProcessorModel logSimpleProcessor =
160-
new LogRecordProcessorModel()
161-
.withSimple(new SimpleLogRecordProcessorModel().withExporter(logExporter));
162-
LogRecordProcessorModel logBatchProcessor =
163-
new LogRecordProcessorModel()
164-
.withBatch(new BatchLogRecordProcessorModel().withExporter(logExporter));
165-
model.withLoggerProvider(
166-
new LoggerProviderModel()
167-
.withProcessors(Arrays.asList(logSimpleProcessor, logBatchProcessor)));
168-
169-
model = applyConfigCustomize(model, new ElasticDeclarativeConfigurationCustomizer());
170-
171-
// tracer provider
172-
assertThat(model.getTracerProvider()).isNotNull();
173-
assertThatJson(model.getTracerProvider()).inPath("processors").isArray().hasSize(2);
174-
for (int i = 0; i < 2; i++) {
175-
String pathPrefix = i == 0 ? "simple" : "batch";
176-
assertThatJson(model.getTracerProvider().getProcessors().get(i))
177-
.inPath(pathPrefix + ".exporter.otlp_" + protocol + ".headers")
178-
.isArray()
179-
.contains(userAgentHeader("elastic-otlp-" + protocol + "-java/" + AgentVersion.VERSION));
180-
}
181-
182-
// meter provider
183-
assertThat(model.getMeterProvider()).isNotNull();
184-
assertThatJson(model.getMeterProvider()).inPath("readers").isArray().hasSize(1);
185-
assertThatJson(model.getMeterProvider().getReaders().get(0))
186-
.inPath("periodic.exporter.otlp_" + protocol + ".headers")
187-
.isArray()
188-
.contains(userAgentHeader("elastic-otlp-" + protocol + "-java/" + AgentVersion.VERSION));
189-
190-
// logger provider
191-
assertThat(model.getLoggerProvider()).isNotNull();
192-
assertThatJson(model.getLoggerProvider()).inPath("processors").isArray().hasSize(2);
193-
for (int i = 0; i < 2; i++) {
194-
String pathPrefix = i == 0 ? "simple" : "batch";
195-
assertThatJson(model.getLoggerProvider().getProcessors().get(i))
196-
.inPath(pathPrefix + ".exporter.otlp_" + protocol + ".headers")
197-
.isArray()
198-
.contains(userAgentHeader("elastic-otlp-" + protocol + "-java/" + AgentVersion.VERSION));
199-
}
200-
}
201-
202-
@Test
203-
void addUserAgentPriority() {
204-
assertThat(ElasticDeclarativeConfigurationCustomizer.addUserAgent(null, null, "my-user-agent"))
205-
.describedAs("add user agent when none is present in headers list or headers")
206-
.hasSize(1)
207-
.flatExtracting("name", "value")
208-
.contains("User-Agent", "my-user-agent");
209-
210-
assertThat(
211-
ElasticDeclarativeConfigurationCustomizer.addUserAgent(
212-
null, Collections.emptyList(), "my-user-agent"))
213-
.describedAs("add user agent when none is present in headers list or headers")
214-
.hasSize(1)
215-
.flatExtracting("name", "value")
216-
.contains("User-Agent", "my-user-agent");
217-
218-
assertThat(
219-
ElasticDeclarativeConfigurationCustomizer.addUserAgent(
220-
// using non-standard case is intentional for testing
221-
"User-agent=custom-user-agent", null, "my-user-agent"))
222-
.describedAs("configured user-agent is preserved")
223-
.isNull();
224-
225-
assertThat(
226-
ElasticDeclarativeConfigurationCustomizer.addUserAgent(
227-
null,
228-
Collections.singletonList(
229-
new NameStringValuePairModel()
230-
.withName("user-agent") // using lower-case is intentional for testing
231-
.withValue("custom-user-Agent")),
232-
"my-user-agent"))
233-
.describedAs("configured user-agent is preserved")
234-
.hasSize(1)
235-
.flatExtracting("name", "value")
236-
.contains("user-agent", "custom-user-Agent");
237-
}
238-
239-
@NotNull
240-
private static Map<String, String> userAgentHeader(String value) {
241-
Map<String, String> header = new HashMap<>();
242-
header.put("name", "User-Agent");
243-
header.put("value", value);
244-
return header;
245-
}
246-
24787
static OpenTelemetryConfigurationModel applyConfigCustomize(
24888
OpenTelemetryConfigurationModel originalModel,
24989
DeclarativeConfigurationCustomizerProvider customizerProvider) {

0 commit comments

Comments
 (0)