Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add file configuration to autoconfigure #5831

Merged
Merged
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
26 changes: 23 additions & 3 deletions sdk-extensions/autoconfigure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ environment variables, e.g., `OTEL_TRACES_EXPORTER=zipkin`.
* [Periodic Metric Reader](#periodic-metric-reader)
* [Metric exporters](#metric-exporters)
+ [Prometheus exporter](#prometheus-exporter)
* [Cardinality limits](#cardinality-limits)
* [Cardinality Limits](#cardinality-limits)
- [Logger provider](#logger-provider)
- [Batch log record processor](#batch-log-record-processor)
* [Batch log record processor](#batch-log-record-processor)
- [Customizing the OpenTelemetry SDK](#customizing-the-opentelemetry-sdk)
- [File Configuration](#file-configuration)

<!-- tocstop -->

Expand Down Expand Up @@ -347,7 +348,7 @@ a Prometheus server scrapes from.

The following configuration options are specific to `SdkLoggerProvider`. See [general configuration](#general-configuration) for general configuration.

## Batch log record processor
### Batch log record processor

| System property | Environment variable | Description |
|---------------------------------|---------------------------------|------------------------------------------------------------------------------------|
Expand All @@ -361,3 +362,22 @@ The following configuration options are specific to `SdkLoggerProvider`. See [ge
Autoconfiguration exposes SPI [hooks](../autoconfigure-spi/src/main/java/io/opentelemetry/sdk/autoconfigure/spi) for customizing behavior programmatically as needed.
It's recommended to use the above configuration properties where possible, only implementing the SPI to add functionality not found in the
SDK by default.

## File Configuration

**Experimental**: File based configuration is experimental and subject to breaking changes.

File configuration allows for configuration via a YAML as described
in [opentelemetry-configuration](https://github.com/open-telemetry/opentelemetry-configuration)
and [file configuration](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/file-configuration.md).

To use, include `io.opentelemetry:opentelemetry-sdk-extension:incubator:<version>` and specify the
path to the config file as described in the table below.

| System property | Environment variable | Purpose |
|------------------|----------------------|------------------------------------------------------------|
| otel.config.file | OTEL_CONFIG_FILE | The path to the SDK configuration file. Defaults to unset. |

NOTE: When a config file is specified, other environment variables described in this document along
with SPI [customizations](#customizing-the-opentelemetry-sdk) are ignored. The contents of the file
alone dictate SDK configuration.
1 change: 1 addition & 0 deletions sdk-extensions/autoconfigure/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ testing {
implementation(project(":sdk:testing"))
implementation(project(":sdk:trace-shaded-deps"))
implementation(project(":sdk-extensions:jaeger-remote-sampler"))
implementation(project(":sdk-extensions:incubator"))

implementation("com.google.guava:guava")
implementation("io.opentelemetry.proto:opentelemetry-proto")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -331,6 +336,13 @@

ConfigProperties config = getConfig();

AutoConfiguredOpenTelemetrySdk fromFileConfiguration = maybeConfigureFromFile(config);
if (fromFileConfiguration != null) {
maybeRegisterShutdownHook(fromFileConfiguration.getOpenTelemetrySdk());
maybeSetAsGlobal(fromFileConfiguration.getOpenTelemetrySdk());
return fromFileConfiguration;
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
}

Resource resource =
ResourceConfiguration.configureResource(config, spiHelper, resourceCustomizer);

Expand Down Expand Up @@ -391,18 +403,8 @@
openTelemetrySdk = sdkBuilder.build();
}

// NOTE: Shutdown hook registration is untested. Modify with caution.
if (registerShutdownHook) {
Runtime.getRuntime().addShutdownHook(shutdownHook(openTelemetrySdk));
}

if (setResultAsGlobal) {
GlobalOpenTelemetry.set(openTelemetrySdk);
GlobalEventEmitterProvider.set(
SdkEventEmitterProvider.create(openTelemetrySdk.getSdkLoggerProvider()));
logger.log(
Level.FINE, "Global OpenTelemetry set to {0} by autoconfiguration", openTelemetrySdk);
}
maybeRegisterShutdownHook(openTelemetrySdk);
maybeSetAsGlobal(openTelemetrySdk);

return AutoConfiguredOpenTelemetrySdk.create(openTelemetrySdk, resource, config);
} catch (RuntimeException e) {
Expand All @@ -424,6 +426,58 @@
}
}

@Nullable
private static AutoConfiguredOpenTelemetrySdk maybeConfigureFromFile(ConfigProperties config) {
String configurationFile = config.getString("OTEL_CONFIG_FILE");
if (configurationFile == null || configurationFile.isEmpty()) {
return null;
}
FileInputStream fis;
try {
fis = new FileInputStream(configurationFile);
} catch (FileNotFoundException e) {
throw new ConfigurationException("Configuration file not found", e);

Check warning on line 439 in sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java

View check run for this annotation

Codecov / codecov/patch

sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java#L438-L439

Added lines #L438 - L439 were not covered by tests
}
try {
Class<?> configurationFactory =
Class.forName("io.opentelemetry.sdk.extension.incubator.fileconfig.ConfigurationFactory");
Method parseAndInterpret =
configurationFactory.getMethod("parseAndInterpret", InputStream.class);
OpenTelemetrySdk sdk = (OpenTelemetrySdk) parseAndInterpret.invoke(null, fis);
// Note: can't access file configuration resource without reflection so setting a dummy
// resource
return AutoConfiguredOpenTelemetrySdk.create(sdk, Resource.getDefault(), config);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
throw new ConfigurationException(
"Error configuring from file. Is opentelemetry-sdk-extension-incubator on the classpath?",
e);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof ConfigurationException) {
throw (ConfigurationException) cause;
}
throw new ConfigurationException("Unexpected error configuring from file", e);

Check warning on line 459 in sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java

View check run for this annotation

Codecov / codecov/patch

sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkBuilder.java#L459

Added line #L459 was not covered by tests
}
}

private void maybeRegisterShutdownHook(OpenTelemetrySdk openTelemetrySdk) {
if (!registerShutdownHook) {
return;
}
Runtime.getRuntime().addShutdownHook(shutdownHook(openTelemetrySdk));
}

private void maybeSetAsGlobal(OpenTelemetrySdk openTelemetrySdk) {
if (!setResultAsGlobal) {
return;
}
GlobalOpenTelemetry.set(openTelemetrySdk);
GlobalEventEmitterProvider.set(
SdkEventEmitterProvider.create(openTelemetrySdk.getSdkLoggerProvider()));
logger.log(
Level.FINE, "Global OpenTelemetry set to {0} by autoconfiguration", openTelemetrySdk);
}

@SuppressWarnings("deprecation") // Support deprecated SdkTracerProviderConfigurer
private void mergeSdkTracerProviderConfigurer() {
for (io.opentelemetry.sdk.autoconfigure.spi.traces.SdkTracerProviderConfigurer configurer :
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.autoconfigure;

import static org.assertj.core.api.Assertions.assertThatThrownBy;

import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

class FileConfigurationTest {

@Test
void configFile(@TempDir Path tempDir) throws IOException {
String yaml =
"file_format: \"0.1\"\n"
+ "resource:\n"
+ " attributes:\n"
+ " service.name: test\n"
+ "tracer_provider:\n"
+ " processors:\n"
+ " - simple:\n"
+ " exporter:\n"
+ " console: {}\n";
Path path = tempDir.resolve("otel-config.yaml");
Files.write(path, yaml.getBytes(StandardCharsets.UTF_8));
ConfigProperties config =
DefaultConfigProperties.createFromMap(
Collections.singletonMap("OTEL_CONFIG_FILE", path.toString()));

assertThatThrownBy(() -> AutoConfiguredOpenTelemetrySdk.builder().setConfig(config).build())
.isInstanceOf(ConfigurationException.class)
.hasMessage(
"Error configuring from file. Is opentelemetry-sdk-extension-incubator on the classpath?");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.autoconfigure;

import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
import io.opentelemetry.api.events.GlobalEventEmitterProvider;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.internal.testing.CleanupExtension;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import io.opentelemetry.sdk.logs.internal.SdkEventEmitterProvider;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;

class FileConfigurationTest {

@RegisterExtension private static final CleanupExtension cleanup = new CleanupExtension();

@TempDir private Path tempDir;
private Path configFilePath;

@BeforeEach
void setup() throws IOException {
String yaml =
"file_format: \"0.1\"\n"
+ "resource:\n"
+ " attributes:\n"
+ " service.name: test\n"
+ "tracer_provider:\n"
+ " processors:\n"
+ " - simple:\n"
+ " exporter:\n"
+ " console: {}\n";
configFilePath = tempDir.resolve("otel-config.yaml");
Files.write(configFilePath, yaml.getBytes(StandardCharsets.UTF_8));
GlobalOpenTelemetry.resetForTest();
GlobalEventEmitterProvider.resetForTest();
}

@Test
void configFile_Valid() {
ConfigProperties config =
DefaultConfigProperties.createFromMap(
Collections.singletonMap("OTEL_CONFIG_FILE", configFilePath.toString()));
OpenTelemetrySdk expectedSdk =
OpenTelemetrySdk.builder()
.setTracerProvider(
SdkTracerProvider.builder()
.setResource(
Resource.getDefault().toBuilder().put("service.name", "test").build())
.addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create()))
.build())
.setPropagators(
ContextPropagators.create(
TextMapPropagator.composite(
W3CTraceContextPropagator.getInstance(),
W3CBaggagePropagator.getInstance())))
.build();
cleanup.addCloseable(expectedSdk);
AutoConfiguredOpenTelemetrySdkBuilder builder = spy(AutoConfiguredOpenTelemetrySdk.builder());
Thread thread = new Thread();
doReturn(thread).when(builder).shutdownHook(any());

AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
builder.setConfig(config).build();
cleanup.addCloseable(autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk());

assertThat(autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk().toString())
.isEqualTo(expectedSdk.toString());
// AutoConfiguredOpenTelemetrySdk#getResource() is set to a dummy value when configuring from
// file
assertThat(autoConfiguredOpenTelemetrySdk.getResource()).isEqualTo(Resource.getDefault());
verify(builder, times(1)).shutdownHook(autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk());
assertThat(Runtime.getRuntime().removeShutdownHook(thread)).isTrue();
}

@Test
void configFile_NoShutdownHook() {
ConfigProperties config =
DefaultConfigProperties.createFromMap(
Collections.singletonMap("OTEL_CONFIG_FILE", configFilePath.toString()));
AutoConfiguredOpenTelemetrySdkBuilder builder = spy(AutoConfiguredOpenTelemetrySdk.builder());

AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
builder.setConfig(config).disableShutdownHook().build();
cleanup.addCloseable(autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk());

verify(builder, never()).shutdownHook(any());
}

@Test
void configFile_setResultAsGlobalFalse() {
GlobalOpenTelemetry.set(OpenTelemetry.noop());
ConfigProperties config =
DefaultConfigProperties.createFromMap(
Collections.singletonMap("OTEL_CONFIG_FILE", configFilePath.toString()));

AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
AutoConfiguredOpenTelemetrySdk.builder().setConfig(config).build();
OpenTelemetrySdk openTelemetrySdk = autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk();
cleanup.addCloseable(openTelemetrySdk);

assertThat(GlobalOpenTelemetry.get()).extracting("delegate").isNotSameAs(openTelemetrySdk);
assertThat(GlobalEventEmitterProvider.get())
.isNotSameAs(openTelemetrySdk.getSdkLoggerProvider());
}

@Test
void configFile_setResultAsGlobalTrue() {
ConfigProperties config =
DefaultConfigProperties.createFromMap(
Collections.singletonMap("OTEL_CONFIG_FILE", configFilePath.toString()));

AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
AutoConfiguredOpenTelemetrySdk.builder().setConfig(config).setResultAsGlobal().build();
OpenTelemetrySdk openTelemetrySdk = autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk();
cleanup.addCloseable(openTelemetrySdk);

assertThat(GlobalOpenTelemetry.get()).extracting("delegate").isSameAs(openTelemetrySdk);
assertThat(GlobalEventEmitterProvider.get())
.isInstanceOf(SdkEventEmitterProvider.class)
.extracting("delegateLoggerProvider")
.isSameAs(openTelemetrySdk.getSdkLoggerProvider());
}

@Test
void configFile_Error(@TempDir Path tempDir) throws IOException {
String yaml =
"file_format: \"0.1\"\n"
+ "resource:\n"
+ " attributes:\n"
+ " service.name: test\n"
+ "tracer_provider:\n"
+ " processors:\n"
+ " - simple:\n"
+ " exporter:\n"
+ " foo: {}\n";
Path path = tempDir.resolve("otel-config.yaml");
Files.write(path, yaml.getBytes(StandardCharsets.UTF_8));
ConfigProperties config =
DefaultConfigProperties.createFromMap(
Collections.singletonMap("OTEL_CONFIG_FILE", path.toString()));

assertThatThrownBy(() -> AutoConfiguredOpenTelemetrySdk.builder().setConfig(config).build())
.isInstanceOf(ConfigurationException.class)
.hasMessage("Unrecognized span exporter(s): [foo]");
}
}