Skip to content

Commit 04bb692

Browse files
authored
switch alternative approach to declarative config (#1060)
* start declarative config alternative approach * add simple '--default-config-yaml' CLI option * remove extra } in json * avoid npe when unable to parse config * first working test * assert on resource attributes * fix things * code review: check for duplicated resource attributes * minor fixes * code reformat
1 parent 948ccf7 commit 04bb692

9 files changed

Lines changed: 410 additions & 83 deletions

File tree

agent/entrypoint/src/main/java/co/elastic/otel/agent/ElasticAgent.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
package co.elastic.otel.agent;
2020

2121
import io.opentelemetry.javaagent.OpenTelemetryAgent;
22+
import java.io.IOException;
23+
import java.io.InputStream;
2224
import java.lang.instrument.Instrumentation;
25+
import java.util.Objects;
2326

2427
/** Elastic agent entry point, delegates to OpenTelemetry agent */
2528
public class ElasticAgent {
@@ -56,7 +59,36 @@ public static void agentmain(String agentArgs, Instrumentation inst) {
5659
* @param args arguments
5760
*/
5861
public static void main(String[] args) {
59-
OpenTelemetryAgent.main(args);
62+
boolean upstreamDefault = true;
63+
for (String arg : args) {
64+
switch (arg) {
65+
case "--default-config-yaml":
66+
printDefaultConfigYaml();
67+
upstreamDefault = false;
68+
break;
69+
}
70+
}
71+
72+
if (upstreamDefault) {
73+
OpenTelemetryAgent.main(args);
74+
}
75+
}
76+
77+
private static void printDefaultConfigYaml() {
78+
try (InputStream input =
79+
ElasticAgent.class
80+
.getClassLoader()
81+
.getResourceAsStream("inst/co/elastic/otel/config.yaml")) {
82+
Objects.requireNonNull(input, "Default config yaml resource is missing");
83+
84+
byte[] buffer = new byte[8192];
85+
int read;
86+
while ((read = input.read(buffer)) != -1) {
87+
System.out.write(buffer, 0, read);
88+
}
89+
} catch (IOException e) {
90+
throw new IllegalStateException("Failed to print default config yaml", e);
91+
}
6092
}
6193

6294
private static void initLogging() {

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

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@
2929
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider;
3030
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel;
3131
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessorModel;
32-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalInstrumentationModel;
33-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel;
34-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationPropertyModel;
3532
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalResourceDetectionModel;
3633
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalResourceDetectorModel;
3734
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel;
@@ -61,49 +58,20 @@
6158
public class ElasticDeclarativeConfigurationCustomizer
6259
implements DeclarativeConfigurationCustomizerProvider {
6360

64-
private static final String RUNTIME_TELEMETRY = "runtime_telemetry";
65-
private static final String EMIT_EXPERIMENTAL_METRICS_DEVELOPMENT =
66-
"emit_experimental_metrics/development";
67-
6861
@Override
6962
public void customize(DeclarativeConfigurationCustomizer customizer) {
7063
customizer.addModelCustomizer(
7164
model -> {
65+
if (model == null) {
66+
model = new OpenTelemetryConfigurationModel();
67+
}
68+
7269
customizeResources(model);
7370
customizeUserAgent(model);
74-
customizeExperimentalRuntimeTelemetryMetrics(model);
7571
return model;
7672
});
7773
}
7874

79-
private static void customizeExperimentalRuntimeTelemetryMetrics(
80-
OpenTelemetryConfigurationModel model) {
81-
82-
ExperimentalInstrumentationModel instrumentationDevelopment =
83-
model.getInstrumentationDevelopment();
84-
if (instrumentationDevelopment == null) {
85-
instrumentationDevelopment = new ExperimentalInstrumentationModel();
86-
model.withInstrumentationDevelopment(instrumentationDevelopment);
87-
}
88-
89-
ExperimentalLanguageSpecificInstrumentationModel java = instrumentationDevelopment.getJava();
90-
if (java == null) {
91-
java = new ExperimentalLanguageSpecificInstrumentationModel();
92-
instrumentationDevelopment.withJava(java);
93-
}
94-
95-
ExperimentalLanguageSpecificInstrumentationPropertyModel runtimeTelemetry =
96-
java.getAdditionalProperties().get(RUNTIME_TELEMETRY);
97-
if (runtimeTelemetry == null) {
98-
runtimeTelemetry = new ExperimentalLanguageSpecificInstrumentationPropertyModel();
99-
java.withAdditionalProperty(RUNTIME_TELEMETRY, runtimeTelemetry);
100-
}
101-
if (runtimeTelemetry.getAdditionalProperties().get(EMIT_EXPERIMENTAL_METRICS_DEVELOPMENT)
102-
== null) {
103-
runtimeTelemetry.withAdditionalProperty(EMIT_EXPERIMENTAL_METRICS_DEVELOPMENT, true);
104-
}
105-
}
106-
10775
private static void customizeResources(OpenTelemetryConfigurationModel model) {
10876
// this is equivalent to adding the following explicitly in declarative configuration
10977
//
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
file_format: "1.0"
2+
resource:
3+
attributes:
4+
# use this to set custom resource attributes
5+
# - name: custom.attribute
6+
# value: custom.attribute.value
7+
8+
# get attributes from OTEL_RESOURCE_ATTRIBUTES environment variable if set
9+
attributes_list: ${OTEL_RESOURCE_ATTRIBUTES:-}
10+
11+
detection/development:
12+
detectors:
13+
# Enable cloud provider resource attributes detectors by default for easier onboarding.
14+
# Those may slow down application startup, thus only the relevant ones should be enabled in production
15+
- aws:
16+
- azure:
17+
- gcp:
18+
# Provides 'process.*' attributes, including 'process.pid'
19+
- process:
20+
# Provides 'host.id'
21+
- host:
22+
# Provides 'container.id'
23+
- container:
24+
# Provides 'service.name' and 'service.instance.id'
25+
- service:
26+
27+
tracer_provider:
28+
processors:
29+
- batch:
30+
exporter:
31+
# get endpoint from OTEL_EXPORTER_OTLP_ENDPOINT environment variable if set, otherwise fallback to local collector
32+
# get endpoint headers (like authentication) from OTEL_EXPORTER_OTLP_HEADERS environment variable if set
33+
otlp_http:
34+
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/traces
35+
headers_list: ${OTEL_EXPORTER_OTLP_HEADERS}
36+
# use the following for grpc
37+
# otlp_grpc:
38+
# endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}
39+
# headers_list: ${OTEL_EXPORTER_OTLP_HEADERS}
40+
41+
meter_provider:
42+
readers:
43+
- periodic:
44+
exporter:
45+
# get endpoint from OTEL_EXPORTER_OTLP_ENDPOINT environment variable if set, otherwise fallback to local collector
46+
# get endpoint headers (like authentication) from OTEL_EXPORTER_OTLP_HEADERS environment variable if set
47+
otlp_http:
48+
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/metrics
49+
headers_list: ${OTEL_EXPORTER_OTLP_HEADERS}
50+
# use the following for grpc
51+
# otlp_grpc:
52+
# endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}
53+
# headers_list: ${OTEL_EXPORTER_OTLP_HEADERS}
54+
55+
logger_provider:
56+
processors:
57+
- batch:
58+
exporter:
59+
# get endpoint from OTEL_EXPORTER_OTLP_ENDPOINT environment variable if set, otherwise fallback to local collector
60+
# get endpoint headers (like authentication) from OTEL_EXPORTER_OTLP_HEADERS environment variable if set
61+
otlp_http:
62+
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/logs
63+
headers_list: ${OTEL_EXPORTER_OTLP_HEADERS}
64+
# use the following for grpc
65+
# otlp_grpc:
66+
# endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}
67+
# headers_list: ${OTEL_EXPORTER_OTLP_HEADERS}
68+
69+
instrumentation/development:
70+
java:
71+
runtime_telemetry:
72+
# emit not-yet stable runtime telemetry metrics which are currently part of semconv for java runtime
73+
emit_experimental_metrics/development: true
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.otel.declarativeconfig;
20+
21+
import static co.elastic.otel.declarativeconfig.ElasticDeclarativeConfigurationCustomizerTest.*;
22+
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
23+
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
24+
import static org.assertj.core.api.Assertions.assertThat;
25+
26+
import io.opentelemetry.javaagent.tooling.resources.ResourceCustomizerProvider;
27+
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration;
28+
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
29+
import java.io.InputStream;
30+
import java.util.function.Consumer;
31+
import net.javacrumbs.jsonunit.assertj.JsonListAssert;
32+
import org.junit.jupiter.api.Test;
33+
34+
public class DefaultDeclarativeConfigTest {
35+
36+
// For now, we can't override env variable for testing, thus we just verify the default values
37+
// in the configuration we provide.
38+
@Test
39+
void testDefaults() {
40+
test(
41+
(config) -> {
42+
assertThat(config.getResource()).isNotNull();
43+
assertThat(config.getResource().getAttributesList()).isNull();
44+
45+
assertThatJson(json(config.getResource())).inPath("attributes").isArray().isEmpty();
46+
JsonListAssert detectorsAssert =
47+
assertThatJson(json(config.getResource()))
48+
.inPath("detection/development.detectors")
49+
.isArray()
50+
.hasSize(9);
51+
52+
// those are the providers magically added by upstream and elastic distributions
53+
detectorsAssert
54+
.first()
55+
.isEqualTo(json("{\"opentelemetry_javaagent_distribution\":null}"));
56+
detectorsAssert.last().isEqualTo(json("{\"elastic_distribution\":null}"));
57+
58+
// those are the ones that should be included in the default configuration
59+
detectorsAssert.contains(
60+
json("{\"aws\":null}"),
61+
json("{\"gcp\":null}"),
62+
json("{\"azure\":null}"),
63+
// TODO: maybe investigate why those are including empty objects
64+
json("{\"process\":{}}"),
65+
json("{\"container\":{}}"),
66+
json("{\"service\":{}}"),
67+
json("{\"host\":{}}"));
68+
69+
assertThat(config.getTracerProvider()).isNotNull();
70+
assertThat(config.getTracerProvider().getProcessors()).hasSize(1);
71+
assertThatJson(json((config.getTracerProvider().getProcessors().get(0))))
72+
.inPath("batch.exporter.otlp_http")
73+
.isObject()
74+
.containsEntry("endpoint", "http://localhost:4318/v1/traces");
75+
76+
assertThat(config.getMeterProvider()).isNotNull();
77+
assertThat(config.getMeterProvider().getReaders()).hasSize(1);
78+
assertThatJson(json(config.getMeterProvider().getReaders().get(0)))
79+
.inPath("periodic.exporter.otlp_http")
80+
.isObject()
81+
.containsEntry("endpoint", "http://localhost:4318/v1/metrics");
82+
83+
assertThat(config.getLoggerProvider()).isNotNull();
84+
assertThat(config.getLoggerProvider().getProcessors()).hasSize(1);
85+
assertThatJson(json((config.getLoggerProvider().getProcessors().get(0))))
86+
.inPath("batch.exporter.otlp_http")
87+
.isObject()
88+
.containsEntry("endpoint", "http://localhost:4318/v1/logs");
89+
90+
assertThatJson(json(config.getInstrumentationDevelopment()))
91+
.describedAs("experimental jvm runtime telemetry metrics enabled by default")
92+
.inPath("java.runtime_telemetry.emit_experimental_metrics/development")
93+
.isBoolean()
94+
.isTrue();
95+
});
96+
}
97+
98+
private static void test(Consumer<OpenTelemetryConfigurationModel> configChecks) {
99+
100+
InputStream input =
101+
DefaultDeclarativeConfigTest.class
102+
.getClassLoader()
103+
.getResourceAsStream("co/elastic/otel/config.yaml");
104+
assertThat(input).isNotNull();
105+
OpenTelemetryConfigurationModel config = DeclarativeConfiguration.parse(input);
106+
107+
// manually apply config customization for simplicity
108+
config = applyConfigCustomize(config, new ElasticDeclarativeConfigurationCustomizer());
109+
config = applyConfigCustomize(config, new ResourceCustomizerProvider());
110+
111+
configChecks.accept(config);
112+
}
113+
}

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

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@
2929
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider;
3030
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel;
3131
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessorModel;
32-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalInstrumentationModel;
33-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel;
34-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationPropertyModel;
3532
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel;
3633
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessorModel;
3734
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LoggerProviderModel;
@@ -83,12 +80,6 @@ void defaultConfig() {
8380
assertThat(model.getTracerProvider()).isNull();
8481
assertThat(model.getMeterProvider()).isNull();
8582
assertThat(model.getLoggerProvider()).isNull();
86-
87-
// java experimental runtime metrics enabled by default
88-
assertThatJson(json(model.getInstrumentationDevelopment()))
89-
.inPath("java.runtime_telemetry.emit_experimental_metrics/development")
90-
.isBoolean()
91-
.isTrue();
9283
}
9384

9485
@ParameterizedTest
@@ -245,28 +236,6 @@ void addUserAgentPriority() {
245236
.contains("user-agent", "custom-user-Agent");
246237
}
247238

248-
@Test
249-
void optOutExperimentalRuntimeMetrics() {
250-
OpenTelemetryConfigurationModel model =
251-
new OpenTelemetryConfigurationModel()
252-
.withInstrumentationDevelopment(
253-
new ExperimentalInstrumentationModel()
254-
.withJava(
255-
new ExperimentalLanguageSpecificInstrumentationModel()
256-
.withAdditionalProperty(
257-
"runtime_telemetry",
258-
new ExperimentalLanguageSpecificInstrumentationPropertyModel()
259-
.withAdditionalProperty(
260-
"emit_experimental_metrics/development", false))));
261-
262-
model = applyConfigCustomize(model, new ElasticDeclarativeConfigurationCustomizer());
263-
264-
assertThatJson(json(model.getInstrumentationDevelopment()))
265-
.inPath("java.runtime_telemetry.emit_experimental_metrics/development")
266-
.isBoolean()
267-
.isFalse();
268-
}
269-
270239
@NotNull
271240
private static Map<String, String> userAgentHeader(String value) {
272241
Map<String, String> header = new HashMap<>();
@@ -275,7 +244,7 @@ private static Map<String, String> userAgentHeader(String value) {
275244
return header;
276245
}
277246

278-
private OpenTelemetryConfigurationModel applyConfigCustomize(
247+
static OpenTelemetryConfigurationModel applyConfigCustomize(
279248
OpenTelemetryConfigurationModel originalModel,
280249
DeclarativeConfigurationCustomizerProvider customizerProvider) {
281250
AtomicReference<OpenTelemetryConfigurationModel> resultModel = new AtomicReference<>();

0 commit comments

Comments
 (0)