Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class DeclarativeConfigContext {
@Nullable private Resource resource = null;
@Nullable private ConfigProvider configProvider;
@Nullable private List<ComponentProvider> componentProviders = null;
@Nullable private DeclarativeConfigurationBuilder builder;

// Visible for testing
DeclarativeConfigContext(SpiHelper spiHelper) {
Expand Down Expand Up @@ -79,6 +80,14 @@ void setResource(Resource resource) {
this.resource = resource;
}

void setBuilder(DeclarativeConfigurationBuilder builder) {
this.builder = builder;
}

DeclarativeConfigurationBuilder getBuilder() {
return Objects.requireNonNull(builder, "builder has not been set");
}

/**
* Overload of {@link #setInternalTelemetry(Consumer, Consumer)} for components which do not
* support setting {@link InternalTelemetryVersion} because they only support {@link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public static ExtendedOpenTelemetrySdk create(
private static ExtendedOpenTelemetrySdk create(
OpenTelemetryConfigurationModel configurationModel, DeclarativeConfigContext context) {
DeclarativeConfigurationBuilder builder = new DeclarativeConfigurationBuilder();
context.setBuilder(builder);

SpiHelper spiHelper = context.getSpiHelper();
for (DeclarativeConfigurationCustomizerProvider provider :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,68 @@

package io.opentelemetry.sdk.extension.incubator.fileconfig;

import io.opentelemetry.api.incubator.config.DeclarativeConfigException;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
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.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;

/** Builder for the declarative configuration. */
public class DeclarativeConfigurationBuilder implements DeclarativeConfigurationCustomizer {
private Function<OpenTelemetryConfigurationModel, OpenTelemetryConfigurationModel>
modelCustomizer = Function.identity();

private final List<Customizer<SpanExporter>> spanExporterCustomizers = new ArrayList<>();
private final List<Customizer<MetricExporter>> metricExporterCustomizers = new ArrayList<>();
private final List<Customizer<LogRecordExporter>> logRecordExporterCustomizers =
new ArrayList<>();

@Override
public void addModelCustomizer(
Function<OpenTelemetryConfigurationModel, OpenTelemetryConfigurationModel> customizer) {
modelCustomizer = mergeCustomizer(modelCustomizer, customizer);
}

@Override
public <T extends SpanExporter> void addSpanExporterCustomizer(
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer) {
spanExporterCustomizers.add(new Customizer<>(exporterType, customizer));
}

@Override
public <T extends MetricExporter> void addMetricExporterCustomizer(
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer) {
metricExporterCustomizers.add(new Customizer<>(exporterType, customizer));
}

@Override
public <T extends LogRecordExporter> void addLogRecordExporterCustomizer(
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer) {
logRecordExporterCustomizers.add(new Customizer<>(exporterType, customizer));
}

List<Customizer<SpanExporter>> getSpanExporterCustomizers() {
return Collections.unmodifiableList(spanExporterCustomizers);
}

List<Customizer<MetricExporter>> getMetricExporterCustomizers() {
return Collections.unmodifiableList(metricExporterCustomizers);
}

List<Customizer<LogRecordExporter>> getLogRecordExporterCustomizers() {
return Collections.unmodifiableList(logRecordExporterCustomizers);
}

private static <I, O1, O2> Function<I, O2> mergeCustomizer(
Function<? super I, ? extends O1> first, Function<? super O1, ? extends O2> second) {
return (I configured) -> {
Expand All @@ -32,4 +80,37 @@ public OpenTelemetryConfigurationModel customizeModel(
OpenTelemetryConfigurationModel configurationModel) {
return modelCustomizer.apply(configurationModel);
}

static class Customizer<T> {
private static final Logger logger = Logger.getLogger(Customizer.class.getName());

private final Class<? extends T> exporterType;
private final BiFunction<T, DeclarativeConfigProperties, T> customizer;

@SuppressWarnings("unchecked")
<E extends T> Customizer(
Class<E> exporterType, BiFunction<E, DeclarativeConfigProperties, E> customizer) {
this.exporterType = exporterType;
this.customizer = (BiFunction<T, DeclarativeConfigProperties, T>) customizer;
}

T maybeCustomize(T exporter, String name, DeclarativeConfigProperties properties) {
if (!exporterType.isInstance(exporter)) {
return exporter;
}
T customized = customizer.apply(exporter, properties);
if (customized == null) {
throw new DeclarativeConfigException(
"Customizer returned null for " + exporterType.getSimpleName() + ": " + name);
}
if (customized != exporter && exporter instanceof Closeable) {
try {
((Closeable) exporter).close();
} catch (IOException e) {
logger.log(Level.WARNING, "Failed to close exporter after customization", e);
}
}
return customized;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@

package io.opentelemetry.sdk.extension.incubator.fileconfig;

import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
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.function.BiFunction;
import java.util.function.Function;

/** A service provider interface (SPI) for customizing declarative configuration. */
Expand All @@ -18,4 +23,43 @@ public interface DeclarativeConfigurationCustomizer {
*/
void addModelCustomizer(
Function<OpenTelemetryConfigurationModel, OpenTelemetryConfigurationModel> customizer);

/**
* Add customizer for {@link SpanExporter} instances created from declarative configuration.
* Multiple customizers compose in registration order.
*
* @param exporterType the exporter type to customize
* @param customizer function receiving (exporter, properties) and returning customized exporter;
* must not return null
* @param <T> the exporter type
*/
<T extends SpanExporter> void addSpanExporterCustomizer(
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer);

/**
* Add customizer for {@link MetricExporter} instances created from declarative configuration.
* Multiple customizers compose in registration order.
*
* @param exporterType the exporter type to customize
* @param customizer function receiving (exporter, properties) and returning customized exporter;
* must not return null
* @param <T> the exporter type
*/
<T extends MetricExporter> void addMetricExporterCustomizer(
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer);

/**
* Add customizer for {@link LogRecordExporter} instances created from declarative configuration.
* Multiple customizers compose in registration order.
*
* <p>If the customizer wraps the exporter in a new {@link java.io.Closeable} instance, the
* customizer is responsible for resource cleanup.
*
* @param exporterType the exporter type to customize
* @param customizer function receiving (exporter, properties) and returning customized exporter;
* must not return null
* @param <T> the exporter type
*/
<T extends LogRecordExporter> void addLogRecordExporterCustomizer(
Class<T> exporterType, BiFunction<T, DeclarativeConfigProperties, T> customizer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ static LogRecordExporterFactory getInstance() {
public LogRecordExporter create(LogRecordExporterModel model, DeclarativeConfigContext context) {
ConfigKeyValue logRecordExporterKeyValue =
FileConfigUtil.validateSingleKeyValue(context, model, "log record exporter");
return context.loadComponent(LogRecordExporter.class, logRecordExporterKeyValue);
LogRecordExporter exporter =
context.loadComponent(LogRecordExporter.class, logRecordExporterKeyValue);
for (DeclarativeConfigurationBuilder.Customizer<LogRecordExporter> customizer :
context.getBuilder().getLogRecordExporterCustomizers()) {
exporter =
customizer.maybeCustomize(
exporter, logRecordExporterKeyValue.getKey(), logRecordExporterKeyValue.getValue());
}
return exporter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ static MetricExporterFactory getInstance() {
public MetricExporter create(PushMetricExporterModel model, DeclarativeConfigContext context) {
ConfigKeyValue metricExporterKeyValue =
FileConfigUtil.validateSingleKeyValue(context, model, "metric exporter");
return context.loadComponent(MetricExporter.class, metricExporterKeyValue);
MetricExporter exporter = context.loadComponent(MetricExporter.class, metricExporterKeyValue);
for (DeclarativeConfigurationBuilder.Customizer<MetricExporter> customizer :
context.getBuilder().getMetricExporterCustomizers()) {
exporter =
customizer.maybeCustomize(
exporter, metricExporterKeyValue.getKey(), metricExporterKeyValue.getValue());
}
return exporter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ static SpanExporterFactory getInstance() {
public SpanExporter create(SpanExporterModel model, DeclarativeConfigContext context) {
ConfigKeyValue spanExporterKeyValue =
FileConfigUtil.validateSingleKeyValue(context, model, "span exporter");
return context.loadComponent(SpanExporter.class, spanExporterKeyValue);
SpanExporter exporter = context.loadComponent(SpanExporter.class, spanExporterKeyValue);
for (DeclarativeConfigurationBuilder.Customizer<SpanExporter> customizer :
context.getBuilder().getSpanExporterCustomizers()) {
exporter =
customizer.maybeCustomize(
exporter, spanExporterKeyValue.getKey(), spanExporterKeyValue.getValue());
}
return exporter;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.sdk.extension.incubator.fileconfig;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import org.junit.jupiter.api.Test;

class DeclarativeConfigurationBuilderTest {

@Test
void spanExporterCustomizer_Single() {
DeclarativeConfigurationBuilder builder = new DeclarativeConfigurationBuilder();

builder.addSpanExporterCustomizer(
SpanExporter.class, (exporter, properties) -> mock(SpanExporter.class));

assertThat(builder.getSpanExporterCustomizers()).hasSize(1);
}

@Test
void spanExporterCustomizer_Multiple_Compose() {
DeclarativeConfigurationBuilder builder = new DeclarativeConfigurationBuilder();

builder.addSpanExporterCustomizer(
SpanExporter.class, (exporter, properties) -> mock(SpanExporter.class));
builder.addSpanExporterCustomizer(
SpanExporter.class, (exporter, properties) -> mock(SpanExporter.class));

assertThat(builder.getSpanExporterCustomizers()).hasSize(2);
}

@Test
void metricExporterCustomizer_Single() {
DeclarativeConfigurationBuilder builder = new DeclarativeConfigurationBuilder();

builder.addMetricExporterCustomizer(
MetricExporter.class, (exporter, properties) -> mock(MetricExporter.class));

assertThat(builder.getMetricExporterCustomizers()).hasSize(1);
}

@Test
void metricExporterCustomizer_Multiple_Compose() {
DeclarativeConfigurationBuilder builder = new DeclarativeConfigurationBuilder();

builder.addMetricExporterCustomizer(
MetricExporter.class, (exporter, properties) -> mock(MetricExporter.class));
builder.addMetricExporterCustomizer(
MetricExporter.class, (exporter, properties) -> mock(MetricExporter.class));

assertThat(builder.getMetricExporterCustomizers()).hasSize(2);
}

@Test
void logRecordExporterCustomizer_Single() {
DeclarativeConfigurationBuilder builder = new DeclarativeConfigurationBuilder();

builder.addLogRecordExporterCustomizer(
LogRecordExporter.class, (exporter, properties) -> mock(LogRecordExporter.class));

assertThat(builder.getLogRecordExporterCustomizers()).hasSize(1);
}

@Test
void logRecordExporterCustomizer_Multiple_Compose() {
DeclarativeConfigurationBuilder builder = new DeclarativeConfigurationBuilder();

builder.addLogRecordExporterCustomizer(
LogRecordExporter.class, (exporter, properties) -> mock(LogRecordExporter.class));
builder.addLogRecordExporterCustomizer(
LogRecordExporter.class, (exporter, properties) -> mock(LogRecordExporter.class));

assertThat(builder.getLogRecordExporterCustomizers()).hasSize(2);
}

@Test
void customizer_ClosesOriginalWhenReplaced() throws Exception {
SpanExporter original = mock(SpanExporter.class);
SpanExporter replacement = mock(SpanExporter.class);
DeclarativeConfigProperties props = mock(DeclarativeConfigProperties.class);

DeclarativeConfigurationBuilder.Customizer<SpanExporter> customizer =
new DeclarativeConfigurationBuilder.Customizer<>(
SpanExporter.class, (exporter, properties) -> replacement);

SpanExporter result = customizer.maybeCustomize(original, "test", props);

assertThat(result).isSameAs(replacement);
verify(original).close();
}

@Test
void customizer_DoesNotCloseWhenSameInstance() throws Exception {
SpanExporter exporter = mock(SpanExporter.class);
DeclarativeConfigProperties props = mock(DeclarativeConfigProperties.class);

DeclarativeConfigurationBuilder.Customizer<SpanExporter> customizer =
new DeclarativeConfigurationBuilder.Customizer<>(SpanExporter.class, (e, properties) -> e);

SpanExporter result = customizer.maybeCustomize(exporter, "test", props);

assertThat(result).isSameAs(exporter);
verify(exporter, never()).close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ void parseAndCreate_Exception_CleansUpPartials() {
// exporter with OTLP exporter, following by invalid batch exporter which references invalid
// exporter "foo".
String yaml =
"file_format: \"1.0-rc.1\"\n"
"file_format: \"1.0-rc.3\"\n"
+ "logger_provider:\n"
+ " processors:\n"
+ " - batch:\n"
Expand All @@ -141,7 +141,7 @@ void parseAndCreate_Exception_CleansUpPartials() {
@Test
void parseAndCreate_EmptyComponentProviderConfig() {
String yaml =
"file_format: \"1.0-rc.1\"\n"
"file_format: \"1.0-rc.3\"\n"
+ "logger_provider:\n"
+ " processors:\n"
+ " - test:\n"
Expand All @@ -159,7 +159,7 @@ void parseAndCreate_EmptyComponentProviderConfig() {
@Test
void create_ModelCustomizer() {
OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel();
model.withFileFormat("1.0-rc.1");
model.withFileFormat("1.0-rc.3");
model.withTracerProvider(
new TracerProviderModel()
.withProcessors(
Expand Down
Loading
Loading