Add autoconfiguration wrapper artifact (#2401)
* Add autoconfiguration wrapper artifact * WIP * WIP * WIP * WIP * WIP * Mostly done * Propagator classpath * Finish * Cleanup * Cleanup * Not visible * Update sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ConfigProperties.java Co-authored-by: John Watson <jkwatson@gmail.com> * More merge Co-authored-by: John Watson <jkwatson@gmail.com>
This commit is contained in:
parent
0dac3f6157
commit
e749f2b262
|
@ -10,6 +10,10 @@ import io.opentelemetry.api.trace.TracerProvider;
|
|||
import io.opentelemetry.context.propagation.ContextPropagators;
|
||||
import io.opentelemetry.spi.OpenTelemetryFactory;
|
||||
import io.opentelemetry.spi.trace.TracerProviderFactory;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
|
@ -30,7 +34,11 @@ import javax.annotation.Nullable;
|
|||
* @see ContextPropagators
|
||||
*/
|
||||
public final class GlobalOpenTelemetry {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(GlobalOpenTelemetry.class.getName());
|
||||
|
||||
private static final Object mutex = new Object();
|
||||
|
||||
@Nullable private static volatile OpenTelemetry globalOpenTelemetry;
|
||||
|
||||
private GlobalOpenTelemetry() {}
|
||||
|
@ -48,6 +56,13 @@ public final class GlobalOpenTelemetry {
|
|||
if (globalOpenTelemetry == null) {
|
||||
synchronized (mutex) {
|
||||
if (globalOpenTelemetry == null) {
|
||||
|
||||
OpenTelemetry autoConfigured = maybeAutoConfigure();
|
||||
if (autoConfigured != null) {
|
||||
set(autoConfigured);
|
||||
return autoConfigured;
|
||||
}
|
||||
|
||||
OpenTelemetryFactory openTelemetryFactory = Utils.loadSpi(OpenTelemetryFactory.class);
|
||||
if (openTelemetryFactory != null) {
|
||||
set(openTelemetryFactory.create());
|
||||
|
@ -115,4 +130,31 @@ public final class GlobalOpenTelemetry {
|
|||
public static ContextPropagators getPropagators() {
|
||||
return get().getPropagators();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static OpenTelemetry maybeAutoConfigure() {
|
||||
final Class<?> openTelemetrySdkAutoConfiguration;
|
||||
try {
|
||||
openTelemetrySdkAutoConfiguration =
|
||||
Class.forName("io.opentelemetry.sdk.autoconfigure.OpenTelemetrySdkAutoConfiguration");
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Method initialize = openTelemetrySdkAutoConfiguration.getMethod("initialize");
|
||||
return (OpenTelemetry) initialize.invoke(null);
|
||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||
throw new IllegalStateException(
|
||||
"OpenTelemetrySdkAutoConfiguration detected on classpath "
|
||||
+ "but could not invoke initialize method. This is a bug in OpenTelemetry.",
|
||||
e);
|
||||
} catch (InvocationTargetException t) {
|
||||
logger.log(
|
||||
Level.SEVERE,
|
||||
"Error automatically configuring OpenTelemetry SDK. OpenTelemetry will not be enabled.",
|
||||
t.getTargetException());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,6 +200,7 @@ subprojects {
|
|||
jsr305 : "com.google.code.findbugs:jsr305:${findBugsJsr305Version}",
|
||||
prometheus_client : "io.prometheus:simpleclient:${prometheusVersion}",
|
||||
prometheus_client_common : "io.prometheus:simpleclient_common:${prometheusVersion}",
|
||||
prometheus_client_httpserver: "io.prometheus:simpleclient_httpserver:${prometheusVersion}",
|
||||
protobuf : "com.google.protobuf:protobuf-java",
|
||||
protobuf_util : "com.google.protobuf:protobuf-java-util",
|
||||
zipkin_reporter : "io.zipkin.reporter2:zipkin-reporter",
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
plugins {
|
||||
id "java-library"
|
||||
id "maven-publish"
|
||||
|
||||
id "org.unbroken-dome.test-sets"
|
||||
id "ru.vyarus.animalsniffer"
|
||||
}
|
||||
|
||||
description = 'OpenTelemetry SDK Auto-configuration'
|
||||
ext.moduleName = "io.opentelemetry.sdk.autoconfigure"
|
||||
|
||||
testSets {
|
||||
testConfigError
|
||||
testFullConfig
|
||||
testPrometheus
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':sdk:all'),
|
||||
project(':sdk:metrics')
|
||||
|
||||
compileOnly project(':extensions:trace-propagators')
|
||||
compileOnly project(':exporters:jaeger')
|
||||
compileOnly project(':exporters:logging')
|
||||
compileOnly project(':exporters:otlp:all')
|
||||
compileOnly project(':exporters:otlp:metrics')
|
||||
compileOnly project(':exporters:prometheus')
|
||||
compileOnly libraries.prometheus_client_httpserver
|
||||
compileOnly project(':exporters:zipkin')
|
||||
|
||||
testImplementation project(':proto'),
|
||||
project(':sdk:testing'),
|
||||
'com.linecorp.armeria:armeria-junit5',
|
||||
'com.linecorp.armeria:armeria-grpc'
|
||||
testRuntimeOnly 'io.grpc:grpc-netty-shaded'
|
||||
|
||||
testFullConfigImplementation project(':extensions:trace-propagators')
|
||||
testFullConfigImplementation project(':exporters:jaeger')
|
||||
testFullConfigImplementation project(':exporters:logging')
|
||||
testFullConfigImplementation project(':exporters:otlp:all')
|
||||
testFullConfigImplementation project(':exporters:otlp:metrics')
|
||||
testFullConfigImplementation project(':exporters:prometheus')
|
||||
testFullConfigImplementation libraries.prometheus_client_httpserver
|
||||
testFullConfigImplementation project(':exporters:zipkin')
|
||||
|
||||
testConfigErrorImplementation project(':extensions:trace-propagators')
|
||||
testConfigErrorImplementation project(':exporters:jaeger')
|
||||
testConfigErrorImplementation project(':exporters:logging')
|
||||
testConfigErrorImplementation project(':exporters:otlp:all')
|
||||
testConfigErrorImplementation project(':exporters:otlp:metrics')
|
||||
testConfigErrorImplementation project(':exporters:prometheus')
|
||||
testConfigErrorImplementation libraries.prometheus_client_httpserver
|
||||
testConfigErrorImplementation project(':exporters:zipkin')
|
||||
testConfigErrorImplementation libraries.junit_pioneer
|
||||
|
||||
testPrometheusImplementation project(':exporters:prometheus')
|
||||
testPrometheusImplementation libraries.prometheus_client_httpserver
|
||||
}
|
||||
|
||||
testFullConfig {
|
||||
environment("OTEL_RESOURCE_ATTRIBUTES", "service.name=test,cat=meow")
|
||||
environment("OTEL_EXPORTER", "otlp,jaeger,zipkin")
|
||||
environment("OTEL_PROPAGATORS", "tracecontext,baggage,b3,b3multi,jaeger,ottracer,xray")
|
||||
environment("OTEL_BSP_SCHEDULE_DELAY_MILLIS", "10")
|
||||
environment("OTEL_IMR_EXPORT_INTERVAL", "10")
|
||||
environment("OTEL_EXPORTER_OTLP_HEADERS", "cat=meow,dog=bark")
|
||||
environment("OTEL_EXPORTER_OTLP_TIMEOUT", "5000")
|
||||
environment("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "2")
|
||||
}
|
||||
|
||||
testPrometheus {
|
||||
environment("OTEL_EXPORTER", "prometheus")
|
||||
environment("OTEL_IMR_EXPORT_INTERVAL", "10")
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
final class ClasspathUtil {
|
||||
|
||||
@SuppressWarnings("UnusedException")
|
||||
static void checkClassExists(String className, String featureName, String requiredLibrary) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
} catch (ClassNotFoundException unused) {
|
||||
throw new ConfigurationException(
|
||||
featureName
|
||||
+ " enabled but "
|
||||
+ requiredLibrary
|
||||
+ " not found on classpath. "
|
||||
+ "Make sure to add it as a dependency to enable this feature.");
|
||||
}
|
||||
}
|
||||
|
||||
private ClasspathUtil() {}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
class ConfigProperties {
|
||||
|
||||
private final Map<String, String> config;
|
||||
|
||||
static ConfigProperties get() {
|
||||
return new ConfigProperties(System.getProperties(), System.getenv());
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
static ConfigProperties createForTest(Map<String, String> properties) {
|
||||
return new ConfigProperties(properties, Collections.emptyMap());
|
||||
}
|
||||
|
||||
private ConfigProperties(Map<?, ?> systemProperties, Map<String, String> environmentVariables) {
|
||||
Map<String, String> config = new HashMap<>();
|
||||
environmentVariables.forEach(
|
||||
(name, value) -> config.put(name.toLowerCase(Locale.ROOT).replace('_', '.'), value));
|
||||
systemProperties.forEach(
|
||||
(key, value) -> config.put(((String) key).toLowerCase(Locale.ROOT), (String) value));
|
||||
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getString(String name) {
|
||||
return config.get(name);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("UnusedException")
|
||||
Integer getInt(String name) {
|
||||
String value = config.get(name);
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw newInvalidPropertyException(name, value, "integer");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("UnusedException")
|
||||
Long getLong(String name) {
|
||||
String value = config.get(name);
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw newInvalidPropertyException(name, value, "long");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("UnusedException")
|
||||
Double getDouble(String name) {
|
||||
String value = config.get(name);
|
||||
if (value == null || value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Double.parseDouble(value);
|
||||
} catch (NumberFormatException ex) {
|
||||
throw newInvalidPropertyException(name, value, "double");
|
||||
}
|
||||
}
|
||||
|
||||
List<String> getCommaSeparatedValues(String name) {
|
||||
String value = config.get(name);
|
||||
if (value == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return filterBlanksAndNulls(value.split(","));
|
||||
}
|
||||
|
||||
Map<String, String> getCommaSeparatedMap(String name) {
|
||||
return getCommaSeparatedValues(name).stream()
|
||||
.map(keyValuePair -> filterBlanksAndNulls(keyValuePair.split("=", 2)))
|
||||
.map(
|
||||
splitKeyValuePairs -> {
|
||||
if (splitKeyValuePairs.size() != 2) {
|
||||
throw new ConfigurationException(
|
||||
"Invalid map property: " + name + "=" + config.get(name));
|
||||
}
|
||||
return new AbstractMap.SimpleImmutableEntry<>(
|
||||
splitKeyValuePairs.get(0), splitKeyValuePairs.get(1));
|
||||
})
|
||||
// If duplicate keys, prioritize later ones similar to duplicate system properties on a
|
||||
// Java command line.
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
Map.Entry::getKey, Map.Entry::getValue, (first, next) -> next, LinkedHashMap::new));
|
||||
}
|
||||
|
||||
boolean getBoolean(String name) {
|
||||
return Boolean.parseBoolean(config.get(name));
|
||||
}
|
||||
|
||||
private static ConfigurationException newInvalidPropertyException(
|
||||
String name, String value, String type) {
|
||||
throw new ConfigurationException(
|
||||
"Invalid value for property " + name + "=" + value + ". Must be a " + type + ".");
|
||||
}
|
||||
|
||||
private static List<String> filterBlanksAndNulls(String[] values) {
|
||||
return Arrays.stream(values)
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
/** An exception that is thrown if the user-provided configuration is invalid. */
|
||||
public final class ConfigurationException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 4717640118051490483L;
|
||||
|
||||
ConfigurationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
|
||||
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder;
|
||||
import io.opentelemetry.exporter.prometheus.PrometheusCollector;
|
||||
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
|
||||
import io.opentelemetry.sdk.metrics.export.IntervalMetricReader;
|
||||
import io.prometheus.client.exporter.HTTPServer;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
final class MetricExporterConfiguration {
|
||||
|
||||
static final List<String> RECOGNIZED_NAMES = Arrays.asList("otlp", "otlp_metrics", "prometheus");
|
||||
|
||||
static boolean configureExporter(
|
||||
String name,
|
||||
ConfigProperties config,
|
||||
boolean metricsAlreadyRegistered,
|
||||
SdkMeterProvider meterProvider) {
|
||||
switch (name) {
|
||||
case "otlp":
|
||||
case "otlp_metrics":
|
||||
if (metricsAlreadyRegistered) {
|
||||
throw new ConfigurationException(
|
||||
"Multiple metrics exporters configured. Only one metrics exporter can be "
|
||||
+ "configured at a time.");
|
||||
}
|
||||
configureOtlpMetrics(config, meterProvider);
|
||||
return true;
|
||||
case "prometheus":
|
||||
if (metricsAlreadyRegistered) {
|
||||
throw new ConfigurationException(
|
||||
"Multiple metrics exporters configured. Only one metrics exporter can be "
|
||||
+ "configured at a time.");
|
||||
}
|
||||
configurePrometheusMetrics(config, meterProvider);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
static OtlpGrpcMetricExporter configureOtlpMetrics(
|
||||
ConfigProperties config, SdkMeterProvider meterProvider) {
|
||||
ClasspathUtil.checkClassExists(
|
||||
"io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter",
|
||||
"OTLP Metrics Exporter",
|
||||
"opentelemetry-exporter-otlp-metrics");
|
||||
OtlpGrpcMetricExporterBuilder builder = OtlpGrpcMetricExporter.builder();
|
||||
|
||||
String endpoint = config.getString("otel.exporter.otlp.endpoint");
|
||||
if (endpoint != null) {
|
||||
builder.setEndpoint(endpoint);
|
||||
}
|
||||
|
||||
boolean insecure = config.getBoolean("otel.exporter.otlp.insecure");
|
||||
if (!insecure) {
|
||||
builder.setUseTls(true);
|
||||
}
|
||||
|
||||
config.getCommaSeparatedMap("otel.exporter.otlp.headers").forEach(builder::addHeader);
|
||||
|
||||
Long deadlineMs = config.getLong("otel.exporter.otlp.timeout");
|
||||
if (deadlineMs != null) {
|
||||
builder.setDeadlineMs(deadlineMs);
|
||||
}
|
||||
|
||||
OtlpGrpcMetricExporter exporter = builder.build();
|
||||
|
||||
IntervalMetricReader.Builder readerBuilder =
|
||||
IntervalMetricReader.builder()
|
||||
.setMetricProducers(Collections.singleton(meterProvider.getMetricProducer()))
|
||||
.setMetricExporter(exporter);
|
||||
Long exportIntervalMillis = config.getLong("otel.imr.export.interval");
|
||||
if (exportIntervalMillis != null) {
|
||||
readerBuilder.setExportIntervalMillis(exportIntervalMillis);
|
||||
}
|
||||
IntervalMetricReader reader = readerBuilder.build();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(reader::shutdown));
|
||||
|
||||
return exporter;
|
||||
}
|
||||
|
||||
private static void configurePrometheusMetrics(
|
||||
ConfigProperties config, SdkMeterProvider meterProvider) {
|
||||
ClasspathUtil.checkClassExists(
|
||||
"io.opentelemetry.exporter.prometheus.PrometheusCollector",
|
||||
"Prometheus Metrics Server",
|
||||
"opentelemetry-exporter-prometheus");
|
||||
PrometheusCollector.builder()
|
||||
.setMetricProducer(meterProvider.getMetricProducer())
|
||||
.buildAndRegister();
|
||||
Integer port = config.getInt("otel.exporter.prometheus.port");
|
||||
if (port == null) {
|
||||
port = 9464;
|
||||
}
|
||||
String host = config.getString("otel.exporter.prometheus.host");
|
||||
if (host == null) {
|
||||
host = "0.0.0.0";
|
||||
}
|
||||
final HTTPServer server;
|
||||
try {
|
||||
server = new HTTPServer(host, port, true);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Failed to create Prometheus server", e);
|
||||
}
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(server::stop));
|
||||
}
|
||||
|
||||
private MetricExporterConfiguration() {}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import io.opentelemetry.api.common.Attributes;
|
||||
import io.opentelemetry.api.common.AttributesBuilder;
|
||||
import io.opentelemetry.context.propagation.ContextPropagators;
|
||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import io.opentelemetry.sdk.trace.SdkTracerProvider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Auto-configuration for the OpenTelemetry SDK. As an alternative to programmatically configuring
|
||||
* the SDK using {@link OpenTelemetrySdk#builder()}, this package can be used to automatically
|
||||
* configure the SDK using environment properties specified by OpenTelemetry.
|
||||
*/
|
||||
public final class OpenTelemetrySdkAutoConfiguration {
|
||||
|
||||
/**
|
||||
* Returns an {@link OpenTelemetrySdk} automatically initialized through recognized system
|
||||
* properties and environment variables.
|
||||
*/
|
||||
public static OpenTelemetrySdk initialize() {
|
||||
ConfigProperties config = ConfigProperties.get();
|
||||
ContextPropagators propagators = PropagatorConfiguration.configurePropagators(config);
|
||||
|
||||
Resource resource = configureResource(config);
|
||||
|
||||
Set<String> exporterNames =
|
||||
new LinkedHashSet<>(config.getCommaSeparatedValues("otel.exporter"));
|
||||
|
||||
Set<String> unrecognizedExporters = new LinkedHashSet<>(exporterNames);
|
||||
unrecognizedExporters.removeAll(SpanExporterConfiguration.RECOGNIZED_NAMES);
|
||||
unrecognizedExporters.removeAll(MetricExporterConfiguration.RECOGNIZED_NAMES);
|
||||
if (!unrecognizedExporters.isEmpty()) {
|
||||
throw new ConfigurationException(
|
||||
"Unrecognized value for otel.exporter: " + String.join(",", exporterNames));
|
||||
}
|
||||
|
||||
configureMeterProvider(resource, exporterNames, config);
|
||||
|
||||
SdkTracerProvider tracerProvider =
|
||||
TracerProviderConfiguration.configureTracerProvider(resource, exporterNames, config);
|
||||
|
||||
return OpenTelemetrySdk.builder()
|
||||
.setTracerProvider(tracerProvider)
|
||||
.setPropagators(propagators)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static void configureMeterProvider(
|
||||
Resource resource, Set<String> exporterNames, ConfigProperties config) {
|
||||
SdkMeterProvider meterProvider =
|
||||
SdkMeterProvider.builder().setResource(resource).buildAndRegisterGlobal();
|
||||
|
||||
boolean metricsConfigured = false;
|
||||
for (String exporterName : new ArrayList<>(exporterNames)) {
|
||||
metricsConfigured =
|
||||
MetricExporterConfiguration.configureExporter(
|
||||
exporterName, config, metricsConfigured, meterProvider);
|
||||
}
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
static Resource configureResource(ConfigProperties config) {
|
||||
AttributesBuilder resourceAttributes = Attributes.builder();
|
||||
config.getCommaSeparatedMap("otel.resource.attributes").forEach(resourceAttributes::put);
|
||||
return Resource.create(resourceAttributes.build()).merge(Resource.getDefault());
|
||||
}
|
||||
|
||||
private OpenTelemetrySdkAutoConfiguration() {}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
|
||||
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
|
||||
import io.opentelemetry.context.propagation.ContextPropagators;
|
||||
import io.opentelemetry.context.propagation.TextMapPropagator;
|
||||
import io.opentelemetry.extension.trace.propagation.AwsXrayPropagator;
|
||||
import io.opentelemetry.extension.trace.propagation.B3Propagator;
|
||||
import io.opentelemetry.extension.trace.propagation.JaegerPropagator;
|
||||
import io.opentelemetry.extension.trace.propagation.OtTracerPropagator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class PropagatorConfiguration {
|
||||
|
||||
static ContextPropagators configurePropagators(ConfigProperties config) {
|
||||
List<TextMapPropagator> propagators = new ArrayList<>();
|
||||
for (String propagatorName : config.getCommaSeparatedValues("otel.propagators")) {
|
||||
propagators.add(PropagatorConfiguration.getPropagator(propagatorName));
|
||||
}
|
||||
return ContextPropagators.create(TextMapPropagator.composite(propagators));
|
||||
}
|
||||
|
||||
private static TextMapPropagator getPropagator(String name) {
|
||||
if (name.equals("tracecontext")) {
|
||||
return W3CTraceContextPropagator.getInstance();
|
||||
}
|
||||
if (name.equals("baggage")) {
|
||||
return W3CBaggagePropagator.getInstance();
|
||||
}
|
||||
|
||||
// Other propagators are in the extension artifact. Check one of the propagators.
|
||||
ClasspathUtil.checkClassExists(
|
||||
"io.opentelemetry.extension.trace.propagation.B3Propagator",
|
||||
name + " propagator",
|
||||
"opentelemetry-extension-trace-propagators");
|
||||
|
||||
switch (name) {
|
||||
case "b3":
|
||||
return B3Propagator.getInstance();
|
||||
case "b3multi":
|
||||
return B3Propagator.builder().injectMultipleHeaders().build();
|
||||
case "jaeger":
|
||||
return JaegerPropagator.getInstance();
|
||||
case "ottracer":
|
||||
return OtTracerPropagator.getInstance();
|
||||
case "xray":
|
||||
return AwsXrayPropagator.getInstance();
|
||||
default:
|
||||
throw new ConfigurationException("Unrecognized value for otel.propagators: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
private PropagatorConfiguration() {}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter;
|
||||
import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporterBuilder;
|
||||
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
|
||||
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
|
||||
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder;
|
||||
import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
|
||||
import io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder;
|
||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
final class SpanExporterConfiguration {
|
||||
|
||||
static final List<String> RECOGNIZED_NAMES =
|
||||
Arrays.asList("otlp", "otlp_span", "jaeger", "zipkin", "logging");
|
||||
|
||||
@Nullable
|
||||
static SpanExporter configureExporter(String name, ConfigProperties config) {
|
||||
switch (name) {
|
||||
case "otlp":
|
||||
case "otlp_span":
|
||||
return configureOtlpSpans(config);
|
||||
case "jaeger":
|
||||
return configureJaeger(config);
|
||||
case "zipkin":
|
||||
return configureZipkin(config);
|
||||
case "logging":
|
||||
ClasspathUtil.checkClassExists(
|
||||
"io.opentelemetry.exporter.logging.LoggingSpanExporter",
|
||||
"Logging Trace Exporter",
|
||||
"opentelemetry-exporter-logging");
|
||||
return new LoggingSpanExporter();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
static OtlpGrpcSpanExporter configureOtlpSpans(ConfigProperties config) {
|
||||
ClasspathUtil.checkClassExists(
|
||||
"io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter",
|
||||
"OTLP Trace Exporter",
|
||||
"opentelemetry-exporter-otlp-trace");
|
||||
OtlpGrpcSpanExporterBuilder builder = OtlpGrpcSpanExporter.builder();
|
||||
|
||||
String endpoint = config.getString("otel.exporter.otlp.endpoint");
|
||||
if (endpoint != null) {
|
||||
builder.setEndpoint(endpoint);
|
||||
}
|
||||
|
||||
boolean insecure = config.getBoolean("otel.exporter.otlp.insecure");
|
||||
if (!insecure) {
|
||||
builder.setUseTls(true);
|
||||
}
|
||||
|
||||
config.getCommaSeparatedMap("otel.exporter.otlp.headers").forEach(builder::addHeader);
|
||||
|
||||
Long deadlineMs = config.getLong("otel.exporter.otlp.timeout");
|
||||
if (deadlineMs != null) {
|
||||
builder.setDeadlineMs(deadlineMs);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static SpanExporter configureJaeger(ConfigProperties config) {
|
||||
ClasspathUtil.checkClassExists(
|
||||
"io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter",
|
||||
"Jaeger gRPC Exporter",
|
||||
"opentelemetry-exporter-jaeger");
|
||||
JaegerGrpcSpanExporterBuilder builder = JaegerGrpcSpanExporter.builder();
|
||||
|
||||
String endpoint = config.getString("otel.exporter.jaeger.endpoint");
|
||||
if (endpoint != null) {
|
||||
builder.setEndpoint(endpoint);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static SpanExporter configureZipkin(ConfigProperties config) {
|
||||
ClasspathUtil.checkClassExists(
|
||||
"io.opentelemetry.exporter.zipkin.ZipkinSpanExporter",
|
||||
"Zipkin Exporter",
|
||||
"opentelemetry-exporter-zipkin");
|
||||
ZipkinSpanExporterBuilder builder = ZipkinSpanExporter.builder();
|
||||
|
||||
String endpoint = config.getString("otel.exporter.zipkin.endpoint");
|
||||
if (endpoint != null) {
|
||||
builder.setEndpoint(endpoint);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private SpanExporterConfiguration() {}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.SdkTracerProviderConfigurer;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import io.opentelemetry.sdk.trace.SdkTracerProvider;
|
||||
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
|
||||
import io.opentelemetry.sdk.trace.config.TraceConfig;
|
||||
import io.opentelemetry.sdk.trace.config.TraceConfigBuilder;
|
||||
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
|
||||
import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder;
|
||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||
import io.opentelemetry.sdk.trace.samplers.Sampler;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
||||
final class TracerProviderConfiguration {
|
||||
|
||||
static SdkTracerProvider configureTracerProvider(
|
||||
Resource resource, Set<String> exporterNames, ConfigProperties config) {
|
||||
SdkTracerProviderBuilder tracerProviderBuilder =
|
||||
SdkTracerProvider.builder()
|
||||
.setResource(resource)
|
||||
.setTraceConfig(configureTraceConfig(config));
|
||||
|
||||
// Run user configuration before setting exporters from environment to allow user span
|
||||
// processors to effect export.
|
||||
for (SdkTracerProviderConfigurer configurer :
|
||||
ServiceLoader.load(SdkTracerProviderConfigurer.class)) {
|
||||
configurer.configure(tracerProviderBuilder);
|
||||
}
|
||||
|
||||
List<SpanExporter> spanExporters = new ArrayList<>();
|
||||
for (String name : new ArrayList<>(exporterNames)) {
|
||||
SpanExporter exporter = SpanExporterConfiguration.configureExporter(name, config);
|
||||
if (exporter != null) {
|
||||
spanExporters.add(exporter);
|
||||
}
|
||||
}
|
||||
|
||||
if (!spanExporters.isEmpty()) {
|
||||
tracerProviderBuilder.addSpanProcessor(configureSpanProcessor(config, spanExporters));
|
||||
}
|
||||
|
||||
SdkTracerProvider tracerProvider = tracerProviderBuilder.build();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(tracerProvider::shutdown));
|
||||
return tracerProvider;
|
||||
}
|
||||
|
||||
// VisibleForTesting
|
||||
static BatchSpanProcessor configureSpanProcessor(
|
||||
ConfigProperties config, List<SpanExporter> exporters) {
|
||||
SpanExporter exporter = SpanExporter.composite(exporters);
|
||||
BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder(exporter);
|
||||
|
||||
Long scheduleDelayMillis = config.getLong("otel.bsp.schedule.delay.millis");
|
||||
if (scheduleDelayMillis != null) {
|
||||
builder.setScheduleDelayMillis(scheduleDelayMillis);
|
||||
}
|
||||
|
||||
Integer maxQueue = config.getInt("otel.bsp.max.queue.size");
|
||||
if (maxQueue != null) {
|
||||
builder.setMaxQueueSize(maxQueue);
|
||||
}
|
||||
|
||||
Integer maxExportBatch = config.getInt("otel.bsp.max.export.batch.size");
|
||||
if (maxExportBatch != null) {
|
||||
builder.setMaxExportBatchSize(maxExportBatch);
|
||||
}
|
||||
|
||||
Integer timeout = config.getInt("otel.bsp.export.timeout.millis");
|
||||
if (timeout != null) {
|
||||
builder.setExporterTimeoutMillis(timeout);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
static TraceConfig configureTraceConfig(ConfigProperties config) {
|
||||
TraceConfigBuilder builder = TraceConfig.getDefault().toBuilder();
|
||||
|
||||
String sampler = config.getString("otel.trace.sampler");
|
||||
if (sampler != null) {
|
||||
builder.setSampler(configureSampler(sampler, config));
|
||||
}
|
||||
|
||||
Integer maxAttrs = config.getInt("otel.span.attribute.count.limit");
|
||||
if (maxAttrs != null) {
|
||||
builder.setMaxNumberOfAttributes(maxAttrs);
|
||||
}
|
||||
|
||||
Integer maxEvents = config.getInt("otel.span.event.count.limit");
|
||||
if (maxEvents != null) {
|
||||
builder.setMaxNumberOfEvents(maxEvents);
|
||||
}
|
||||
|
||||
Integer maxLinks = config.getInt("otel.span.link.count.limit");
|
||||
if (maxLinks != null) {
|
||||
builder.setMaxNumberOfLinks(maxLinks);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
static Sampler configureSampler(String sampler, ConfigProperties config) {
|
||||
switch (sampler) {
|
||||
case "always_on":
|
||||
return Sampler.alwaysOn();
|
||||
case "always_off":
|
||||
return Sampler.alwaysOff();
|
||||
case "traceidratio":
|
||||
{
|
||||
Double ratio = config.getDouble("otel.trace.sampler.arg");
|
||||
if (ratio == null) {
|
||||
throw new ConfigurationException(
|
||||
"otel.trace.sampler=traceidratio but otel.trace.sampler.arg is not provided. "
|
||||
+ "Set otel.trace.sampler.arg to a value in the range [0.0, 1.0].");
|
||||
}
|
||||
return Sampler.traceIdRatioBased(ratio);
|
||||
}
|
||||
case "parentbased_always_on":
|
||||
return Sampler.parentBased(Sampler.alwaysOn());
|
||||
case "parentbased_always_off":
|
||||
return Sampler.parentBased(Sampler.alwaysOff());
|
||||
case "parentbased_traceidratio":
|
||||
{
|
||||
Double ratio = config.getDouble("otel.trace.sampler.arg");
|
||||
if (ratio == null) {
|
||||
throw new ConfigurationException(
|
||||
"otel.trace.sampler=parentbased_traceidratio but otel.trace.sampler.arg is not "
|
||||
+ "provided. Set otel.trace.sampler.arg to a value in the "
|
||||
+ "range [0.0, 1.0].");
|
||||
}
|
||||
return Sampler.parentBased(Sampler.traceIdRatioBased(ratio));
|
||||
}
|
||||
default:
|
||||
throw new ConfigurationException("Unrecognized value for otel.trace.sampler: " + sampler);
|
||||
}
|
||||
}
|
||||
|
||||
private TracerProviderConfiguration() {}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure.spi;
|
||||
|
||||
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
|
||||
|
||||
/**
|
||||
* A service provider interface (SPI) for performing additional programmatic configuration of a
|
||||
* {@link SdkTracerProviderBuilder} during initialization. When using auto-configuration, you should
|
||||
* prefer to use system properties or environment variables for configuration, but this may be
|
||||
* useful to register components that are not part of the SDK such as custom exporters.
|
||||
*/
|
||||
public interface SdkTracerProviderConfigurer {
|
||||
/** Configures the {@link SdkTracerProviderBuilder}. */
|
||||
void configure(SdkTracerProviderBuilder tracerProvider);
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ConfigPropertiesTest {
|
||||
|
||||
@Test
|
||||
void allValid() {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put("string", "str");
|
||||
properties.put("int", "10");
|
||||
properties.put("long", "20");
|
||||
properties.put("double", "5.4");
|
||||
properties.put("list", "cat,dog,bear");
|
||||
properties.put("map", "cat=meow,dog=bark,bear=growl");
|
||||
|
||||
ConfigProperties config = ConfigProperties.createForTest(properties);
|
||||
assertThat(config.getString("string")).isEqualTo("str");
|
||||
assertThat(config.getInt("int")).isEqualTo(10);
|
||||
assertThat(config.getLong("long")).isEqualTo(20);
|
||||
assertThat(config.getDouble("double")).isEqualTo(5.4);
|
||||
assertThat(config.getCommaSeparatedValues("list")).containsExactly("cat", "dog", "bear");
|
||||
assertThat(config.getCommaSeparatedMap("map"))
|
||||
.containsExactly(entry("cat", "meow"), entry("dog", "bark"), entry("bear", "growl"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void allMissing() {
|
||||
ConfigProperties config = ConfigProperties.createForTest(Collections.emptyMap());
|
||||
assertThat(config.getString("string")).isNull();
|
||||
assertThat(config.getInt("int")).isNull();
|
||||
assertThat(config.getLong("long")).isNull();
|
||||
assertThat(config.getDouble("double")).isNull();
|
||||
assertThat(config.getCommaSeparatedValues("list")).isEmpty();
|
||||
assertThat(config.getCommaSeparatedMap("map")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void allEmpty() {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put("string", "");
|
||||
properties.put("int", "");
|
||||
properties.put("long", "");
|
||||
properties.put("double", "");
|
||||
properties.put("list", "");
|
||||
properties.put("map", "");
|
||||
|
||||
ConfigProperties config = ConfigProperties.createForTest(properties);
|
||||
assertThat(config.getString("string")).isEmpty();
|
||||
assertThat(config.getInt("int")).isNull();
|
||||
assertThat(config.getLong("long")).isNull();
|
||||
assertThat(config.getDouble("double")).isNull();
|
||||
assertThat(config.getCommaSeparatedValues("list")).isEmpty();
|
||||
assertThat(config.getCommaSeparatedMap("map")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidInt() {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
ConfigProperties.createForTest(Collections.singletonMap("int", "bar"))
|
||||
.getInt("int"))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid value for property int=bar. Must be a integer.");
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
ConfigProperties.createForTest(Collections.singletonMap("int", "999999999999999"))
|
||||
.getInt("int"))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid value for property int=999999999999999. Must be a integer.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidLong() {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
ConfigProperties.createForTest(Collections.singletonMap("long", "bar"))
|
||||
.getLong("long"))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid value for property long=bar. Must be a long.");
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
ConfigProperties.createForTest(
|
||||
Collections.singletonMap("long", "99223372036854775807"))
|
||||
.getLong("long"))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid value for property long=99223372036854775807. Must be a long.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidDouble() {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
ConfigProperties.createForTest(Collections.singletonMap("double", "bar"))
|
||||
.getDouble("double"))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid value for property double=bar. Must be a double.");
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
ConfigProperties.createForTest(Collections.singletonMap("double", "1.0.1"))
|
||||
.getDouble("double"))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid value for property double=1.0.1. Must be a double.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void uncleanList() {
|
||||
assertThat(
|
||||
ConfigProperties.createForTest(
|
||||
Collections.singletonMap("list", " a ,b,c , d,, ,"))
|
||||
.getCommaSeparatedValues("list"))
|
||||
.containsExactly("a", "b", "c", "d");
|
||||
}
|
||||
|
||||
@Test
|
||||
void uncleanMap() {
|
||||
assertThat(
|
||||
ConfigProperties.createForTest(
|
||||
Collections.singletonMap("map", " a=1 ,b=2,c = 3 , d= 4,, ,"))
|
||||
.getCommaSeparatedMap("map"))
|
||||
.containsExactly(entry("a", "1"), entry("b", "2"), entry("c", "3"), entry("d", "4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidMap() {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
ConfigProperties.createForTest(Collections.singletonMap("map", "a=1,b="))
|
||||
.getCommaSeparatedMap("map"))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid map property: map=a=1,b=");
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
ConfigProperties.createForTest(Collections.singletonMap("map", "a=1,b"))
|
||||
.getCommaSeparatedMap("map"))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid map property: map=a=1,b");
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
ConfigProperties.createForTest(Collections.singletonMap("map", "a=1,=b"))
|
||||
.getCommaSeparatedMap("map"))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid map property: map=a=1,=b");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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.metrics.SdkMeterProvider;
|
||||
import java.util.Collections;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class NotOnClasspathTest {
|
||||
|
||||
private static final ConfigProperties EMPTY =
|
||||
ConfigProperties.createForTest(Collections.emptyMap());
|
||||
|
||||
@Test
|
||||
void otlpSpans() {
|
||||
assertThatThrownBy(() -> SpanExporterConfiguration.configureExporter("otlp", EMPTY))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessageContaining(
|
||||
"OTLP Trace Exporter enabled but opentelemetry-exporter-otlp-trace not found on "
|
||||
+ "classpath");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jaeger() {
|
||||
assertThatThrownBy(() -> SpanExporterConfiguration.configureExporter("jaeger", EMPTY))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessageContaining(
|
||||
"Jaeger gRPC Exporter enabled but opentelemetry-exporter-jaeger not found on "
|
||||
+ "classpath");
|
||||
}
|
||||
|
||||
@Test
|
||||
void zipkin() {
|
||||
assertThatThrownBy(() -> SpanExporterConfiguration.configureExporter("zipkin", EMPTY))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessageContaining(
|
||||
"Zipkin Exporter enabled but opentelemetry-exporter-zipkin not found on classpath");
|
||||
}
|
||||
|
||||
@Test
|
||||
void logging() {
|
||||
assertThatThrownBy(() -> SpanExporterConfiguration.configureExporter("logging", EMPTY))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessageContaining(
|
||||
"Logging Trace Exporter enabled but opentelemetry-exporter-logging not found on "
|
||||
+ "classpath");
|
||||
}
|
||||
|
||||
@Test
|
||||
void otlpMetrics() {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
MetricExporterConfiguration.configureExporter(
|
||||
"otlp", EMPTY, false, SdkMeterProvider.builder().build()))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessageContaining(
|
||||
"OTLP Metrics Exporter enabled but opentelemetry-exporter-otlp-metrics not found on "
|
||||
+ "classpath");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prometheus() {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
MetricExporterConfiguration.configureExporter(
|
||||
"prometheus", EMPTY, false, SdkMeterProvider.builder().build()))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessageContaining(
|
||||
"Prometheus Metrics Server enabled but opentelemetry-exporter-prometheus not found on "
|
||||
+ "classpath");
|
||||
}
|
||||
|
||||
@Test
|
||||
void b3propagator() {
|
||||
assertThatThrownBy(
|
||||
() ->
|
||||
PropagatorConfiguration.configurePropagators(
|
||||
ConfigProperties.createForTest(
|
||||
Collections.singletonMap("otel.propagators", "b3"))))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessageContaining(
|
||||
"b3 propagator enabled but opentelemetry-extension-trace-propagators not found on "
|
||||
+ "classpath");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
||||
import java.util.Collections;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class OpenTelemetrySdkAutoConfigurationTest {
|
||||
|
||||
@Test
|
||||
void resourcePrioritizesUser() {
|
||||
Resource resource =
|
||||
OpenTelemetrySdkAutoConfiguration.configureResource(
|
||||
ConfigProperties.createForTest(
|
||||
Collections.singletonMap("otel.resource.attributes", "telemetry.sdk.name=test")));
|
||||
assertThat(resource.getAttributes().get(SemanticAttributes.TELEMETRY_SDK_NAME))
|
||||
.isEqualTo("test");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import io.opentelemetry.api.common.Attributes;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import io.opentelemetry.sdk.trace.SdkTracerProvider;
|
||||
import io.opentelemetry.sdk.trace.SpanProcessor;
|
||||
import io.opentelemetry.sdk.trace.config.TraceConfig;
|
||||
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
|
||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||
import io.opentelemetry.sdk.trace.samplers.Sampler;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
// NB: We use AssertJ extracting to reflectively access implementation details to test configuration
|
||||
// because the use of BatchSpanProcessor makes it difficult to verify values through public means.
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TracerProviderConfigurationTest {
|
||||
|
||||
private static final ConfigProperties EMPTY =
|
||||
ConfigProperties.createForTest(Collections.emptyMap());
|
||||
|
||||
@Mock private SpanExporter exporter;
|
||||
|
||||
@Test
|
||||
void configureTracerProvider() {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put("otel.bsp.schedule.delay.millis", "100000");
|
||||
properties.put("otel.trace.sampler", "always_off");
|
||||
|
||||
Resource resource = Resource.create(Attributes.builder().put("cat", "meow").build());
|
||||
// We don't have any exporters on classpath for this test so check no-op case. Exporter cases
|
||||
// are verified in other test sets like testFullConfig.
|
||||
SdkTracerProvider tracerProvider =
|
||||
TracerProviderConfiguration.configureTracerProvider(
|
||||
resource, Collections.emptySet(), ConfigProperties.createForTest(properties));
|
||||
try {
|
||||
assertThat(tracerProvider.getActiveTraceConfig().getSampler()).isEqualTo(Sampler.alwaysOff());
|
||||
|
||||
assertThat(tracerProvider)
|
||||
.extracting("sharedState")
|
||||
.satisfies(
|
||||
sharedState -> {
|
||||
assertThat(sharedState).extracting("resource").isEqualTo(resource);
|
||||
assertThat(sharedState)
|
||||
.extracting("activeSpanProcessor")
|
||||
.isEqualTo(SpanProcessor.composite());
|
||||
});
|
||||
} finally {
|
||||
tracerProvider.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureSpanProcessor_empty() {
|
||||
BatchSpanProcessor processor =
|
||||
TracerProviderConfiguration.configureSpanProcessor(
|
||||
EMPTY, Collections.singletonList(exporter));
|
||||
|
||||
try {
|
||||
assertThat(processor)
|
||||
.extracting("worker")
|
||||
.satisfies(
|
||||
worker -> {
|
||||
assertThat(worker)
|
||||
.extracting("scheduleDelayNanos")
|
||||
.isEqualTo(TimeUnit.MILLISECONDS.toNanos(5000));
|
||||
assertThat(worker).extracting("exporterTimeoutMillis").isEqualTo(30000);
|
||||
assertThat(worker).extracting("maxExportBatchSize").isEqualTo(512);
|
||||
assertThat(worker)
|
||||
.extracting("queue")
|
||||
.isInstanceOfSatisfying(
|
||||
ArrayBlockingQueue.class,
|
||||
queue -> assertThat(queue.remainingCapacity()).isEqualTo(2048));
|
||||
assertThat(worker).extracting("spanExporter").isEqualTo(exporter);
|
||||
});
|
||||
} finally {
|
||||
processor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureSpanProcessor_configured() {
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put("otel.bsp.schedule.delay.millis", "100000");
|
||||
properties.put("otel.bsp.max.queue.size", "2");
|
||||
properties.put("otel.bsp.max.export.batch.size", "3");
|
||||
properties.put("otel.bsp.export.timeout.millis", "4");
|
||||
|
||||
BatchSpanProcessor processor =
|
||||
TracerProviderConfiguration.configureSpanProcessor(
|
||||
ConfigProperties.createForTest(properties), Collections.singletonList(exporter));
|
||||
|
||||
try {
|
||||
assertThat(processor)
|
||||
.extracting("worker")
|
||||
.satisfies(
|
||||
worker -> {
|
||||
assertThat(worker)
|
||||
.extracting("scheduleDelayNanos")
|
||||
.isEqualTo(TimeUnit.MILLISECONDS.toNanos(100000));
|
||||
assertThat(worker).extracting("exporterTimeoutMillis").isEqualTo(4);
|
||||
assertThat(worker).extracting("maxExportBatchSize").isEqualTo(3);
|
||||
assertThat(worker)
|
||||
.extracting("queue")
|
||||
.isInstanceOfSatisfying(
|
||||
ArrayBlockingQueue.class,
|
||||
queue -> assertThat(queue.remainingCapacity()).isEqualTo(2));
|
||||
assertThat(worker).extracting("spanExporter").isEqualTo(exporter);
|
||||
});
|
||||
} finally {
|
||||
processor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureTraceConfig_empty() {
|
||||
assertThat(TracerProviderConfiguration.configureTraceConfig(EMPTY))
|
||||
.isEqualTo(TraceConfig.getDefault());
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureTraceConfig_full() {
|
||||
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put("otel.trace.sampler", "always_off");
|
||||
properties.put("otel.span.attribute.count.limit", "5");
|
||||
properties.put("otel.span.event.count.limit", "4");
|
||||
properties.put("otel.span.link.count.limit", "3");
|
||||
|
||||
TraceConfig config =
|
||||
TracerProviderConfiguration.configureTraceConfig(
|
||||
ConfigProperties.createForTest(properties));
|
||||
assertThat(config.getSampler()).isEqualTo(Sampler.alwaysOff());
|
||||
assertThat(config.getMaxNumberOfAttributes()).isEqualTo(5);
|
||||
assertThat(config.getMaxNumberOfEvents()).isEqualTo(4);
|
||||
assertThat(config.getMaxNumberOfLinks()).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureSampler() {
|
||||
assertThat(TracerProviderConfiguration.configureSampler("always_on", EMPTY))
|
||||
.isEqualTo(Sampler.alwaysOn());
|
||||
assertThat(TracerProviderConfiguration.configureSampler("always_off", EMPTY))
|
||||
.isEqualTo(Sampler.alwaysOff());
|
||||
assertThat(
|
||||
TracerProviderConfiguration.configureSampler(
|
||||
"traceidratio",
|
||||
ConfigProperties.createForTest(
|
||||
Collections.singletonMap("otel.trace.sampler.arg", "0.5"))))
|
||||
.isEqualTo(Sampler.traceIdRatioBased(0.5));
|
||||
assertThatThrownBy(() -> TracerProviderConfiguration.configureSampler("traceidratio", EMPTY))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessageContaining("otel.trace.sampler.arg is not provided");
|
||||
assertThat(TracerProviderConfiguration.configureSampler("parentbased_always_on", EMPTY))
|
||||
.isEqualTo(Sampler.parentBased(Sampler.alwaysOn()));
|
||||
assertThat(TracerProviderConfiguration.configureSampler("parentbased_always_off", EMPTY))
|
||||
.isEqualTo(Sampler.parentBased(Sampler.alwaysOff()));
|
||||
assertThat(
|
||||
TracerProviderConfiguration.configureSampler(
|
||||
"parentbased_traceidratio",
|
||||
ConfigProperties.createForTest(
|
||||
Collections.singletonMap("otel.trace.sampler.arg", "0.4"))))
|
||||
.isEqualTo(Sampler.parentBased(Sampler.traceIdRatioBased(0.4)));
|
||||
assertThatThrownBy(
|
||||
() -> TracerProviderConfiguration.configureSampler("parentbased_traceidratio", EMPTY))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessageContaining("otel.trace.sampler.arg is not provided");
|
||||
|
||||
assertThatThrownBy(() -> TracerProviderConfiguration.configureSampler("catsampler", EMPTY))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Unrecognized value for otel.trace.sampler: catsampler");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import io.github.netmikey.logunit.api.LogCapturer;
|
||||
import io.opentelemetry.api.GlobalOpenTelemetry;
|
||||
import io.opentelemetry.context.propagation.ContextPropagators;
|
||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.junitpioneer.jupiter.SetSystemProperty;
|
||||
import org.slf4j.event.Level;
|
||||
import org.slf4j.event.LoggingEvent;
|
||||
|
||||
// All tests fail due to config errors so never register a global. We can test everything here
|
||||
// without separating test sets.
|
||||
class ConfigErrorTest {
|
||||
|
||||
@RegisterExtension
|
||||
LogCapturer logs = LogCapturer.create().captureForType(GlobalOpenTelemetry.class);
|
||||
|
||||
@Test
|
||||
@SetSystemProperty(key = "otel.exporter", value = "otlp_metrics,prometheus")
|
||||
void multipleMetricExportersPrometheusThrows() {
|
||||
assertThatThrownBy(OpenTelemetrySdkAutoConfiguration::initialize)
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage(
|
||||
"Multiple metrics exporters configured. "
|
||||
+ "Only one metrics exporter can be configured at a time.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SetSystemProperty(key = "otel.exporter", value = "prometheus,otlp_metrics")
|
||||
void multipleMetricExportersOtlpThrows() {
|
||||
assertThatThrownBy(OpenTelemetrySdkAutoConfiguration::initialize)
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage(
|
||||
"Multiple metrics exporters configured. "
|
||||
+ "Only one metrics exporter can be configured at a time.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SetSystemProperty(key = "otel.propagators", value = "cat")
|
||||
void invalidPropagator() {
|
||||
assertThatThrownBy(OpenTelemetrySdkAutoConfiguration::initialize)
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Unrecognized value for otel.propagators: cat");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SetSystemProperty(key = "otel.trace.sampler", value = "traceidratio")
|
||||
void missingTraceIdRatio() {
|
||||
assertThatThrownBy(OpenTelemetrySdkAutoConfiguration::initialize)
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage(
|
||||
"otel.trace.sampler=traceidratio but otel.trace.sampler.arg is not provided. "
|
||||
+ "Set otel.trace.sampler.arg to a value in the range [0.0, 1.0].");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SetSystemProperty(key = "otel.trace.sampler", value = "traceidratio")
|
||||
@SetSystemProperty(key = "otel.trace.sampler.arg", value = "bar")
|
||||
void invalidTraceIdRatio() {
|
||||
assertThatThrownBy(OpenTelemetrySdkAutoConfiguration::initialize)
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid value for property otel.trace.sampler.arg=bar. Must be a double.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SetSystemProperty(key = "otel.trace.sampler", value = "parentbased_traceidratio")
|
||||
void missingTraceIdRatioWithParent() {
|
||||
assertThatThrownBy(OpenTelemetrySdkAutoConfiguration::initialize)
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage(
|
||||
"otel.trace.sampler=parentbased_traceidratio but otel.trace.sampler.arg is "
|
||||
+ "not provided. Set otel.trace.sampler.arg to a value in the range [0.0, 1.0].");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SetSystemProperty(key = "otel.trace.sampler", value = "parentbased_traceidratio")
|
||||
@SetSystemProperty(key = "otel.trace.sampler.arg", value = "bar")
|
||||
void invalidTraceIdRatioWithParent() {
|
||||
assertThatThrownBy(OpenTelemetrySdkAutoConfiguration::initialize)
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Invalid value for property otel.trace.sampler.arg=bar. Must be a double.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SetSystemProperty(key = "otel.trace.sampler", value = "cat")
|
||||
void invalidSampler() {
|
||||
assertThatThrownBy(OpenTelemetrySdkAutoConfiguration::initialize)
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Unrecognized value for otel.trace.sampler: cat");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SetSystemProperty(key = "otel.exporter", value = "otlp,cat,dog")
|
||||
void invalidExporter() {
|
||||
assertThatThrownBy(OpenTelemetrySdkAutoConfiguration::initialize)
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Unrecognized value for otel.exporter: cat,dog");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SetSystemProperty(key = "otel.exporter", value = "bar")
|
||||
void globalOpenTelemetryWhenError() {
|
||||
assertThat(GlobalOpenTelemetry.get())
|
||||
.isInstanceOf(OpenTelemetrySdk.class)
|
||||
.extracting("propagators")
|
||||
// Failed to initialize so is no-op
|
||||
.isEqualTo(ContextPropagators.noop());
|
||||
|
||||
LoggingEvent log =
|
||||
logs.assertContains(
|
||||
"Error automatically configuring OpenTelemetry SDK. "
|
||||
+ "OpenTelemetry will not be enabled.");
|
||||
assertThat(log.getLevel()).isEqualTo(Level.ERROR);
|
||||
assertThat(log.getThrowable())
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Unrecognized value for otel.exporter: bar");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
import com.linecorp.armeria.common.HttpResponse;
|
||||
import com.linecorp.armeria.common.HttpStatus;
|
||||
import com.linecorp.armeria.common.RequestHeaders;
|
||||
import com.linecorp.armeria.server.ServerBuilder;
|
||||
import com.linecorp.armeria.server.ServiceRequestContext;
|
||||
import com.linecorp.armeria.server.grpc.GrpcService;
|
||||
import com.linecorp.armeria.testing.junit5.server.ServerExtension;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import io.opentelemetry.api.GlobalOpenTelemetry;
|
||||
import io.opentelemetry.api.baggage.Baggage;
|
||||
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.api.trace.SpanContext;
|
||||
import io.opentelemetry.api.trace.SpanId;
|
||||
import io.opentelemetry.api.trace.TraceFlags;
|
||||
import io.opentelemetry.api.trace.TraceId;
|
||||
import io.opentelemetry.api.trace.TraceState;
|
||||
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.exporter.jaeger.proto.api_v2.Collector;
|
||||
import io.opentelemetry.exporter.jaeger.proto.api_v2.CollectorServiceGrpc;
|
||||
import io.opentelemetry.extension.trace.propagation.AwsXrayPropagator;
|
||||
import io.opentelemetry.extension.trace.propagation.B3Propagator;
|
||||
import io.opentelemetry.extension.trace.propagation.JaegerPropagator;
|
||||
import io.opentelemetry.extension.trace.propagation.OtTracerPropagator;
|
||||
import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest;
|
||||
import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse;
|
||||
import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc;
|
||||
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
|
||||
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse;
|
||||
import io.opentelemetry.proto.collector.trace.v1.TraceServiceGrpc;
|
||||
import io.opentelemetry.proto.common.v1.AnyValue;
|
||||
import io.opentelemetry.proto.common.v1.KeyValue;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
@SuppressWarnings("InterruptedExceptionSwallowed")
|
||||
class FullConfigTest {
|
||||
|
||||
private static final BlockingQueue<Collector.PostSpansRequest> jaegerRequests =
|
||||
new LinkedBlockingDeque<>();
|
||||
private static final BlockingQueue<ExportTraceServiceRequest> otlpTraceRequests =
|
||||
new LinkedBlockingDeque<>();
|
||||
private static final BlockingQueue<ExportMetricsServiceRequest> otlpMetricsRequests =
|
||||
new LinkedBlockingDeque<>();
|
||||
private static final BlockingQueue<String> zipkinJsonRequests = new LinkedBlockingDeque<>();
|
||||
|
||||
@RegisterExtension
|
||||
public static final ServerExtension server =
|
||||
new ServerExtension() {
|
||||
@Override
|
||||
protected void configure(ServerBuilder sb) {
|
||||
sb.service(
|
||||
GrpcService.builder()
|
||||
// OTLP spans
|
||||
.addService(
|
||||
new TraceServiceGrpc.TraceServiceImplBase() {
|
||||
@Override
|
||||
public void export(
|
||||
ExportTraceServiceRequest request,
|
||||
StreamObserver<ExportTraceServiceResponse> responseObserver) {
|
||||
try {
|
||||
RequestHeaders headers =
|
||||
ServiceRequestContext.current().request().headers();
|
||||
assertThat(headers.get("cat")).isEqualTo("meow");
|
||||
assertThat(headers.get("dog")).isEqualTo("bark");
|
||||
} catch (Throwable t) {
|
||||
responseObserver.onError(t);
|
||||
return;
|
||||
}
|
||||
otlpTraceRequests.add(request);
|
||||
responseObserver.onNext(ExportTraceServiceResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
})
|
||||
// OTLP metrics
|
||||
.addService(
|
||||
new MetricsServiceGrpc.MetricsServiceImplBase() {
|
||||
@Override
|
||||
public void export(
|
||||
ExportMetricsServiceRequest request,
|
||||
StreamObserver<ExportMetricsServiceResponse> responseObserver) {
|
||||
try {
|
||||
RequestHeaders headers =
|
||||
ServiceRequestContext.current().request().headers();
|
||||
assertThat(headers.get("cat")).isEqualTo("meow");
|
||||
assertThat(headers.get("dog")).isEqualTo("bark");
|
||||
} catch (Throwable t) {
|
||||
responseObserver.onError(t);
|
||||
return;
|
||||
}
|
||||
if (request.getResourceMetricsCount() > 0) {
|
||||
otlpMetricsRequests.add(request);
|
||||
}
|
||||
responseObserver.onNext(
|
||||
ExportMetricsServiceResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
})
|
||||
// Jaeger
|
||||
.addService(
|
||||
new CollectorServiceGrpc.CollectorServiceImplBase() {
|
||||
@Override
|
||||
public void postSpans(
|
||||
Collector.PostSpansRequest request,
|
||||
StreamObserver<Collector.PostSpansResponse> responseObserver) {
|
||||
jaegerRequests.add(request);
|
||||
responseObserver.onNext(Collector.PostSpansResponse.getDefaultInstance());
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
})
|
||||
.useBlockingTaskExecutor(true)
|
||||
.build());
|
||||
|
||||
// Zipkin
|
||||
sb.service(
|
||||
"/api/v2/spans",
|
||||
(ctx, req) ->
|
||||
HttpResponse.from(
|
||||
req.aggregate()
|
||||
.thenApply(
|
||||
aggRes -> {
|
||||
zipkinJsonRequests.add(aggRes.contentUtf8());
|
||||
return HttpResponse.of(HttpStatus.OK);
|
||||
})));
|
||||
}
|
||||
};
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
otlpTraceRequests.clear();
|
||||
otlpMetricsRequests.clear();
|
||||
jaegerRequests.clear();
|
||||
zipkinJsonRequests.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void configures() throws Exception {
|
||||
String endpoint = "localhost:" + server.httpPort();
|
||||
System.setProperty("otel.exporter.otlp.endpoint", endpoint);
|
||||
System.setProperty("otel.exporter.otlp.insecure", "true");
|
||||
System.setProperty("otel.exporter.otlp.timeout", "10000");
|
||||
|
||||
System.setProperty("otel.exporter.jaeger.endpoint", endpoint);
|
||||
|
||||
System.setProperty("otel.exporter.zipkin.endpoint", "http://" + endpoint + "/api/v2/spans");
|
||||
OpenTelemetrySdkAutoConfiguration.initialize();
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
GlobalOpenTelemetry.get()
|
||||
.getPropagators()
|
||||
.getTextMapPropagator()
|
||||
.inject(
|
||||
Context.root()
|
||||
.with(
|
||||
Span.wrap(
|
||||
SpanContext.create(
|
||||
TraceId.fromLongs(1, 1),
|
||||
SpanId.fromLong(2),
|
||||
TraceFlags.getDefault(),
|
||||
TraceState.builder().set("cat", "meow").build())))
|
||||
.with(Baggage.builder().put("airplane", "luggage").build()),
|
||||
headers,
|
||||
Map::put);
|
||||
List<String> keys = new ArrayList<>();
|
||||
keys.addAll(W3CTraceContextPropagator.getInstance().fields());
|
||||
keys.addAll(W3CBaggagePropagator.getInstance().fields());
|
||||
keys.addAll(B3Propagator.getInstance().fields());
|
||||
keys.addAll(JaegerPropagator.getInstance().fields());
|
||||
// Legacy baggage format.
|
||||
keys.add("uberctx-airplane");
|
||||
keys.addAll(OtTracerPropagator.getInstance().fields());
|
||||
keys.addAll(AwsXrayPropagator.getInstance().fields());
|
||||
assertThat(headers).containsOnlyKeys(keys);
|
||||
|
||||
GlobalOpenTelemetry.get()
|
||||
.getTracer("test")
|
||||
.spanBuilder("test")
|
||||
.startSpan()
|
||||
.setAttribute("cat", "meow")
|
||||
.setAttribute("dog", "bark")
|
||||
.end();
|
||||
|
||||
await()
|
||||
.untilAsserted(
|
||||
() -> {
|
||||
assertThat(jaegerRequests).hasSize(1);
|
||||
assertThat(otlpTraceRequests).hasSize(1);
|
||||
assertThat(zipkinJsonRequests).hasSize(1);
|
||||
|
||||
// Not well defined how many metric exports would have happened by now, check that
|
||||
// any
|
||||
// did. The metrics will be BatchSpanProcessor metrics.
|
||||
assertThat(otlpMetricsRequests).isNotEmpty();
|
||||
});
|
||||
|
||||
ExportTraceServiceRequest traceRequest = otlpTraceRequests.take();
|
||||
assertThat(traceRequest.getResourceSpans(0).getResource().getAttributesList())
|
||||
.contains(
|
||||
KeyValue.newBuilder()
|
||||
.setKey("service.name")
|
||||
.setValue(AnyValue.newBuilder().setStringValue("test").build())
|
||||
.build(),
|
||||
KeyValue.newBuilder()
|
||||
.setKey("cat")
|
||||
.setValue(AnyValue.newBuilder().setStringValue("meow").build())
|
||||
.build());
|
||||
io.opentelemetry.proto.trace.v1.Span span =
|
||||
traceRequest.getResourceSpans(0).getInstrumentationLibrarySpans(0).getSpans(0);
|
||||
// Dog dropped by attribute limit.
|
||||
assertThat(span.getAttributesList())
|
||||
.containsExactlyInAnyOrder(
|
||||
KeyValue.newBuilder()
|
||||
.setKey("configured")
|
||||
.setValue(AnyValue.newBuilder().setBoolValue(true).build())
|
||||
.build(),
|
||||
KeyValue.newBuilder()
|
||||
.setKey("cat")
|
||||
.setValue(AnyValue.newBuilder().setStringValue("meow").build())
|
||||
.build());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
|
||||
import io.opentelemetry.sdk.metrics.SdkMeterProvider;
|
||||
import java.util.Collections;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class MetricExporterConfigurationTest {
|
||||
// Timeout difficult to test using real exports so just check implementation detail here.
|
||||
@Test
|
||||
void configureOtlpTimeout() {
|
||||
OtlpGrpcMetricExporter exporter =
|
||||
MetricExporterConfiguration.configureOtlpMetrics(
|
||||
ConfigProperties.createForTest(
|
||||
Collections.singletonMap("otel.exporter.otlp.timeout", "10")),
|
||||
SdkMeterProvider.builder().build());
|
||||
try {
|
||||
assertThat(exporter)
|
||||
.isInstanceOfSatisfying(
|
||||
OtlpGrpcMetricExporter.class,
|
||||
otlp -> assertThat(otlp).extracting("deadlineMs").isEqualTo(10L));
|
||||
} finally {
|
||||
exporter.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
|
||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||
import java.util.Collections;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class SpanExporterConfigurationTest {
|
||||
|
||||
private static final ConfigProperties EMPTY =
|
||||
ConfigProperties.createForTest(Collections.emptyMap());
|
||||
|
||||
// Timeout difficult to test using real exports so just check implementation detail here.
|
||||
@Test
|
||||
void configureOtlpTimeout() {
|
||||
SpanExporter exporter =
|
||||
SpanExporterConfiguration.configureExporter(
|
||||
"otlp",
|
||||
ConfigProperties.createForTest(
|
||||
Collections.singletonMap("otel.exporter.otlp.timeout", "10")));
|
||||
try {
|
||||
assertThat(exporter)
|
||||
.isInstanceOfSatisfying(
|
||||
OtlpGrpcSpanExporter.class,
|
||||
otlp -> assertThat(otlp).extracting("deadlineMs").isEqualTo(10L));
|
||||
} finally {
|
||||
exporter.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void configureExporterOtlpSpan() {
|
||||
SpanExporter exporter = SpanExporterConfiguration.configureExporter("otlp_span", EMPTY);
|
||||
try {
|
||||
assertThat(exporter).isInstanceOf(OtlpGrpcSpanExporter.class);
|
||||
} finally {
|
||||
exporter.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.SdkTracerProviderConfigurer;
|
||||
import io.opentelemetry.sdk.trace.ReadWriteSpan;
|
||||
import io.opentelemetry.sdk.trace.ReadableSpan;
|
||||
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
|
||||
import io.opentelemetry.sdk.trace.SpanProcessor;
|
||||
|
||||
public class TestTracerProviderConfigurer implements SdkTracerProviderConfigurer {
|
||||
@Override
|
||||
public void configure(SdkTracerProviderBuilder tracerProvider) {
|
||||
tracerProvider.addSpanProcessor(
|
||||
new SpanProcessor() {
|
||||
@Override
|
||||
public void onStart(Context parentContext, ReadWriteSpan span) {
|
||||
span.setAttribute("configured", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStartRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd(ReadableSpan span) {}
|
||||
|
||||
@Override
|
||||
public boolean isEndRequired() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
io.opentelemetry.sdk.autoconfigure.TestTracerProviderConfigurer
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.linecorp.armeria.client.WebClient;
|
||||
import com.linecorp.armeria.common.AggregatedHttpResponse;
|
||||
import io.opentelemetry.api.common.Labels;
|
||||
import io.opentelemetry.api.metrics.GlobalMetricsProvider;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class PrometheusTest {
|
||||
|
||||
@Test
|
||||
void prometheusExporter() throws Exception {
|
||||
int port = 9464;
|
||||
// Just use prometheus standard port if it's available
|
||||
try (ServerSocket unused = new ServerSocket(port)) {
|
||||
// Port available
|
||||
} catch (IOException e) {
|
||||
// Otherwise use a random port. There's a small race if another process takes it before we
|
||||
// initialize. Consider adding retries to this test if it flakes, presumably it never will on
|
||||
// CI since there's no prometheus there blocking the well-known port.
|
||||
try (ServerSocket socket2 = new ServerSocket(0)) {
|
||||
port = socket2.getLocalPort();
|
||||
}
|
||||
}
|
||||
System.setProperty("otel.exporter.prometheus.host", "127.0.0.1");
|
||||
System.setProperty("otel.exporter.prometheus.port", String.valueOf(port));
|
||||
OpenTelemetrySdkAutoConfiguration.initialize();
|
||||
|
||||
GlobalMetricsProvider.get()
|
||||
.get("test")
|
||||
.longValueObserverBuilder("test")
|
||||
.setUpdater(result -> result.observe(2, Labels.empty()))
|
||||
.build();
|
||||
|
||||
WebClient client = WebClient.of("http://127.0.0.1:" + port);
|
||||
AggregatedHttpResponse response = client.get("/metrics").aggregate().join();
|
||||
assertThat(response.contentUtf8()).contains("test 2.0");
|
||||
}
|
||||
}
|
|
@ -56,6 +56,7 @@ include ":all",
|
|||
":sdk:testing",
|
||||
":sdk:trace",
|
||||
":sdk-extensions:async-processor",
|
||||
":sdk-extensions:autoconfigure",
|
||||
":sdk-extensions:aws",
|
||||
":sdk-extensions:logging",
|
||||
":sdk-extensions:otproto",
|
||||
|
|
Loading…
Reference in New Issue