Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions custom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ dependencies {
}

compileOnly("io.opentelemetry:opentelemetry-sdk")
compileOnly("io.opentelemetry:opentelemetry-api-incubator")
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp")
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-incubator")
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling")
compileOnly(libs.bundles.semconv)
Expand Down Expand Up @@ -54,8 +56,10 @@ dependencies {
// test dependencies
testImplementation(project(":testing-common"))
testImplementation("io.opentelemetry:opentelemetry-sdk")
testImplementation("io.opentelemetry:opentelemetry-api-incubator")
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator")
testImplementation("io.opentelemetry:opentelemetry-exporter-otlp")
testImplementation("io.opentelemetry.javaagent:opentelemetry-javaagent-tooling") {
//The following dependency isn't actually needed, but breaks the classpath when testing with Java 8
Expand Down
48 changes: 48 additions & 0 deletions custom/src/main/java/co/elastic/otel/ElasticDistroResource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.javaagent.tooling.AgentVersion;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.incubating.TelemetryIncubatingAttributes;

public class ElasticDistroResource {

private ElasticDistroResource() {}

public static Resource get() {
if (AgentVersion.VERSION == null) {
return Resource.empty();
}
try {
Class.forName("co.elastic.otel.agent.ElasticAgent");
} catch (ClassNotFoundException e) {
// this means that we are running as an extension of the vanilla agent
// and not as distro.
return Resource.empty();
}
return Resource.create(
Attributes.of(
TelemetryIncubatingAttributes.TELEMETRY_DISTRO_NAME,
"elastic",
TelemetryIncubatingAttributes.TELEMETRY_DISTRO_VERSION,
AgentVersion.VERSION));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,19 @@
package co.elastic.otel;

import com.google.auto.service.AutoService;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.javaagent.tooling.AgentVersion;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.incubating.TelemetryIncubatingAttributes;

/**
* Provides {@code telemetry.distro.name} and {@code telemetry.distro.version} resource attributes
* for automatic configuration
*/
@AutoService(ResourceProvider.class)
public class ElasticDistroResourceProvider implements ResourceProvider {

@Override
public Resource createResource(ConfigProperties configProperties) {
if (AgentVersion.VERSION == null) {
return Resource.empty();
}
try {
Class.forName("co.elastic.otel.agent.ElasticAgent");
} catch (ClassNotFoundException e) {
// this means that we are running as an extension of the vanilla agent
// and not as distro.
return Resource.empty();
}
return Resource.create(
Attributes.of(
TelemetryIncubatingAttributes.TELEMETRY_DISTRO_NAME,
"elastic",
TelemetryIncubatingAttributes.TELEMETRY_DISTRO_VERSION,
AgentVersion.VERSION));
return ElasticDistroResource.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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 java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toSet;

import com.google.auto.service.AutoService;
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizer;
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider;
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.OpenTelemetryConfigurationModel;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ResourceModel;
import java.util.List;
import java.util.Set;

@AutoService(DeclarativeConfigurationCustomizerProvider.class)
public class ElasticDeclarativeConfigurationCustomizer
implements DeclarativeConfigurationCustomizerProvider {

@Override
public void customize(DeclarativeConfigurationCustomizer customizer) {
customizer.addModelCustomizer(
model -> {
customizeResources(model);
return model;
});
}

private static void customizeResources(OpenTelemetryConfigurationModel model) {
// this is equivalent to adding the following explicitly in declarative configuration
//
// detection/development:
// detectors:
// - <... other detectors ...>
// - elastic_distribution:

ResourceModel resource = model.getResource();
if (resource == null) {
resource = new ResourceModel();
model.withResource(resource);
}

ExperimentalResourceDetectionModel detectionDevelopment = resource.getDetectionDevelopment();
if (null == detectionDevelopment) {
detectionDevelopment = new ExperimentalResourceDetectionModel();
resource.withDetectionDevelopment(detectionDevelopment);
}
List<ExperimentalResourceDetectorModel> detectors =
requireNonNull(detectionDevelopment.getDetectors());

Set<String> names =
detectors.stream()
.flatMap(detector -> detector.getAdditionalProperties().keySet().stream())
.collect(toSet());

// add at the end to make it have priority over upstream distro provider (which is added 1st)
if (!names.contains(ElasticDistroComponentProvider.NAME)) {
ExperimentalResourceDetectorModel detector = new ExperimentalResourceDetectorModel();
detector.getAdditionalProperties().put(ElasticDistroComponentProvider.NAME, null);
detectors.add(detector);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 co.elastic.otel.ElasticDistroResource;
import com.google.auto.service.AutoService;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider;
import io.opentelemetry.sdk.resources.Resource;

/**
* Provides {@code telemetry.distro.name} and {@code telemetry.distro.version} resource attributes
* for declarative configuration
*/
@AutoService(ComponentProvider.class)
public class ElasticDistroComponentProvider implements ComponentProvider {

static final String NAME = "elastic_distribution";

@Override
public Class<?> getType() {
return Resource.class;
}

@Override
public String getName() {
return NAME;
}

@Override
public Object create(DeclarativeConfigProperties config) {
return ElasticDistroResource.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* 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 org.assertj.core.api.Assertions.assertThat;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.javaagent.tooling.resources.ResourceCustomizerProvider;
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizer;
import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfigurationCustomizerProvider;
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class ElasticDeclarativeConfigurationCustomizerTest {

// because declarative config relies on json mapping annotations, we can leverage this for testing
// the configuration customization.
private final ObjectMapper objectMapper = new ObjectMapper();

@Test
void defaultConfig() {
OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel();
model = applyConfigCustomize(model, new ElasticDeclarativeConfigurationCustomizer());

// ensures that we add our resource detector even if the model does not provide any
checkJson(
model.getResource(),
"{\"attributes\":[],\"detection/development\":{\"detectors\":[{\"elastic_distribution\":null}]}}");
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
void upstreamProvider(boolean elasticFirst) {
// upstream provider is always added first in the list, even if we add ours first
// this ordering behavior is implemented in upstream provider
OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel();
DeclarativeConfigurationCustomizerProvider first;
DeclarativeConfigurationCustomizerProvider second;
if (elasticFirst) {
first = new ElasticDeclarativeConfigurationCustomizer();
second = new ResourceCustomizerProvider();
} else {
first = new ElasticDeclarativeConfigurationCustomizer();
second = new ResourceCustomizerProvider();
}

model = applyConfigCustomize(model, first);
model = applyConfigCustomize(model, second);
checkJson(
model.getResource(),
"{\"attributes\":[],\"detection/development\":{\"detectors\":[{\"opentelemetry_javaagent_distribution\":null},{\"elastic_distribution\":null}]}}");
}

private void checkJson(Object o, String expected) {
try {
assertThat(objectMapper.writeValueAsString(o)).isEqualTo(expected);
} catch (JsonProcessingException e) {
throw new IllegalStateException(e);
}
}

private OpenTelemetryConfigurationModel applyConfigCustomize(
OpenTelemetryConfigurationModel originalModel,
DeclarativeConfigurationCustomizerProvider customizerProvider) {
AtomicReference<OpenTelemetryConfigurationModel> resultModel = new AtomicReference<>();
customizerProvider.customize(
new TestCustomizer() {
@Override
public void addModelCustomizer(
Function<OpenTelemetryConfigurationModel, OpenTelemetryConfigurationModel>
customizer) {
resultModel.set(customizer.apply(originalModel));
}
});
return resultModel.get();
}

private static class TestCustomizer implements DeclarativeConfigurationCustomizer {

@Override
public void addModelCustomizer(
Function<OpenTelemetryConfigurationModel, OpenTelemetryConfigurationModel> customizer) {}

@Override
public <T extends SpanExporter> void addSpanExporterCustomizer(
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer) {}

@Override
public <T extends MetricExporter> void addMetricExporterCustomizer(
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer) {}

@Override
public <T extends LogRecordExporter> void addLogRecordExporterCustomizer(
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer) {}
}
}
Loading