Skip to content

Commit

Permalink
Load OTel SDK config from environment variables and system properties…
Browse files Browse the repository at this point in the history
….… (#1434)
  • Loading branch information
cyrille-leclerc authored Dec 9, 2024
1 parent 2b6625e commit bfd7390
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.maven;

import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/** Utility class to use the {@link AutoConfiguredOpenTelemetrySdk}. */
public class AutoConfigureUtil2 {

private AutoConfigureUtil2() {}

/**
* Returns the {@link Resource} that was autoconfigured.
*
* <p>Inspired by {@link
* io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil#getConfig(AutoConfiguredOpenTelemetrySdk)}
*/
public static Resource getResource(
AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
try {
Method method = AutoConfiguredOpenTelemetrySdk.class.getDeclaredMethod("getResource");
method.setAccessible(true);
return (Resource) method.invoke(autoConfiguredOpenTelemetrySdk);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new IllegalStateException(
"Error calling getResource on AutoConfiguredOpenTelemetrySdk", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@

package io.opentelemetry.maven;

import com.google.common.annotations.VisibleForTesting;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.maven.semconv.MavenOtelSemanticAttributes;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.resources.Resource;
import java.io.Closeable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.annotation.PreDestroy;
import javax.inject.Named;
import javax.inject.Singleton;
Expand All @@ -36,6 +41,10 @@ public final class OpenTelemetrySdkService implements Closeable {

private final OpenTelemetrySdk openTelemetrySdk;

@VisibleForTesting final Resource resource;

private final ConfigProperties configProperties;

private final Tracer tracer;

private final boolean mojosInstrumentationEnabled;
Expand All @@ -47,32 +56,68 @@ public OpenTelemetrySdkService() {
"OpenTelemetry: Initialize OpenTelemetrySdkService v{}...",
MavenOtelSemanticAttributes.TELEMETRY_DISTRO_VERSION_VALUE);

// Change default of "otel.[traces,metrics,logs].exporter" from "otlp" to "none"
// The impacts are
// * If no otel exporter settings are passed, then the Maven extension will not export
// rather than exporting on OTLP GRPC to http://localhost:4317
// * If OTEL_EXPORTER_OTLP_ENDPOINT is defined but OTEL_[TRACES,METRICS,LOGS]_EXPORTER,
// is not, then don't export
Map<String, String> properties = new HashMap<>();
properties.put("otel.traces.exporter", "none");
properties.put("otel.metrics.exporter", "none");
properties.put("otel.logs.exporter", "none");

AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
AutoConfiguredOpenTelemetrySdk.builder()
.setServiceClassLoader(getClass().getClassLoader())
.addPropertiesSupplier(() -> properties)
.addPropertiesCustomizer(
OpenTelemetrySdkService::requireExplicitConfigOfTheOtlpExporter)
.disableShutdownHook()
.build();

this.openTelemetrySdk = autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk();
this.configProperties =
Optional.ofNullable(AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk))
.orElseGet(() -> DefaultConfigProperties.createFromMap(Collections.emptyMap()));

this.resource = AutoConfigureUtil2.getResource(autoConfiguredOpenTelemetrySdk);
// Display resource attributes in debug logs for troubleshooting when traces are not found in
// the observability backend, helping understand `service.name`, `service.namespace`, etc.
logger.debug("OpenTelemetry: OpenTelemetrySdkService initialized, resource:{}", resource);

Boolean mojoSpansEnabled = getBooleanConfig("otel.instrumentation.maven.mojo.enabled");
this.mojosInstrumentationEnabled = mojoSpansEnabled == null || mojoSpansEnabled;
this.mojosInstrumentationEnabled =
configProperties.getBoolean("otel.instrumentation.maven.mojo.enabled", true);

this.tracer = openTelemetrySdk.getTracer("io.opentelemetry.contrib.maven", VERSION);
}

/**
* The OTel SDK by default sends data to the OTLP gRPC endpoint localhost:4317 if no exporter and
* no OTLP exporter endpoint are defined. This is not suited for a build tool for which we want
* the OTel SDK to be disabled by default.
*
* <p>Change the OTel SDL behavior: if none of the exporter and the OTLP exporter endpoint are
* defined, explicitly disable the exporter setting "{@code
* otel.[traces,metrics,logs].exporter=none}"
*
* @return The properties to be returned by {@link
* io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder#addPropertiesCustomizer(java.util.function.Function)}
*/
static Map<String, String> requireExplicitConfigOfTheOtlpExporter(
ConfigProperties configProperties) {

Map<String, String> properties = new HashMap<>();
if (configProperties.getString("otel.exporter.otlp.endpoint") != null) {
logger.debug("OpenTelemetry: OTLP exporter endpoint is explicitly configured");
return properties;
}
String[] signalTypes = {"traces", "metrics", "logs"};
for (String signalType : signalTypes) {
boolean isExporterImplicitlyConfiguredToOtlp =
configProperties.getString("otel." + signalType + ".exporter") == null;
boolean isOtlpExporterEndpointSpecified =
configProperties.getString("otel.exporter.otlp." + signalType + ".endpoint") != null;

if (isExporterImplicitlyConfiguredToOtlp && !isOtlpExporterEndpointSpecified) {
logger.debug(
"OpenTelemetry: Disabling default OTLP exporter endpoint for signal {} exporter",
signalType);
properties.put("otel." + signalType + ".exporter", "none");
}
}

return properties;
}

@PreDestroy
@Override
public synchronized void close() {
Expand All @@ -97,6 +142,10 @@ public Tracer getTracer() {
return this.tracer;
}

public ConfigProperties getConfigProperties() {
return configProperties;
}

/** Returns the {@link ContextPropagators} for this {@link OpenTelemetry}. */
public ContextPropagators getPropagators() {
return this.openTelemetrySdk.getPropagators();
Expand All @@ -105,17 +154,4 @@ public ContextPropagators getPropagators() {
public boolean isMojosInstrumentationEnabled() {
return mojosInstrumentationEnabled;
}

@Nullable
private static Boolean getBooleanConfig(String name) {
String value = System.getProperty(name);
if (value != null) {
return Boolean.parseBoolean(value);
}
value = System.getenv(name.toUpperCase(Locale.ROOT).replace('.', '_'));
if (value != null) {
return Boolean.parseBoolean(value);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.maven;

import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;

class AutoConfigureUtil2Test {

/**
* Verify the reflection call works with the current version of AutoConfiguredOpenTelemetrySdk.
*
* @throws NoSuchMethodException if the method does not exist
*/
@Test
void test_getResource() throws NoSuchMethodException {
Method method = AutoConfiguredOpenTelemetrySdk.class.getDeclaredMethod("getResource");
method.setAccessible(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,132 @@

package io.opentelemetry.maven;

import org.junit.jupiter.api.Disabled;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.resources.Resource;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;

/**
* Note: if otel-java-contrib bumps to Java 11+, we could use junit-pioneer's
* {@code @SetSystemProperty} and {@code @ClearSystemProperty} but no bump is planned for now.
*/
public class OpenTelemetrySdkServiceTest {

/** Verify default `service.name` */
/** Verify default config */
@Test
@Disabled
public void testDefaultConfiguration() {
testConfiguration("maven");
System.clearProperty("otel.exporter.otlp.endpoint");
System.clearProperty("otel.service.name");
System.clearProperty("otel.resource.attributes");
try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) {

Resource resource = openTelemetrySdkService.resource;
assertThat(resource.getAttribute(stringKey("service.name"))).isEqualTo("maven");

ConfigProperties configProperties = openTelemetrySdkService.getConfigProperties();
assertThat(configProperties.getString("otel.exporter.otlp.endpoint")).isNull();
assertThat(configProperties.getString("otel.traces.exporter")).isEqualTo("none");
assertThat(configProperties.getString("otel.metrics.exporter")).isEqualTo("none");
assertThat(configProperties.getString("otel.logs.exporter")).isEqualTo("none");
}
}

/** Verify overwritten `service.name` */
/** Verify overwritten `service.name`,`key1` and `key2` */
@Test
@Disabled
public void testOverwrittenConfiguration() {
public void testOverwrittenResourceAttributes() {
System.setProperty("otel.service.name", "my-maven");
try {
testConfiguration("my-maven");
System.setProperty("otel.resource.attributes", "key1=val1,key2=val2");

try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) {

Resource resource = openTelemetrySdkService.resource;
assertThat(resource.getAttribute(stringKey("service.name"))).isEqualTo("my-maven");
assertThat(resource.getAttribute(stringKey("key1"))).isEqualTo("val1");
assertThat(resource.getAttribute(stringKey("key2"))).isEqualTo("val2");

} finally {
System.clearProperty("otel.service.name");
System.clearProperty("otel.resource.attributes");
}
}

/** Verify defining `otel.exporter.otlp.endpoint` works */
@Test
public void testOverwrittenExporterConfiguration_1() {
System.setProperty("otel.exporter.otlp.endpoint", "https://example.com:4317");

try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) {

ConfigProperties configProperties = openTelemetrySdkService.getConfigProperties();
assertThat(configProperties.getString("otel.exporter.otlp.endpoint"))
.isEqualTo("https://example.com:4317");
assertThat(configProperties.getString("otel.traces.exporter")).isNull();
assertThat(configProperties.getString("otel.metrics.exporter")).isNull();
assertThat(configProperties.getString("otel.logs.exporter")).isNull();

} finally {
System.clearProperty("otel.exporter.otlp.endpoint");
}
}

/** Verify defining `otel.exporter.otlp.traces.endpoint` works */
@Test
public void testOverwrittenExporterConfiguration_2() {
System.clearProperty("otel.exporter.otlp.endpoint");
System.clearProperty("otel.traces.exporter");
System.setProperty("otel.exporter.otlp.traces.endpoint", "https://example.com:4317/");

try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) {

ConfigProperties configProperties = openTelemetrySdkService.getConfigProperties();
assertThat(configProperties.getString("otel.exporter.otlp.endpoint")).isNull();
assertThat(configProperties.getString("otel.exporter.otlp.traces.endpoint"))
.isEqualTo("https://example.com:4317/");
assertThat(configProperties.getString("otel.traces.exporter")).isNull();
assertThat(configProperties.getString("otel.metrics.exporter")).isEqualTo("none");
assertThat(configProperties.getString("otel.logs.exporter")).isEqualTo("none");

} finally {
System.clearProperty("otel.exporter.otlp.endpoint");
System.clearProperty("otel.traces.exporter");
System.clearProperty("otel.exporter.otlp.traces.endpoint");
}
}

/** Verify defining `otel.exporter.otlp.traces.endpoint` and `otel.traces.exporter` works */
@Test
public void testOverwrittenExporterConfiguration_3() {
System.clearProperty("otel.exporter.otlp.endpoint");
System.setProperty("otel.traces.exporter", "otlp");
System.setProperty("otel.exporter.otlp.traces.endpoint", "https://example.com:4317/");

try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) {

ConfigProperties configProperties = openTelemetrySdkService.getConfigProperties();
assertThat(configProperties.getString("otel.exporter.otlp.endpoint")).isNull();
assertThat(configProperties.getString("otel.exporter.otlp.traces.endpoint"))
.isEqualTo("https://example.com:4317/");
assertThat(configProperties.getString("otel.traces.exporter")).isEqualTo("otlp");
assertThat(configProperties.getString("otel.metrics.exporter")).isEqualTo("none");
assertThat(configProperties.getString("otel.logs.exporter")).isEqualTo("none");

} finally {
System.clearProperty("otel.exporter.otlp.endpoint");
System.clearProperty("otel.exporter.otlp.traces.endpoint");
System.clearProperty("otel.exporter.otlp.traces.protocol");
}
}

void testConfiguration(String expectedServiceName) {
// OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService();
// openTelemetrySdkService.initialize();
// try {
// Resource resource =
// openTelemetrySdkService.autoConfiguredOpenTelemetrySdk.getResource();
// assertThat(resource.getAttribute(ResourceAttributes.SERVICE_NAME))
// .isEqualTo(expectedServiceName);
// } finally {
// openTelemetrySdkService.dispose();
// GlobalOpenTelemetry.resetForTest();
// GlobalEventEmitterProvider.resetForTest();
// }
@AfterAll
static void afterAll() {
System.clearProperty("otel.exporter.otlp.endpoint");
System.clearProperty("otel.exporter.otlp.traces.endpoint");
System.clearProperty("otel.exporter.otlp.traces.protocol");
System.clearProperty("otel.resource.attributes");
System.clearProperty("otel.service.name");
System.clearProperty("otel.traces.exporter");
}
}

0 comments on commit bfd7390

Please sign in to comment.