Skip to content

Commit 897bbef

Browse files
committed
Improve pattern for validating and loading SDK extension plugins
1 parent 7b2161c commit 897bbef

16 files changed

+278
-340
lines changed

sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ComposableSamplerFactory.java

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

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

8+
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
89
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableParentThresholdSamplerModel;
910
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableProbabilitySamplerModel;
10-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableRuleBasedSamplerModel;
1111
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalComposableSamplerModel;
1212
import io.opentelemetry.sdk.extension.incubator.trace.samplers.ComposableSampler;
1313
import java.util.Map;
@@ -26,33 +26,47 @@ static ComposableSamplerFactory getInstance() {
2626
@Override
2727
public ComposableSampler create(
2828
ExperimentalComposableSamplerModel model, DeclarativeConfigContext context) {
29+
// We don't use the variable till later but call validate first to confirm there are not
30+
// multiple samplers.
31+
Map.Entry<String, DeclarativeConfigProperties> samplerKeyValue =
32+
FileConfigUtil.validateSingleKeyValue(context, model, "composable sampler");
33+
2934
if (model.getAlwaysOn() != null) {
3035
return ComposableSampler.alwaysOn();
3136
}
3237
if (model.getAlwaysOff() != null) {
3338
return ComposableSampler.alwaysOff();
3439
}
35-
ExperimentalComposableProbabilitySamplerModel probability = model.getProbability();
36-
if (probability != null) {
37-
Double ratio = probability.getRatio();
38-
if (ratio == null) {
39-
ratio = 1.0d;
40-
}
41-
return ComposableSampler.probability(ratio);
40+
if (model.getProbability() != null) {
41+
return createProbabilitySampler(model.getProbability());
4242
}
43-
ExperimentalComposableRuleBasedSamplerModel ruleBased = model.getRuleBased();
44-
if (ruleBased != null) {
45-
return ComposableRuleBasedSamplerFactory.getInstance().create(ruleBased, context);
43+
if (model.getRuleBased() != null) {
44+
return ComposableRuleBasedSamplerFactory.getInstance().create(model.getRuleBased(), context);
4645
}
47-
ExperimentalComposableParentThresholdSamplerModel parentThreshold = model.getParentThreshold();
48-
if (parentThreshold != null) {
49-
ExperimentalComposableSamplerModel rootModel =
50-
FileConfigUtil.requireNonNull(parentThreshold.getRoot(), "parent threshold sampler root");
51-
ComposableSampler rootSampler = INSTANCE.create(rootModel, context);
52-
return ComposableSampler.parentThreshold(rootSampler);
46+
if (model.getParentThreshold() != null) {
47+
return createParentThresholdSampler(model.getParentThreshold(), context);
5348
}
54-
Map.Entry<String, ?> keyValue =
55-
FileConfigUtil.getSingletonMapEntry(model.getAdditionalProperties(), "composable sampler");
56-
return context.loadComponent(ComposableSampler.class, keyValue.getKey(), keyValue.getValue());
49+
50+
return context.loadComponent(
51+
ComposableSampler.class, samplerKeyValue.getKey(), samplerKeyValue.getValue());
52+
}
53+
54+
private static ComposableSampler createProbabilitySampler(
55+
ExperimentalComposableProbabilitySamplerModel probabilityModel) {
56+
Double ratio = probabilityModel.getRatio();
57+
if (ratio == null) {
58+
ratio = 1.0d;
59+
}
60+
return ComposableSampler.probability(ratio);
61+
}
62+
63+
private static ComposableSampler createParentThresholdSampler(
64+
ExperimentalComposableParentThresholdSamplerModel parentThresholdModel,
65+
DeclarativeConfigContext context) {
66+
ExperimentalComposableSamplerModel rootModel =
67+
FileConfigUtil.requireNonNull(
68+
parentThresholdModel.getRoot(), "parent threshold sampler root");
69+
ComposableSampler rootSampler = INSTANCE.create(rootModel, context);
70+
return ComposableSampler.parentThreshold(rootSampler);
5771
}
5872
}

sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigContext.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,8 @@ SpiHelper getSpiHelper() {
8282
* @throws DeclarativeConfigException if no matching providers are found, or if multiple are found
8383
* (i.e. conflict), or if {@link ComponentProvider#create(DeclarativeConfigProperties)} throws
8484
*/
85-
@SuppressWarnings({"unchecked", "rawtypes"})
86-
<T> T loadComponent(Class<T> type, String name, Object model) {
87-
DeclarativeConfigProperties config =
88-
DeclarativeConfiguration.toConfigProperties(model, spiHelper.getComponentLoader());
89-
85+
@SuppressWarnings({"unchecked"})
86+
<T> T loadComponent(Class<T> type, String name, DeclarativeConfigProperties config) {
9087
// TODO(jack-berg): cache loaded component providers
9188
List<ComponentProvider> componentProviders = spiHelper.load(ComponentProvider.class);
9289
List<ComponentProvider> matchedProviders =

sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/FileConfigUtil.java

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,39 @@
88
import static java.util.stream.Collectors.joining;
99

1010
import io.opentelemetry.api.incubator.config.DeclarativeConfigException;
11+
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
12+
import java.util.AbstractMap;
1113
import java.util.Map;
14+
import java.util.Set;
1215
import javax.annotation.Nullable;
1316

1417
final class FileConfigUtil {
1518

1619
private FileConfigUtil() {}
1720

18-
static <T> T assertNotNull(@Nullable T object, String description) {
19-
if (object == null) {
20-
throw new NullPointerException(description + " is null");
21-
}
22-
return object;
23-
}
24-
2521
static <T> T requireNonNull(@Nullable T object, String description) {
2622
if (object == null) {
2723
throw new DeclarativeConfigException(description + " is required but is null");
2824
}
2925
return object;
3026
}
3127

32-
static <T> Map.Entry<String, T> getSingletonMapEntry(
33-
Map<String, T> additionalProperties, String resourceName) {
34-
if (additionalProperties.isEmpty()) {
35-
throw new DeclarativeConfigException(resourceName + " must be set");
36-
}
37-
if (additionalProperties.size() > 1) {
38-
throw new DeclarativeConfigException(
39-
"Invalid configuration - multiple "
40-
+ resourceName
41-
+ "s set: "
42-
+ additionalProperties.keySet().stream().collect(joining(",", "[", "]")));
43-
}
44-
return additionalProperties.entrySet().stream()
45-
.findFirst()
46-
.orElseThrow(
47-
() ->
48-
new IllegalStateException(
49-
"Missing " + resourceName + ". This is a programming error."));
50-
}
51-
52-
static void requireNullResource(
53-
@Nullable Object resource, String resourceName, Map<String, ?> additionalProperties) {
54-
if (resource != null) {
28+
static Map.Entry<String, DeclarativeConfigProperties> validateSingleKeyValue(
29+
DeclarativeConfigContext context, Object model, String resourceName) {
30+
DeclarativeConfigProperties modelConfigProperties =
31+
DeclarativeConfiguration.toConfigProperties(
32+
model, context.getSpiHelper().getComponentLoader());
33+
Set<String> propertyKeys = modelConfigProperties.getPropertyKeys();
34+
if (propertyKeys.size() != 1) {
35+
String suffix =
36+
propertyKeys.isEmpty()
37+
? ""
38+
: ": " + propertyKeys.stream().collect(joining(",", "[", "]"));
5539
throw new DeclarativeConfigException(
56-
"Invalid configuration - multiple "
57-
+ resourceName
58-
+ "s set: "
59-
+ additionalProperties.keySet().stream().collect(joining(",", "[", "]")));
40+
resourceName + " must have exactly one entry but has " + propertyKeys.size() + suffix);
6041
}
42+
String key = propertyKeys.iterator().next();
43+
DeclarativeConfigProperties value = modelConfigProperties.getStructured(key);
44+
return new AbstractMap.SimpleEntry<>(key, value);
6145
}
6246
}

sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordExporterFactory.java

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

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

8+
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
89
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel;
910
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
10-
import java.util.LinkedHashMap;
1111
import java.util.Map;
1212

1313
final class LogRecordExporterFactory implements Factory<LogRecordExporterModel, LogRecordExporter> {
@@ -21,26 +21,13 @@ static LogRecordExporterFactory getInstance() {
2121

2222
@Override
2323
public LogRecordExporter create(LogRecordExporterModel model, DeclarativeConfigContext context) {
24-
Map<String, Object> exporterResourceByName = new LinkedHashMap<>();
25-
26-
if (model.getOtlpHttp() != null) {
27-
exporterResourceByName.put("otlp_http", model.getOtlpHttp());
28-
}
29-
if (model.getOtlpGrpc() != null) {
30-
exporterResourceByName.put("otlp_grpc", model.getOtlpGrpc());
31-
}
32-
if (model.getOtlpFileDevelopment() != null) {
33-
exporterResourceByName.put("otlp_file/development", model.getOtlpFileDevelopment());
34-
}
35-
if (model.getConsole() != null) {
36-
exporterResourceByName.put("console", model.getConsole());
37-
}
38-
exporterResourceByName.putAll(model.getAdditionalProperties());
39-
40-
Map.Entry<String, ?> keyValue =
41-
FileConfigUtil.getSingletonMapEntry(exporterResourceByName, "log record exporter");
42-
LogRecordExporter metricExporter =
43-
context.loadComponent(LogRecordExporter.class, keyValue.getKey(), keyValue.getValue());
44-
return context.addCloseable(metricExporter);
24+
Map.Entry<String, DeclarativeConfigProperties> logRecordExporterKeyValue =
25+
FileConfigUtil.validateSingleKeyValue(context, model, "log record exporter");
26+
LogRecordExporter logRecordExporter =
27+
context.loadComponent(
28+
LogRecordExporter.class,
29+
logRecordExporterKeyValue.getKey(),
30+
logRecordExporterKeyValue.getValue());
31+
return context.addCloseable(logRecordExporter);
4532
}
4633
}

sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/LogRecordProcessorFactory.java

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

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

8+
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
89
import io.opentelemetry.api.metrics.MeterProvider;
910
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.BatchLogRecordProcessorModel;
1011
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordExporterModel;
1112
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessorModel;
12-
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.LogRecordProcessorPropertyModel;
1313
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.SimpleLogRecordProcessorModel;
1414
import io.opentelemetry.sdk.logs.LogRecordProcessor;
1515
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
@@ -33,54 +33,64 @@ static LogRecordProcessorFactory getInstance() {
3333
@Override
3434
public LogRecordProcessor create(
3535
LogRecordProcessorModel model, DeclarativeConfigContext context) {
36-
BatchLogRecordProcessorModel batchModel = model.getBatch();
37-
if (batchModel != null) {
38-
LogRecordExporterModel exporterModel =
39-
FileConfigUtil.requireNonNull(
40-
batchModel.getExporter(), "batch log record processor exporter");
36+
// We don't use the variable till later but call validate first to confirm there are not
37+
// multiple samplers.
38+
Map.Entry<String, DeclarativeConfigProperties> processorKeyValue =
39+
FileConfigUtil.validateSingleKeyValue(context, model, "log record processor");
4140

42-
LogRecordExporter logRecordExporter =
43-
LogRecordExporterFactory.getInstance().create(exporterModel, context);
44-
BatchLogRecordProcessorBuilder builder = BatchLogRecordProcessor.builder(logRecordExporter);
45-
if (batchModel.getExportTimeout() != null) {
46-
builder.setExporterTimeout(Duration.ofMillis(batchModel.getExportTimeout()));
47-
}
48-
if (batchModel.getMaxExportBatchSize() != null) {
49-
builder.setMaxExportBatchSize(batchModel.getMaxExportBatchSize());
50-
}
51-
if (batchModel.getMaxQueueSize() != null) {
52-
builder.setMaxQueueSize(batchModel.getMaxQueueSize());
53-
}
54-
if (batchModel.getScheduleDelay() != null) {
55-
builder.setScheduleDelay(Duration.ofMillis(batchModel.getScheduleDelay()));
56-
}
57-
MeterProvider meterProvider = context.getMeterProvider();
58-
if (meterProvider != null) {
59-
builder.setMeterProvider(meterProvider);
60-
}
61-
62-
return context.addCloseable(builder.build());
41+
if (model.getBatch() != null) {
42+
return createBatchLogRecordProcessor(model.getBatch(), context);
6343
}
64-
65-
SimpleLogRecordProcessorModel simpleModel = model.getSimple();
66-
if (simpleModel != null) {
67-
LogRecordExporterModel exporterModel =
68-
FileConfigUtil.requireNonNull(
69-
simpleModel.getExporter(), "simple log record processor exporter");
70-
LogRecordExporter logRecordExporter =
71-
LogRecordExporterFactory.getInstance().create(exporterModel, context);
72-
MeterProvider meterProvider = context.getMeterProvider();
73-
return context.addCloseable(
74-
SimpleLogRecordProcessor.builder(logRecordExporter)
75-
.setMeterProvider(() -> meterProvider)
76-
.build());
44+
if (model.getSimple() != null) {
45+
return createSimpleLogRecordProcessor(model.getSimple(), context);
7746
}
7847

79-
Map.Entry<String, LogRecordProcessorPropertyModel> keyValue =
80-
FileConfigUtil.getSingletonMapEntry(
81-
model.getAdditionalProperties(), "log record processor");
8248
LogRecordProcessor logRecordProcessor =
83-
context.loadComponent(LogRecordProcessor.class, keyValue.getKey(), keyValue.getValue());
49+
context.loadComponent(
50+
LogRecordProcessor.class, processorKeyValue.getKey(), processorKeyValue.getValue());
8451
return context.addCloseable(logRecordProcessor);
8552
}
53+
54+
private static LogRecordProcessor createBatchLogRecordProcessor(
55+
BatchLogRecordProcessorModel batchModel, DeclarativeConfigContext context) {
56+
LogRecordExporterModel exporterModel =
57+
FileConfigUtil.requireNonNull(
58+
batchModel.getExporter(), "batch log record processor exporter");
59+
60+
LogRecordExporter logRecordExporter =
61+
LogRecordExporterFactory.getInstance().create(exporterModel, context);
62+
BatchLogRecordProcessorBuilder builder = BatchLogRecordProcessor.builder(logRecordExporter);
63+
if (batchModel.getExportTimeout() != null) {
64+
builder.setExporterTimeout(Duration.ofMillis(batchModel.getExportTimeout()));
65+
}
66+
if (batchModel.getMaxExportBatchSize() != null) {
67+
builder.setMaxExportBatchSize(batchModel.getMaxExportBatchSize());
68+
}
69+
if (batchModel.getMaxQueueSize() != null) {
70+
builder.setMaxQueueSize(batchModel.getMaxQueueSize());
71+
}
72+
if (batchModel.getScheduleDelay() != null) {
73+
builder.setScheduleDelay(Duration.ofMillis(batchModel.getScheduleDelay()));
74+
}
75+
MeterProvider meterProvider = context.getMeterProvider();
76+
if (meterProvider != null) {
77+
builder.setMeterProvider(meterProvider);
78+
}
79+
80+
return context.addCloseable(builder.build());
81+
}
82+
83+
private static LogRecordProcessor createSimpleLogRecordProcessor(
84+
SimpleLogRecordProcessorModel simpleModel, DeclarativeConfigContext context) {
85+
LogRecordExporterModel exporterModel =
86+
FileConfigUtil.requireNonNull(
87+
simpleModel.getExporter(), "simple log record processor exporter");
88+
LogRecordExporter logRecordExporter =
89+
LogRecordExporterFactory.getInstance().create(exporterModel, context);
90+
MeterProvider meterProvider = context.getMeterProvider();
91+
return context.addCloseable(
92+
SimpleLogRecordProcessor.builder(logRecordExporter)
93+
.setMeterProvider(() -> meterProvider)
94+
.build());
95+
}
8696
}

sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/MetricExporterFactory.java

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55

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

8+
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
89
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PushMetricExporterModel;
910
import io.opentelemetry.sdk.metrics.export.MetricExporter;
10-
import java.util.LinkedHashMap;
1111
import java.util.Map;
1212

1313
final class MetricExporterFactory implements Factory<PushMetricExporterModel, MetricExporter> {
@@ -21,26 +21,13 @@ static MetricExporterFactory getInstance() {
2121

2222
@Override
2323
public MetricExporter create(PushMetricExporterModel model, DeclarativeConfigContext context) {
24-
Map<String, Object> exporterResourceByName = new LinkedHashMap<>();
25-
26-
if (model.getOtlpHttp() != null) {
27-
exporterResourceByName.put("otlp_http", model.getOtlpHttp());
28-
}
29-
if (model.getOtlpGrpc() != null) {
30-
exporterResourceByName.put("otlp_grpc", model.getOtlpGrpc());
31-
}
32-
if (model.getOtlpFileDevelopment() != null) {
33-
exporterResourceByName.put("otlp_file/development", model.getOtlpFileDevelopment());
34-
}
35-
if (model.getConsole() != null) {
36-
exporterResourceByName.put("console", model.getConsole());
37-
}
38-
exporterResourceByName.putAll(model.getAdditionalProperties());
39-
40-
Map.Entry<String, ?> keyValue =
41-
FileConfigUtil.getSingletonMapEntry(exporterResourceByName, "metric exporter");
24+
Map.Entry<String, DeclarativeConfigProperties> metricExporterKeyValue =
25+
FileConfigUtil.validateSingleKeyValue(context, model, "metric exporter");
4226
MetricExporter metricExporter =
43-
context.loadComponent(MetricExporter.class, keyValue.getKey(), keyValue.getValue());
27+
context.loadComponent(
28+
MetricExporter.class,
29+
metricExporterKeyValue.getKey(),
30+
metricExporterKeyValue.getValue());
4431
return context.addCloseable(metricExporter);
4532
}
4633
}

0 commit comments

Comments
 (0)