Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
package co.elastic.otel.agent;

import io.opentelemetry.javaagent.OpenTelemetryAgent;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.Instrumentation;
import java.util.Objects;

/** Elastic agent entry point, delegates to OpenTelemetry agent */
public class ElasticAgent {
Expand Down Expand Up @@ -56,7 +59,36 @@ public static void agentmain(String agentArgs, Instrumentation inst) {
* @param args arguments
*/
public static void main(String[] args) {
OpenTelemetryAgent.main(args);
boolean upstreamDefault = true;
for (String arg : args) {
switch (arg) {
case "--default-config-yaml":
printDefaultConfigYaml();
upstreamDefault = false;
break;
}
}

if (upstreamDefault) {
OpenTelemetryAgent.main(args);
}
}

private static void printDefaultConfigYaml() {
try (InputStream input =
ElasticAgent.class
.getClassLoader()
.getResourceAsStream("inst/co/elastic/otel/config.yaml")) {
Objects.requireNonNull(input, "Default config yaml resource is missing");
Comment thread
SylvainJuge marked this conversation as resolved.

byte[] buffer = new byte[8192];
int read;
while ((read = input.read(buffer)) != -1) {
System.out.write(buffer, 0, read);
}
} catch (IOException e) {
throw new IllegalStateException("Failed to print default config yaml", e);
}
}

private static void initLogging() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessorModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalInstrumentationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationPropertyModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalResourceDetectionModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalResourceDetectorModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel;
Expand Down Expand Up @@ -61,49 +58,16 @@
public class ElasticDeclarativeConfigurationCustomizer
implements DeclarativeConfigurationCustomizerProvider {

private static final String RUNTIME_TELEMETRY = "runtime_telemetry";
private static final String EMIT_EXPERIMENTAL_METRICS_DEVELOPMENT =
"emit_experimental_metrics/development";

@Override
public void customize(DeclarativeConfigurationCustomizer customizer) {
customizer.addModelCustomizer(
model -> {
customizeResources(model);
customizeUserAgent(model);
customizeExperimentalRuntimeTelemetryMetrics(model);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[for reviewer] we remove the "magic" implementation, this is replaced with the default config in yaml (unfortunately we don't have a test for it, at least yet).

return model;
});
}

private static void customizeExperimentalRuntimeTelemetryMetrics(
OpenTelemetryConfigurationModel model) {

ExperimentalInstrumentationModel instrumentationDevelopment =
model.getInstrumentationDevelopment();
if (instrumentationDevelopment == null) {
instrumentationDevelopment = new ExperimentalInstrumentationModel();
model.withInstrumentationDevelopment(instrumentationDevelopment);
}

ExperimentalLanguageSpecificInstrumentationModel java = instrumentationDevelopment.getJava();
if (java == null) {
java = new ExperimentalLanguageSpecificInstrumentationModel();
instrumentationDevelopment.withJava(java);
}

ExperimentalLanguageSpecificInstrumentationPropertyModel runtimeTelemetry =
java.getAdditionalProperties().get(RUNTIME_TELEMETRY);
if (runtimeTelemetry == null) {
runtimeTelemetry = new ExperimentalLanguageSpecificInstrumentationPropertyModel();
java.withAdditionalProperty(RUNTIME_TELEMETRY, runtimeTelemetry);
}
if (runtimeTelemetry.getAdditionalProperties().get(EMIT_EXPERIMENTAL_METRICS_DEVELOPMENT)
== null) {
runtimeTelemetry.withAdditionalProperty(EMIT_EXPERIMENTAL_METRICS_DEVELOPMENT, true);
}
}

private static void customizeResources(OpenTelemetryConfigurationModel model) {
// this is equivalent to adding the following explicitly in declarative configuration
//
Expand Down
61 changes: 61 additions & 0 deletions custom/src/main/resources/co/elastic/otel/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
file_format: "1.0"
resource:
attributes:
# use this to set custom resource attributes
# - name: custom.attribute
# value: custom.attribute.value

# get attributes from OTEL_RESOURCE_ATTRIBUTES environment variable if set
attributes_list: ${OTEL_RESOURCE_ATTRIBUTES:-}

detection/development:
detectors:
# Enable cloud provider resource attributes detectors by default for easier onboarding.
# Those may slow down application startup, thus only the relevant ones should be enabled in production
- aws:
- azure:
- gcp:
# Provides 'process.*' attributes, including 'process.pid'
- process:
# Provides 'host.id'
- host:
# Provides 'container.id'
- container:
# Provides 'service.name' and 'service.instance.id'
- service:

tracer_provider:
processors:
- batch:
exporter:
otlp_http:
# get endpoint from OTEL_EXPORTER_OTLP_ENDPOINT environment variable if set, otherwise fallback to local collector
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/traces
# get endpoint headers (like authentication) from OTEL_EXPORTER_OTLP_HEADERS environment variable if set
headers_list: ${OTEL_EXPORTER_OTLP_HEADERS}

meter_provider:
readers:
- periodic:
exporter:
otlp_http:
# get endpoint from OTEL_EXPORTER_OTLP_ENDPOINT environment variable if set, otherwise fallback to local collector
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/metrics
# get endpoint headers (like authentication) from OTEL_EXPORTER_OTLP_HEADERS environment variable if set
headers_list: ${OTEL_EXPORTER_OTLP_HEADERS}

logger_provider:
processors:
- batch:
exporter:
otlp_http:
# get endpoint from OTEL_EXPORTER_OTLP_ENDPOINT environment variable if set, otherwise fallback to local collector
endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/logs
# get endpoint headers (like authentication) from OTEL_EXPORTER_OTLP_HEADERS environment variable if set
headers_list: ${OTEL_EXPORTER_OTLP_HEADERS}

instrumentation/development:
java:
runtime_telemetry:
# emit not-yet stable runtime telemetry metrics which are currently part of semconv for java runtime
emit_experimental_metrics/development: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.otel.declarativeconfig;

import static co.elastic.otel.declarativeconfig.ElasticDeclarativeConfigurationCustomizerTest.*;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.javaagent.tooling.resources.ResourceCustomizerProvider;
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
import java.io.InputStream;
import java.util.function.Consumer;
import net.javacrumbs.jsonunit.assertj.JsonListAssert;
import org.junit.jupiter.api.Test;

public class DefaultDeclarativeConfigTest {

// For now, we can't override env variable for testing, thus we just verify the default values
// in the configuration we provide.
@Test
void testDefaults() {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[for reviewer] this tests that the yaml has a correct structure and contains the things we hope it contains, it does not really checks that everything in it is actually correct, which is what the smoke test provides.

test(
(config) -> {
assertThat(config.getResource()).isNotNull();
assertThat(config.getResource().getAttributesList()).isNull();

assertThatJson(json(config.getResource())).inPath("attributes").isArray().isEmpty();
JsonListAssert detectorsAssert =
assertThatJson(json(config.getResource()))
.inPath("detection/development.detectors")
.isArray()
.hasSize(9);

// those are the providers magically added by upstream and elastic distributions
detectorsAssert
.first()
.isEqualTo(json("{\"opentelemetry_javaagent_distribution\":null}"));
detectorsAssert.last().isEqualTo(json("{\"elastic_distribution\":null}"));

// those are the ones that should be included in the default configuration
detectorsAssert.contains(
json("{\"aws\":null}"),
json("{\"gcp\":null}"),
json("{\"azure\":null}"),
// TODO: maybe investigate why those are including empty objects
json("{\"process\":{}}}"),
json("{\"container\":{}}}"),
json("{\"service\":{}}}"),
json("{\"host\":{}}}"));
Comment thread
SylvainJuge marked this conversation as resolved.
Outdated

assertThat(config.getTracerProvider()).isNotNull();
assertThat(assertThat(config.getTracerProvider().getProcessors()).hasSize(1));
assertThatJson(json((config.getTracerProvider().getProcessors().get(0))))
.inPath("batch.exporter.otlp_http")
.isObject()
.containsEntry("endpoint", "http://localhost:4318/v1/traces");

assertThat(config.getMeterProvider()).isNotNull();
assertThat(assertThat(config.getMeterProvider().getReaders()).hasSize(1));
assertThatJson(json((config.getMeterProvider().getReaders().get(0))))
.inPath("periodic.exporter.otlp_http")
.isObject()
.containsEntry("endpoint", "http://localhost:4318/v1/metrics");

assertThat(config.getLoggerProvider()).isNotNull();
assertThat(assertThat(config.getLoggerProvider().getProcessors()).hasSize(1));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
assertThatJson(json((config.getLoggerProvider().getProcessors().get(0))))
.inPath("batch.exporter.otlp_http")
.isObject()
.containsEntry("endpoint", "http://localhost:4318/v1/logs");
Comment thread
SylvainJuge marked this conversation as resolved.

assertThatJson(json(config.getInstrumentationDevelopment()))
.describedAs("experimental jvm runtime telemetry metrics enabled by default")
.inPath("java.runtime_telemetry.emit_experimental_metrics/development")
.isBoolean()
.isTrue();
});
}

private static void test(Consumer<OpenTelemetryConfigurationModel> configChecks) {

InputStream input =
DefaultDeclarativeConfigTest.class
.getClassLoader()
.getResourceAsStream("co/elastic/otel/config.yaml");
assertThat(input).isNotNull();
OpenTelemetryConfigurationModel config = DeclarativeConfiguration.parse(input);

// manually apply config customization for simplicity
config = applyConfigCustomize(config, new ElasticDeclarativeConfigurationCustomizer());
config = applyConfigCustomize(config, new ResourceCustomizerProvider());

configChecks.accept(config);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchSpanProcessorModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalInstrumentationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationPropertyModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessorModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LoggerProviderModel;
Expand Down Expand Up @@ -83,12 +80,6 @@ void defaultConfig() {
assertThat(model.getTracerProvider()).isNull();
assertThat(model.getMeterProvider()).isNull();
assertThat(model.getLoggerProvider()).isNull();

// java experimental runtime metrics enabled by default
assertThatJson(json(model.getInstrumentationDevelopment()))
.inPath("java.runtime_telemetry.emit_experimental_metrics/development")
.isBoolean()
.isTrue();
}

@ParameterizedTest
Expand Down Expand Up @@ -245,28 +236,6 @@ void addUserAgentPriority() {
.contains("user-agent", "custom-user-Agent");
}

@Test
void optOutExperimentalRuntimeMetrics() {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[for reviewer] I'm happy to get rid of this "half hadooken code"

OpenTelemetryConfigurationModel model =
new OpenTelemetryConfigurationModel()
.withInstrumentationDevelopment(
new ExperimentalInstrumentationModel()
.withJava(
new ExperimentalLanguageSpecificInstrumentationModel()
.withAdditionalProperty(
"runtime_telemetry",
new ExperimentalLanguageSpecificInstrumentationPropertyModel()
.withAdditionalProperty(
"emit_experimental_metrics/development", false))));

model = applyConfigCustomize(model, new ElasticDeclarativeConfigurationCustomizer());

assertThatJson(json(model.getInstrumentationDevelopment()))
.inPath("java.runtime_telemetry.emit_experimental_metrics/development")
.isBoolean()
.isFalse();
}

@NotNull
private static Map<String, String> userAgentHeader(String value) {
Map<String, String> header = new HashMap<>();
Expand All @@ -275,7 +244,7 @@ private static Map<String, String> userAgentHeader(String value) {
return header;
}

private OpenTelemetryConfigurationModel applyConfigCustomize(
static OpenTelemetryConfigurationModel applyConfigCustomize(
OpenTelemetryConfigurationModel originalModel,
DeclarativeConfigurationCustomizerProvider customizerProvider) {
AtomicReference<OpenTelemetryConfigurationModel> resultModel = new AtomicReference<>();
Expand Down
Loading