add support for OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_HEADERS and OTEL_EXPORTER_OTLP_PROTOCOL for spring boot starter (#9950)

This commit is contained in:
Gregor Zeitlinger 2023-12-21 17:23:48 +01:00 committed by GitHub
parent 67facf3ef7
commit 87615cc9d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 771 additions and 192 deletions

View File

@ -390,51 +390,105 @@ If an exporter is present in the classpath during runtime and a spring bean of t
#### Configuration Properties
##### Enabling/Disabling Features
##### Enabling/Disabling Exporters
| Feature | Property | Default Value | ConditionalOnClass |
|------------------|---------------------------------------------|---------------|------------------------|
| spring-web | otel.instrumentation.spring-webmvc.enabled | `true` | RestTemplate |
| spring-webmvc | otel.instrumentation.spring-web.enabled | `true` | OncePerRequestFilter |
| spring-webflux | otel.instrumentation.spring-webflux.enabled | `true` | WebClient |
| @WithSpan | otel.instrumentation.annotations.enabled | `true` | WithSpan, Aspect |
| Otlp Exporter | otel.exporter.otlp.enabled | `true` | OtlpGrpcSpanExporter |
| Jaeger Exporter | otel.exporter.jaeger.enabled | `true` | JaegerGrpcSpanExporter |
| Zipkin Exporter | otel.exporter.zipkin.enabled | `true` | ZipkinSpanExporter |
| Logging Exporter | otel.exporter.logging.enabled | `true` | LoggingSpanExporter |
All exporters can be enabled or disabled as in the
[SDK auto-configuration](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#exporters).
This is the preferred way to enable/disable exporters and takes precedence over the properties below.
| Feature | Property | Default Value | ConditionalOnClass |
|-----------------------|---------------------------------------------|---------------|---------------------------|
| Otlp Exporter | otel.exporter.otlp.enabled | `true` | - |
| Otlp Span Exporter | otel.exporter.otlp.traces.enabled | `true` | OtlpGrpcSpanExporter |
| Otlp Metrics Exporter | otel.exporter.otlp.metrics.enabled | `true` | OtlpGrpcMetricExporter |
| Otlp Logs Exporter | otel.exporter.otlp.logs.enabled | `true` | OtlpGrpcLogRecordExporter |
| Jaeger Exporter | otel.exporter.jaeger.enabled | `true` | JaegerGrpcSpanExporter |
| Zipkin Exporter | otel.exporter.zipkin.enabled | `true` | ZipkinSpanExporter |
| Logging Exporter | otel.exporter.logging.enabled | `false` | LoggingSpanExporter |
<!-- Slf4j Log Correlation otel.springboot.loggers.slf4j.enabled true org.slf4j.MDC -->
##### Enabling/Disabling Features
| Feature | Property | Default Value | ConditionalOnClass |
|-----------------------|---------------------------------------------|---------------|---------------------------|
| spring-web | otel.instrumentation.spring-webmvc.enabled | `true` | RestTemplate |
| spring-webmvc | otel.instrumentation.spring-web.enabled | `true` | OncePerRequestFilter |
| spring-webflux | otel.instrumentation.spring-webflux.enabled | `true` | WebClient |
| @WithSpan | otel.instrumentation.annotations.enabled | `true` | WithSpan, Aspect |
##### Resource Properties
| Feature | Property | Default Value |
| -------- | ------------------------------------------------ | ---------------------- |
| Resource | otel.springboot.resource.enabled | `true` |
| | otel.springboot.resource.attributes.service.name | `unknown_service:java` |
| | otel.springboot.resource.attributes | `empty map` |
| Feature | Property | Default Value |
| -------- |---------------------------------------------------------------------| ---------------------- |
| Resource | otel.springboot.resource.enabled | `true` |
| | otel.resource.attributes (old: otel.springboot.resource.attributes) | `empty map` |
`unknown_service:java` will be used as the service-name if no value has been specified to the
property `spring.application.name` or `otel.springboot.resource.attributes.service.name` (which has
the highest priority)
`otel.springboot.resource.attributes` supports a pattern-based resource configuration in the
`otel.resource.attributes` supports a pattern-based resource configuration in the
application.properties like this:
```
otel.springboot.resource.attributes.environment=dev
otel.springboot.resource.attributes.xyz=foo
otel.resource.attributes.environment=dev
otel.resource.attributes.xyz=foo
```
It's also possible to specify the resource attributes in `application.yaml`:
```yaml
otel:
resource:
attributes:
environment: dev
xyz: foo
```
Finally, the resource attributes can be specified as a comma-separated list, as described in the
[specification](https://opentelemetry.io/docs/concepts/sdk-configuration/general-sdk-configuration/#otel_resource_attributes):
```shell
export OTEL_RESOURCE_ATTRIBUTES="key1=value1,key2=value2"
```
The service name is determined by the following precedence, in accordance with the OpenTelemetry
[specification](https://opentelemetry.io/docs/concepts/sdk-configuration/general-sdk-configuration/#otel_service_name):
1. `otel.service.name` spring property or `OTEL_SERVICE_NAME` environment variable (highest
precedence)
2. `service.name` in `otel.resource.attributes` system/spring property or `OTEL_RESOURCE_ATTRIBUTES`
environment variable
3. `service.name` in `otel.springboot.resource.attributes` system/spring property
4. `spring.application.name` spring property
5. the default value `unknown_service:java` (lowest precedence)
##### Exporter Properties
| Feature | Property | Default Value |
| --------------- | ----------------------------- | ------------------------------------ |
|-----------------|-------------------------------|--------------------------------------|
| Otlp Exporter | otel.exporter.otlp.endpoint | `localhost:4317` |
| | otel.exporter.otlp.protocol | `grpc` |
| | otel.exporter.otlp.headers | |
| | otel.exporter.otlp.timeout | `1s` |
| Jaeger Exporter | otel.exporter.jaeger.endpoint | `localhost:14250` |
| | otel.exporter.jaeger.timeout | `1s` |
| Zipkin Exporter | otel.exporter.jaeger.endpoint | `http://localhost:9411/api/v2/spans` |
The `otel.exporter.otlp.headers` property can be specified as a comma-separated list,
which is compliant with the
[specification](https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_headers).
Similar to the resource attributes, the headers can be specified in `application.properties` or
`application.yaml`:
```yaml
otel:
exporter:
otlp:
headers:
- key: "header1"
value: "value1"
- key: "header2"
value: "value2"
```
##### Tracer Properties
| Feature | Property | Default Value |

View File

@ -90,6 +90,7 @@ testing {
implementation(project(":testing-common"))
implementation("io.opentelemetry:opentelemetry-sdk")
implementation("io.opentelemetry:opentelemetry-sdk-testing")
implementation("org.mockito:mockito-inline")
implementation("org.springframework.boot:spring-boot-autoconfigure:$springBootVersion")
implementation(project(":instrumentation:logback:logback-appender-1.0:library"))

View File

@ -8,6 +8,11 @@ package io.opentelemetry.instrumentation.spring.autoconfigure;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp.OtlpLoggerExporterAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp.OtlpMetricExporterAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp.OtlpSpanExporterAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.MapConverter;
import io.opentelemetry.instrumentation.spring.autoconfigure.resources.OtelResourceAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.resources.SpringResourceConfigProperties;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
@ -30,8 +35,10 @@ import io.opentelemetry.sdk.trace.samplers.Sampler;
import java.util.Collections;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -56,6 +63,19 @@ public class OpenTelemetryAutoConfiguration {
@ConditionalOnProperty(name = "otel.sdk.disabled", havingValue = "false", matchIfMissing = true)
public static class OpenTelemetrySdkConfig {
@Bean
@ConfigurationPropertiesBinding
@ConditionalOnBean({
OtelResourceAutoConfiguration.class,
OtlpLoggerExporterAutoConfiguration.class,
OtlpSpanExporterAutoConfiguration.class,
OtlpMetricExporterAutoConfiguration.class
})
public MapConverter mapConverter() {
// needed for otlp exporter headers and OtelResourceProperties
return new MapConverter();
}
@Bean
@ConditionalOnMissingBean
public SdkTracerProvider sdkTracerProvider(

View File

@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.internal;
import java.util.Arrays;
import javax.annotation.Nullable;
import org.springframework.core.env.Environment;
/**
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
* any time.
*/
public final class ExporterConfigEvaluator {
private ExporterConfigEvaluator() {}
public static boolean isExporterEnabled(
Environment environment,
@Nullable String oldAllKey,
String oldKey,
String exportersKey,
String wantExporter,
boolean defaultValue) {
String exporter = environment.getProperty(exportersKey);
if (exporter != null) {
return Arrays.asList(exporter.split(",")).contains(wantExporter);
}
String old = environment.getProperty(oldKey);
if (old != null) {
return "true".equals(old);
}
if (oldAllKey != null) {
String oldAll = environment.getProperty(oldAllKey);
if (oldAll != null) {
return "true".equals(oldAll);
}
}
return defaultValue;
}
}

View File

@ -6,12 +6,14 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.jaeger;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.internal.ExporterConfigEvaluator;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
@ -23,7 +25,7 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@EnableConfigurationProperties(JaegerSpanExporterProperties.class)
@ConditionalOnProperty(prefix = "otel.exporter.jaeger", name = "enabled", matchIfMissing = true)
@Conditional(JaegerSpanExporterAutoConfiguration.CustomCondition.class)
@ConditionalOnClass(io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter.class)
@Deprecated
public class JaegerSpanExporterAutoConfiguration {
@ -43,4 +45,19 @@ public class JaegerSpanExporterAutoConfiguration {
}
return builder.build();
}
static final class CustomCondition implements Condition {
@Override
public boolean matches(
org.springframework.context.annotation.ConditionContext context,
org.springframework.core.type.AnnotatedTypeMetadata metadata) {
return ExporterConfigEvaluator.isExporterEnabled(
context.getEnvironment(),
null,
"otel.exporter.jaeger.enabled",
"otel.traces.exporter",
"jaeger",
true);
}
}
}

View File

@ -8,12 +8,12 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.logging;
import io.opentelemetry.exporter.logging.LoggingMetricExporter;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.internal.ExporterConfigEvaluator;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@ -21,7 +21,7 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(LoggingExporterProperties.class)
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@Conditional(LoggingMetricExporterAutoConfiguration.AnyPropertyEnabled.class)
@Conditional(LoggingMetricExporterAutoConfiguration.CustomCondition.class)
@ConditionalOnClass(LoggingMetricExporter.class)
public class LoggingMetricExporterAutoConfiguration {
@ -30,16 +30,18 @@ public class LoggingMetricExporterAutoConfiguration {
return LoggingMetricExporter.create();
}
static final class AnyPropertyEnabled extends AnyNestedCondition {
AnyPropertyEnabled() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
static final class CustomCondition implements Condition {
@Override
public boolean matches(
org.springframework.context.annotation.ConditionContext context,
org.springframework.core.type.AnnotatedTypeMetadata metadata) {
return ExporterConfigEvaluator.isExporterEnabled(
context.getEnvironment(),
"otel.exporter.logging.enabled",
"otel.exporter.logging.metrics.enabled",
"otel.metrics.exporter",
"logging",
false);
}
@ConditionalOnProperty("otel.exporter.logging.enabled")
static class LoggingEnabled {}
@ConditionalOnProperty("otel.exporter.logging.metrics.enabled")
static class LoggingMetricsEnabled {}
}
}

View File

@ -7,13 +7,13 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.logging;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.internal.ExporterConfigEvaluator;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@ -21,7 +21,7 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(LoggingExporterProperties.class)
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@Conditional(LoggingSpanExporterAutoConfiguration.AnyPropertyEnabled.class)
@Conditional(LoggingSpanExporterAutoConfiguration.CustomCondition.class)
@ConditionalOnClass(LoggingSpanExporter.class)
public class LoggingSpanExporterAutoConfiguration {
@ -31,16 +31,18 @@ public class LoggingSpanExporterAutoConfiguration {
return LoggingSpanExporter.create();
}
static final class AnyPropertyEnabled extends AnyNestedCondition {
AnyPropertyEnabled() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
static final class CustomCondition implements Condition {
@Override
public boolean matches(
org.springframework.context.annotation.ConditionContext context,
org.springframework.core.type.AnnotatedTypeMetadata metadata) {
return ExporterConfigEvaluator.isExporterEnabled(
context.getEnvironment(),
"otel.exporter.logging.enabled",
"otel.exporter.logging.traces.enabled",
"otel.traces.exporter",
"logging",
false);
}
@ConditionalOnProperty("otel.exporter.logging.enabled")
static class LoggingEnabled {}
@ConditionalOnProperty("otel.exporter.logging.traces.enabled")
static class LoggingTracesEnabled {}
}
}

View File

@ -6,6 +6,8 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -24,6 +26,11 @@ public final class OtlpExporterProperties {
private boolean enabled = true;
@Nullable private String endpoint;
@Nullable private String protocol;
private final Map<String, String> headers = new HashMap<>();
@Nullable private Duration timeout;
private final SignalProperties traces = new SignalProperties();
private final SignalProperties metrics = new SignalProperties();
@ -46,6 +53,19 @@ public final class OtlpExporterProperties {
this.endpoint = endpoint;
}
@Nullable
public String getProtocol() {
return protocol;
}
public void setProtocol(@Nullable String protocol) {
this.protocol = protocol;
}
public Map<String, String> getHeaders() {
return headers;
}
@Nullable
public Duration getTimeout() {
return timeout;
@ -71,6 +91,11 @@ public final class OtlpExporterProperties {
private boolean enabled = true;
@Nullable private String endpoint;
@Nullable private String protocol;
private final Map<String, String> headers = new HashMap<>();
@Nullable private Duration timeout;
public boolean isEnabled() {
@ -90,6 +115,19 @@ public final class OtlpExporterProperties {
this.endpoint = endpoint;
}
@Nullable
public String getProtocol() {
return protocol;
}
public void setProtocol(@Nullable String protocol) {
this.protocol = protocol;
}
public Map<String, String> getHeaders() {
return headers;
}
@Nullable
public Duration getTimeout() {
return timeout;

View File

@ -0,0 +1,105 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp;
import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
class OtlpExporterUtil {
private OtlpExporterUtil() {}
static <G, H, E> E applySignalProperties(
String dataType,
OtlpExporterProperties properties,
OtlpExporterProperties.SignalProperties signalProperties,
Supplier<G> newGrpcBuilder,
Supplier<H> newHttpBuilder,
BiConsumer<G, String> setGrpcEndpoint,
BiConsumer<H, String> setHttpEndpoint,
BiConsumer<G, Map.Entry<String, String>> addGrpcHeader,
BiConsumer<H, Map.Entry<String, String>> addHttpHeader,
BiConsumer<G, Duration> setGrpcTimeout,
BiConsumer<H, Duration> setHttpTimeout,
Function<G, E> buildGrpcExporter,
Function<H, E> buildHttpExporter) {
String protocol = signalProperties.getProtocol();
if (protocol == null) {
protocol = properties.getProtocol();
}
G grpcBuilder = newGrpcBuilder.get();
H httpBuilder = newHttpBuilder.get();
boolean isHttpProtobuf = Objects.equals(protocol, OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF);
String endpoint = signalProperties.getEndpoint();
if (endpoint == null) {
endpoint = properties.getEndpoint();
}
if (endpoint != null) {
if (isHttpProtobuf) {
if (!endpoint.endsWith("/")) {
endpoint += "/";
}
endpoint += signalPath(dataType);
}
if (isHttpProtobuf) {
setHttpEndpoint.accept(httpBuilder, endpoint);
} else {
setGrpcEndpoint.accept(grpcBuilder, endpoint);
}
}
Map<String, String> headers = signalProperties.getHeaders();
if (headers.isEmpty()) {
headers = properties.getHeaders();
}
for (Map.Entry<String, String> entry : headers.entrySet()) {
if (isHttpProtobuf) {
addHttpHeader.accept(httpBuilder, entry);
} else {
addGrpcHeader.accept(grpcBuilder, entry);
}
}
Duration timeout = signalProperties.getTimeout();
if (timeout == null) {
timeout = properties.getTimeout();
}
if (timeout != null) {
if (isHttpProtobuf) {
setHttpTimeout.accept(httpBuilder, timeout);
} else {
setGrpcTimeout.accept(grpcBuilder, timeout);
}
}
return isHttpProtobuf
? buildHttpExporter.apply(httpBuilder)
: buildGrpcExporter.apply(grpcBuilder);
}
private static String signalPath(String dataType) {
switch (dataType) {
case OtlpConfigUtil.DATA_TYPE_METRICS:
return "v1/metrics";
case OtlpConfigUtil.DATA_TYPE_TRACES:
return "v1/traces";
case OtlpConfigUtil.DATA_TYPE_LOGS:
return "v1/logs";
default:
throw new IllegalArgumentException(
"Cannot determine signal path for unrecognized data type: " + dataType);
}
}
}

View File

@ -5,48 +5,64 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder;
import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import java.time.Duration;
import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.internal.ExporterConfigEvaluator;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.Conditional;
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@EnableConfigurationProperties(OtlpExporterProperties.class)
@ConditionalOnProperty(
prefix = "otel.exporter.otlp",
name = {"enabled", "logs.enabled"},
matchIfMissing = true)
@Conditional(OtlpLoggerExporterAutoConfiguration.CustomCondition.class)
@ConditionalOnClass(OtlpGrpcLogRecordExporter.class)
public class OtlpLoggerExporterAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OtlpGrpcLogRecordExporter otelOtlpGrpcLogRecordExporter(
OtlpExporterProperties properties) {
OtlpGrpcLogRecordExporterBuilder builder = OtlpGrpcLogRecordExporter.builder();
@ConditionalOnMissingBean({OtlpGrpcLogRecordExporter.class, OtlpHttpLogRecordExporter.class})
public LogRecordExporter otelOtlpLogRecordExporter(OtlpExporterProperties properties) {
String endpoint = properties.getLogs().getEndpoint();
if (endpoint == null) {
endpoint = properties.getEndpoint();
}
if (endpoint != null) {
builder.setEndpoint(endpoint);
}
return OtlpExporterUtil.applySignalProperties(
OtlpConfigUtil.DATA_TYPE_LOGS,
properties,
properties.getLogs(),
OtlpGrpcLogRecordExporter::builder,
OtlpHttpLogRecordExporter::builder,
OtlpGrpcLogRecordExporterBuilder::setEndpoint,
OtlpHttpLogRecordExporterBuilder::setEndpoint,
(builder, entry) -> {
builder.addHeader(entry.getKey(), entry.getValue());
},
(builder, entry) -> {
builder.addHeader(entry.getKey(), entry.getValue());
},
OtlpGrpcLogRecordExporterBuilder::setTimeout,
OtlpHttpLogRecordExporterBuilder::setTimeout,
OtlpGrpcLogRecordExporterBuilder::build,
OtlpHttpLogRecordExporterBuilder::build);
}
Duration timeout = properties.getLogs().getTimeout();
if (timeout == null) {
timeout = properties.getTimeout();
static final class CustomCondition implements Condition {
@Override
public boolean matches(
org.springframework.context.annotation.ConditionContext context,
org.springframework.core.type.AnnotatedTypeMetadata metadata) {
return ExporterConfigEvaluator.isExporterEnabled(
context.getEnvironment(),
"otel.exporter.otlp.enabled",
"otel.exporter.otlp.logs.enabled",
"otel.logs.exporter",
"otlp",
true);
}
if (timeout != null) {
builder.setTimeout(timeout);
}
return builder.build();
}
}

View File

@ -5,49 +5,65 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp;
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder;
import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import java.time.Duration;
import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.internal.ExporterConfigEvaluator;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@EnableConfigurationProperties(OtlpExporterProperties.class)
@ConditionalOnProperty(
prefix = "otel.exporter.otlp",
name = {"enabled", "metrics.enabled"},
matchIfMissing = true)
@Conditional(OtlpMetricExporterAutoConfiguration.CustomCondition.class)
@ConditionalOnClass(OtlpGrpcMetricExporter.class)
public class OtlpMetricExporterAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OtlpGrpcMetricExporter otelOtlpGrpcMetricExporter(OtlpExporterProperties properties) {
OtlpGrpcMetricExporterBuilder builder = OtlpGrpcMetricExporter.builder();
@ConditionalOnMissingBean({OtlpGrpcMetricExporter.class, OtlpHttpMetricExporter.class})
public MetricExporter otelOtlpMetricExporter(OtlpExporterProperties properties) {
return OtlpExporterUtil.applySignalProperties(
OtlpConfigUtil.DATA_TYPE_METRICS,
properties,
properties.getLogs(),
OtlpGrpcMetricExporter::builder,
OtlpHttpMetricExporter::builder,
OtlpGrpcMetricExporterBuilder::setEndpoint,
OtlpHttpMetricExporterBuilder::setEndpoint,
(builder, entry) -> {
builder.addHeader(entry.getKey(), entry.getValue());
},
(builder, entry) -> {
builder.addHeader(entry.getKey(), entry.getValue());
},
OtlpGrpcMetricExporterBuilder::setTimeout,
OtlpHttpMetricExporterBuilder::setTimeout,
OtlpGrpcMetricExporterBuilder::build,
OtlpHttpMetricExporterBuilder::build);
}
String endpoint = properties.getMetrics().getEndpoint();
if (endpoint == null) {
endpoint = properties.getEndpoint();
static final class CustomCondition implements Condition {
@Override
public boolean matches(
org.springframework.context.annotation.ConditionContext context,
org.springframework.core.type.AnnotatedTypeMetadata metadata) {
return ExporterConfigEvaluator.isExporterEnabled(
context.getEnvironment(),
"otel.exporter.otlp.enabled",
"otel.exporter.otlp.metrics.enabled",
"otel.metrics.exporter",
"otlp",
true);
}
if (endpoint != null) {
builder.setEndpoint(endpoint);
}
Duration timeout = properties.getMetrics().getTimeout();
if (timeout == null) {
timeout = properties.getTimeout();
}
if (timeout != null) {
builder.setTimeout(timeout);
}
return builder.build();
}
}

View File

@ -5,16 +5,21 @@
package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder;
import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import java.time.Duration;
import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.internal.ExporterConfigEvaluator;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
@ -25,34 +30,53 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@EnableConfigurationProperties(OtlpExporterProperties.class)
@ConditionalOnProperty(
prefix = "otel.exporter.otlp",
name = {"enabled", "traces.enabled"},
matchIfMissing = true)
@Conditional(OtlpSpanExporterAutoConfiguration.CustomCondition.class)
@ConditionalOnClass(OtlpGrpcSpanExporter.class)
public class OtlpSpanExporterAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OtlpGrpcSpanExporter otelOtlpGrpcSpanExporter(OtlpExporterProperties properties) {
OtlpGrpcSpanExporterBuilder builder = OtlpGrpcSpanExporter.builder();
@ConditionalOnMissingBean({OtlpHttpSpanExporterBuilder.class})
public OtlpHttpSpanExporterBuilder otelOtlpHttpSpanExporterBuilder() {
// used for testing only - the builder is final
return OtlpHttpSpanExporter.builder();
}
String endpoint = properties.getTraces().getEndpoint();
if (endpoint == null) {
endpoint = properties.getEndpoint();
}
if (endpoint != null) {
builder.setEndpoint(endpoint);
}
@Bean
@ConditionalOnMissingBean({OtlpGrpcSpanExporter.class, OtlpHttpSpanExporter.class})
public SpanExporter otelOtlpSpanExporter(
OtlpExporterProperties properties, OtlpHttpSpanExporterBuilder otlpHttpSpanExporterBuilder) {
return OtlpExporterUtil.applySignalProperties(
OtlpConfigUtil.DATA_TYPE_TRACES,
properties,
properties.getLogs(),
OtlpGrpcSpanExporter::builder,
() -> otlpHttpSpanExporterBuilder,
OtlpGrpcSpanExporterBuilder::setEndpoint,
OtlpHttpSpanExporterBuilder::setEndpoint,
(builder, entry) -> {
builder.addHeader(entry.getKey(), entry.getValue());
},
(builder, entry) -> {
builder.addHeader(entry.getKey(), entry.getValue());
},
OtlpGrpcSpanExporterBuilder::setTimeout,
OtlpHttpSpanExporterBuilder::setTimeout,
OtlpGrpcSpanExporterBuilder::build,
OtlpHttpSpanExporterBuilder::build);
}
Duration timeout = properties.getTraces().getTimeout();
if (timeout == null) {
timeout = properties.getTimeout();
static final class CustomCondition implements Condition {
@Override
public boolean matches(
org.springframework.context.annotation.ConditionContext context,
org.springframework.core.type.AnnotatedTypeMetadata metadata) {
return ExporterConfigEvaluator.isExporterEnabled(
context.getEnvironment(),
"otel.exporter.otlp.enabled",
"otel.exporter.otlp.traces.enabled",
"otel.traces.exporter",
"otlp",
true);
}
if (timeout != null) {
builder.setTimeout(timeout);
}
return builder.build();
}
}

View File

@ -8,12 +8,14 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.zipkin;
import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
import io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.exporters.internal.ExporterConfigEvaluator;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
@ -24,7 +26,7 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@EnableConfigurationProperties(ZipkinSpanExporterProperties.class)
@ConditionalOnProperty(prefix = "otel.exporter.zipkin", name = "enabled", matchIfMissing = true)
@Conditional(ZipkinSpanExporterAutoConfiguration.CustomCondition.class)
@ConditionalOnClass(ZipkinSpanExporter.class)
public class ZipkinSpanExporterAutoConfiguration {
@ -38,4 +40,19 @@ public class ZipkinSpanExporterAutoConfiguration {
}
return builder.build();
}
static final class CustomCondition implements Condition {
@Override
public boolean matches(
org.springframework.context.annotation.ConditionContext context,
org.springframework.core.type.AnnotatedTypeMetadata metadata) {
return ExporterConfigEvaluator.isExporterEnabled(
context.getEnvironment(),
null,
"otel.exporter.zipkin.enabled",
"otel.traces.exporter",
"zipkin",
true);
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.internal;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import java.util.Collections;
import java.util.Map;
import org.springframework.core.convert.converter.Converter;
/**
* The MapConverter class is used to convert a String to a Map. The String is expected to be in the
* format of a comma separated list of key=value pairs, e.g. key1=value1,key2=value2.
*
* <p>This is the expected format for the <code>OTEL_RESOURCE_ATTRIBUTES</code> and <code>
* OTEL_EXPORTER_OTLP_HEADERS</code> environment variables.
*
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
* at any time.
*/
public class MapConverter implements Converter<String, Map<String, String>> {
public static final String KEY = "key";
@Override
public Map<String, String> convert(String source) {
DefaultConfigProperties properties =
DefaultConfigProperties.createFromMap(Collections.singletonMap(KEY, source));
return properties.getMap(KEY);
}
}

View File

@ -25,14 +25,16 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(OtelResourceProperties.class)
@EnableConfigurationProperties({OtelSpringResourceProperties.class, OtelResourceProperties.class})
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@ConditionalOnProperty(prefix = "otel.springboot.resource", name = "enabled", matchIfMissing = true)
public class OtelResourceAutoConfiguration {
@Bean
public ResourceProvider otelResourceProvider(OtelResourceProperties otelResourceProperties) {
return new SpringResourceProvider(otelResourceProperties);
public ResourceProvider otelResourceProvider(
OtelSpringResourceProperties otelSpringResourceProperties,
OtelResourceProperties otelResourceProperties) {
return new SpringResourceProvider(otelSpringResourceProperties, otelResourceProperties);
}
@Bean

View File

@ -9,7 +9,7 @@ import java.util.Collections;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "otel.springboot.resource")
@ConfigurationProperties(prefix = "otel.resource")
public class OtelResourceProperties {
private Map<String, String> attributes = Collections.emptyMap();

View File

@ -0,0 +1,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure.resources;
import java.util.Collections;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "otel.springboot.resource")
public class OtelSpringResourceProperties {
private Map<String, String> attributes = Collections.emptyMap();
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
}

View File

@ -11,30 +11,32 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.ResourceAttributes;
import java.util.Map;
public class SpringResourceProvider implements ResourceProvider {
private final OtelSpringResourceProperties otelSpringResourceProperties;
private final OtelResourceProperties otelResourceProperties;
public SpringResourceProvider(OtelResourceProperties otelResourceProperties) {
public SpringResourceProvider(
OtelSpringResourceProperties otelSpringResourceProperties,
OtelResourceProperties otelResourceProperties) {
this.otelSpringResourceProperties = otelSpringResourceProperties;
this.otelResourceProperties = otelResourceProperties;
}
@Override
public Resource createResource(ConfigProperties configProperties) {
String applicationName = configProperties.getString("spring.application.name");
Map<String, String> attributes = otelResourceProperties.getAttributes();
AttributesBuilder attributesBuilder = Attributes.builder();
attributes.forEach(attributesBuilder::put);
return defaultResource(applicationName).merge(Resource.create(attributesBuilder.build()));
}
private static Resource defaultResource(String applicationName) {
if (applicationName == null) {
return Resource.getDefault();
String springApplicationName = configProperties.getString("spring.application.name");
if (springApplicationName != null) {
attributesBuilder.put(ResourceAttributes.SERVICE_NAME, springApplicationName);
}
return Resource.getDefault()
.merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName)));
otelSpringResourceProperties.getAttributes().forEach(attributesBuilder::put);
otelResourceProperties.getAttributes().forEach(attributesBuilder::put);
String applicationName = configProperties.getString("otel.service.name");
if (applicationName != null) {
attributesBuilder.put(ResourceAttributes.SERVICE_NAME, applicationName);
}
return Resource.create(attributesBuilder.build());
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.spring.autoconfigure;
import io.opentelemetry.instrumentation.spring.autoconfigure.internal.MapConverter;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Unconditionally create {@link MapConverter} bean, because the tests don't evaluate the
* ConditionalOnBean annotation correctly.
*/
@Configuration
public class MapConverterTestAutoConfiguration {
@Bean
@ConfigurationPropertiesBinding
public MapConverter mapConverter() {
return new MapConverter();
}
}

View File

@ -28,7 +28,7 @@ class MetricExporterAutoConfigurationTest {
void defaultConfiguration() {
contextRunner.run(
context -> {
assertThat(context.getBean("otelOtlpGrpcMetricExporter", OtlpGrpcMetricExporter.class))
assertThat(context.getBean("otelOtlpMetricExporter", OtlpGrpcMetricExporter.class))
.as("OTLP exporter is enabled by default")
.isNotNull();
assertThat(context.containsBean("otelLoggingMetricExporter"))
@ -43,8 +43,7 @@ class MetricExporterAutoConfigurationTest {
.withPropertyValues("otel.exporter.logging.enabled=true")
.run(
context -> {
assertThat(
context.getBean("otelOtlpGrpcMetricExporter", OtlpGrpcMetricExporter.class))
assertThat(context.getBean("otelOtlpMetricExporter", OtlpGrpcMetricExporter.class))
.as("OTLP exporter is present even with logging enabled")
.isNotNull();
assertThat(context.getBean("otelLoggingMetricExporter", LoggingMetricExporter.class))

View File

@ -28,7 +28,7 @@ class SpanExporterAutoConfigurationTest {
void defaultConfiguration() {
contextRunner.run(
context -> {
assertThat(context.getBean("otelOtlpGrpcSpanExporter", OtlpGrpcSpanExporter.class))
assertThat(context.getBean("otelOtlpSpanExporter", OtlpGrpcSpanExporter.class))
.as("OTLP exporter is enabled by default")
.isNotNull();
assertThat(context.containsBean("otelLoggingSpanExporter"))
@ -43,7 +43,7 @@ class SpanExporterAutoConfigurationTest {
.withPropertyValues("otel.exporter.logging.enabled=true")
.run(
context -> {
assertThat(context.getBean("otelOtlpGrpcSpanExporter", OtlpGrpcSpanExporter.class))
assertThat(context.getBean("otelOtlpSpanExporter", OtlpGrpcSpanExporter.class))
.as("OTLP exporter is present even with logging enabled")
.isNotNull();
assertThat(context.getBean("otelLoggingSpanExporter", LoggingSpanExporter.class))

View File

@ -61,12 +61,20 @@ class JaegerSpanExporterAutoConfigurationTest {
@Test
@DisplayName("when exporters are DISABLED should NOT initialize JaegerGrpcSpanExporter bean")
void disabledProperty() {
void disabledPropertyOld() {
this.contextRunner
.withPropertyValues("otel.exporter.jaeger.enabled=false")
.run(context -> assertThat(context.containsBean("otelJaegerSpanExporter")).isFalse());
}
@Test
@DisplayName("when exporters are DISABLED should NOT initialize JaegerGrpcSpanExporter bean")
void disabledProperty() {
this.contextRunner
.withPropertyValues("otel.traces.exporter=none")
.run(context -> assertThat(context.containsBean("otelJaegerSpanExporter")).isFalse());
}
@Test
@DisplayName(
"when jaeger enabled property is MISSING should initialize JaegerGrpcSpanExporter bean")

View File

@ -22,6 +22,17 @@ class LoggingMetricExporterAutoConfigurationTest {
OpenTelemetryAutoConfiguration.class,
LoggingMetricExporterAutoConfiguration.class));
@Test
void loggingEnabledNew() {
runner
.withPropertyValues("otel.metrics.exporter=logging")
.run(
context ->
assertThat(
context.getBean("otelLoggingMetricExporter", LoggingMetricExporter.class))
.isNotNull());
}
@Test
void loggingEnabled() {
runner

View File

@ -24,6 +24,16 @@ class LoggingSpanExporterAutoConfigurationTest {
OpenTelemetryAutoConfiguration.class,
LoggingSpanExporterAutoConfiguration.class));
@Test
void loggingEnabledNew() {
contextRunner
.withPropertyValues("otel.traces.exporter=logging")
.run(
context ->
assertThat(context.getBean("otelLoggingSpanExporter", LoggingSpanExporter.class))
.isNotNull());
}
@Test
@DisplayName("when exporters are ENABLED should initialize LoggingSpanExporter bean")
void loggingEnabled() {

View File

@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -27,9 +28,7 @@ class OtlpLogExporterAutoConfigurationTest {
.withPropertyValues("otel.exporter.otlp.enabled=true")
.run(
context ->
assertThat(
context.getBean(
"otelOtlpGrpcLogRecordExporter", OtlpGrpcLogRecordExporter.class))
assertThat(context.getBean("otelOtlpLogRecordExporter", LogRecordExporter.class))
.isNotNull());
}
@ -39,9 +38,7 @@ class OtlpLogExporterAutoConfigurationTest {
.withPropertyValues("otel.exporter.otlp.logs.enabled=true")
.run(
context ->
assertThat(
context.getBean(
"otelOtlpGrpcLogRecordExporter", OtlpGrpcLogRecordExporter.class))
assertThat(context.getBean("otelOtlpLogRecordExporter", LogRecordExporter.class))
.isNotNull());
}
@ -49,16 +46,21 @@ class OtlpLogExporterAutoConfigurationTest {
void otlpDisabled() {
runner
.withPropertyValues("otel.exporter.otlp.enabled=false")
.run(
context -> assertThat(context.containsBean("otelOtlpGrpcLogRecordExporter")).isFalse());
.run(context -> assertThat(context.containsBean("otelOtlpLogRecordExporter")).isFalse());
}
@Test
void otlpLogsDisabledOld() {
runner
.withPropertyValues("otel.exporter.otlp.logs.enabled=false")
.run(context -> assertThat(context.containsBean("otelOtlpLogRecordExporter")).isFalse());
}
@Test
void otlpLogsDisabled() {
runner
.withPropertyValues("otel.exporter.otlp.logs.enabled=false")
.run(
context -> assertThat(context.containsBean("otelOtlpGrpcLogRecordExporter")).isFalse());
.withPropertyValues("otel.logs.exporter=none")
.run(context -> assertThat(context.containsBean("otelOtlpLogRecordExporter")).isFalse());
}
@Test
@ -66,8 +68,7 @@ class OtlpLogExporterAutoConfigurationTest {
runner.run(
context ->
assertThat(
context.getBean(
"otelOtlpGrpcLogRecordExporter", OtlpGrpcLogRecordExporter.class))
context.getBean("otelOtlpLogRecordExporter", OtlpGrpcLogRecordExporter.class))
.isNotNull());
}
}

View File

@ -27,8 +27,7 @@ class OtlpMetricExporterAutoConfigurationTest {
.withPropertyValues("otel.exporter.otlp.enabled=true")
.run(
context ->
assertThat(
context.getBean("otelOtlpGrpcMetricExporter", OtlpGrpcMetricExporter.class))
assertThat(context.getBean("otelOtlpMetricExporter", OtlpGrpcMetricExporter.class))
.isNotNull());
}
@ -38,8 +37,7 @@ class OtlpMetricExporterAutoConfigurationTest {
.withPropertyValues("otel.exporter.otlp.metrics.enabled=true")
.run(
context ->
assertThat(
context.getBean("otelOtlpGrpcMetricExporter", OtlpGrpcMetricExporter.class))
assertThat(context.getBean("otelOtlpMetricExporter", OtlpGrpcMetricExporter.class))
.isNotNull());
}
@ -47,21 +45,28 @@ class OtlpMetricExporterAutoConfigurationTest {
void otlpDisabled() {
runner
.withPropertyValues("otel.exporter.otlp.enabled=false")
.run(context -> assertThat(context.containsBean("otelOtlpGrpcMetricExporter")).isFalse());
.run(context -> assertThat(context.containsBean("otelOtlpMetricExporter")).isFalse());
}
@Test
void otlpMetricsDisabledOld() {
runner
.withPropertyValues("otel.exporter.otlp.metrics.enabled=false")
.run(context -> assertThat(context.containsBean("otelOtlpMetricExporter")).isFalse());
}
@Test
void otlpMetricsDisabled() {
runner
.withPropertyValues("otel.exporter.otlp.metrics.enabled=false")
.run(context -> assertThat(context.containsBean("otelOtlpGrpcMetricExporter")).isFalse());
.withPropertyValues("otel.metrics.exporter=none")
.run(context -> assertThat(context.containsBean("otelOtlpMetricExporter")).isFalse());
}
@Test
void exporterPresentByDefault() {
runner.run(
context ->
assertThat(context.getBean("otelOtlpGrpcMetricExporter", OtlpGrpcMetricExporter.class))
assertThat(context.getBean("otelOtlpMetricExporter", OtlpGrpcMetricExporter.class))
.isNotNull());
}
}

View File

@ -7,21 +7,32 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.exporters.otlp;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.instrumentation.spring.autoconfigure.MapConverterTestAutoConfiguration;
import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.util.stream.Collectors;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
/** Spring Boot auto configuration test for {@link OtlpSpanExporterAutoConfiguration}. */
class OtlpSpanExporterAutoConfigurationTest {
private final OtlpHttpSpanExporterBuilder otlpHttpSpanExporterBuilder =
Mockito.mock(OtlpHttpSpanExporterBuilder.class);
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(
OpenTelemetryAutoConfiguration.class, OtlpSpanExporterAutoConfiguration.class));
OpenTelemetryAutoConfiguration.class,
OtlpSpanExporterAutoConfiguration.class,
MapConverterTestAutoConfiguration.class))
.withBean(OtlpHttpSpanExporterBuilder.class, () -> otlpHttpSpanExporterBuilder);
@Test
@DisplayName("when exporters are ENABLED should initialize OtlpGrpcSpanExporter bean")
@ -30,8 +41,10 @@ class OtlpSpanExporterAutoConfigurationTest {
.withPropertyValues("otel.exporter.otlp.enabled=true")
.run(
context ->
assertThat(context.getBean("otelOtlpGrpcSpanExporter", OtlpGrpcSpanExporter.class))
assertThat(context.getBean("otelOtlpSpanExporter", OtlpGrpcSpanExporter.class))
.isNotNull());
Mockito.verifyNoMoreInteractions(otlpHttpSpanExporterBuilder);
}
@Test
@ -40,7 +53,7 @@ class OtlpSpanExporterAutoConfigurationTest {
.withPropertyValues("otel.exporter.otlp.traces.enabled=true")
.run(
context ->
assertThat(context.getBean("otelOtlpGrpcSpanExporter", OtlpGrpcSpanExporter.class))
assertThat(context.getBean("otelOtlpSpanExporter", OtlpGrpcSpanExporter.class))
.isNotNull());
}
@ -49,14 +62,21 @@ class OtlpSpanExporterAutoConfigurationTest {
void otlpDisabled() {
this.contextRunner
.withPropertyValues("otel.exporter.otlp.enabled=false")
.run(context -> assertThat(context.containsBean("otelOtlpGrpcSpanExporter")).isFalse());
.run(context -> assertThat(context.containsBean("otelOtlpSpanExporter")).isFalse());
}
@Test
void otlpTracesDisabledOld() {
this.contextRunner
.withPropertyValues("otel.exporter.otlp.traces.enabled=false")
.run(context -> assertThat(context.containsBean("otelOtlpSpanExporter")).isFalse());
}
@Test
void otlpTracesDisabled() {
this.contextRunner
.withPropertyValues("otel.exporter.otlp.traces.enabled=false")
.run(context -> assertThat(context.containsBean("otelOtlpGrpcSpanExporter")).isFalse());
.withPropertyValues("otel.traces.exporter=none")
.run(context -> assertThat(context.containsBean("otelOtlpSpanExporter")).isFalse());
}
@Test
@ -64,7 +84,57 @@ class OtlpSpanExporterAutoConfigurationTest {
void exporterPresentByDefault() {
this.contextRunner.run(
context ->
assertThat(context.getBean("otelOtlpGrpcSpanExporter", OtlpGrpcSpanExporter.class))
assertThat(context.getBean("otelOtlpSpanExporter", OtlpGrpcSpanExporter.class))
.isNotNull());
}
@Test
@DisplayName("use http/protobuf when protocol set")
void useHttp() {
this.contextRunner
.withPropertyValues(
"otel.exporter.otlp.enabled=true",
"otel.exporter.otlp.protocol=http/protobuf",
"otel.exporter.otlp.endpoint=http://localhost:4317",
"otel.exporter.otlp.headers.x=1",
"otel.exporter.otlp.headers.y=2",
"otel.exporter.otlp.timeout=1s")
.run(context -> {});
Mockito.verify(otlpHttpSpanExporterBuilder).build();
Mockito.verify(otlpHttpSpanExporterBuilder).setEndpoint("http://localhost:4317/v1/traces");
Mockito.verify(otlpHttpSpanExporterBuilder).addHeader("x", "1");
Mockito.verify(otlpHttpSpanExporterBuilder).addHeader("y", "2");
Mockito.verify(otlpHttpSpanExporterBuilder).setTimeout(java.time.Duration.ofSeconds(1));
Mockito.verifyNoMoreInteractions(otlpHttpSpanExporterBuilder);
}
@Test
@DisplayName("use http/protobuf with environment variables for headers using the MapConverter")
void useHttpWithEnv() {
this.contextRunner
.withPropertyValues(
"otel.exporter.otlp.enabled=true", "otel.exporter.otlp.protocol=http/protobuf")
// are similar to environment variables in that they use the same converters
.withSystemProperties("otel.exporter.otlp.headers=x=1,y=2")
.run(context -> {});
Mockito.verify(otlpHttpSpanExporterBuilder).build();
Mockito.verify(otlpHttpSpanExporterBuilder).addHeader("x", "1");
Mockito.verify(otlpHttpSpanExporterBuilder).addHeader("y", "2");
Mockito.verifyNoMoreInteractions(otlpHttpSpanExporterBuilder);
}
@Test
@DisplayName("logging exporter can still be configured")
void loggingExporter() {
this.contextRunner
.withBean(LoggingSpanExporter.class, LoggingSpanExporter::create)
.run(
context ->
assertThat(
context.getBeanProvider(SpanExporter.class).stream()
.collect(Collectors.toList()))
.hasSize(2));
}
}

View File

@ -53,12 +53,20 @@ class ZipkinSpanExporterAutoConfigurationTest {
@Test
@DisplayName("when exporters are DISABLED should NOT initialize ZipkinSpanExporter bean")
void disabledProperty() {
void disabledPropertyOld() {
this.contextRunner
.withPropertyValues("otel.exporter.zipkin.enabled=false")
.run(context -> assertThat(context.containsBean("otelZipkinSpanExporter")).isFalse());
}
@Test
@DisplayName("when exporters are DISABLED should NOT initialize ZipkinSpanExporter bean")
void disabledProperty() {
this.contextRunner
.withPropertyValues("otel.traces.exporter=none")
.run(context -> assertThat(context.containsBean("otelZipkinSpanExporter")).isFalse());
}
@Test
@DisplayName("when zipkin enabled property is MISSING should initialize ZipkinSpanExporter bean")
void noProperty() {

View File

@ -8,6 +8,11 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.resources;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import com.google.common.collect.ImmutableMap;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.instrumentation.spring.autoconfigure.MapConverterTestAutoConfiguration;
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -17,7 +22,9 @@ public class OtelResourcePropertiesTest {
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withPropertyValues("otel.springboot.resource.enabled=true")
.withConfiguration(AutoConfigurations.of(OtelResourceAutoConfiguration.class));
.withConfiguration(
AutoConfigurations.of(
OtelResourceAutoConfiguration.class, MapConverterTestAutoConfiguration.class));
@Test
@DisplayName("when attributes are SET should set OtelResourceProperties with given attributes")
@ -25,20 +32,29 @@ public class OtelResourcePropertiesTest {
this.contextRunner
.withPropertyValues(
"otel.springboot.resource.attributes.environment=dev",
"otel.springboot.resource.attributes.xyz=foo",
"otel.springboot.resource.attributes.service.name=backend-name",
"otel.resource.attributes=foo=bar,environment=dev,service.name=hidden2",
"otel.springboot.resource.attributes.foo=baz", // hidden by otel.resource.attributes
"otel.springboot.resource.attributes.service.name=hidden1",
"otel.springboot.resource.attributes.service.instance.id=id-example")
.run(
context -> {
OtelResourceProperties propertiesBean = context.getBean(OtelResourceProperties.class);
ResourceProvider resource =
context.getBean("otelResourceProvider", ResourceProvider.class);
assertThat(propertiesBean.getAttributes())
assertThat(
resource
.createResource(
DefaultConfigProperties.createFromMap(
ImmutableMap.of(
"spring.application.name", "hidden0",
"otel.service.name", "backend")))
.getAttributes()
.asMap())
.contains(
entry("environment", "dev"),
entry("xyz", "foo"),
entry("service.name", "backend-name"),
entry("service.instance.id", "id-example"));
entry(AttributeKey.stringKey("foo"), "bar"),
entry(AttributeKey.stringKey("environment"), "dev"),
entry(AttributeKey.stringKey("service.name"), "backend"),
entry(AttributeKey.stringKey("service.instance.id"), "id-example"));
});
}
@ -48,6 +64,7 @@ public class OtelResourcePropertiesTest {
this.contextRunner.run(
context ->
assertThat(context.getBean(OtelResourceProperties.class).getAttributes()).isEmpty());
assertThat(context.getBean(OtelSpringResourceProperties.class).getAttributes())
.isEmpty());
}
}

View File

@ -31,7 +31,7 @@ class SpringBootSmokeTest extends SmokeTest {
@Override
protected Map<String, String> getExtraEnv() {
return Collections.singletonMap("OTEL_METRICS_EXPORTER", "otlp")
return ["OTEL_METRICS_EXPORTER": "otlp", "OTEL_RESOURCE_ATTRIBUTES": "foo=bar"]
}
@Override
@ -87,6 +87,13 @@ class SpringBootSmokeTest extends SmokeTest {
metrics.hasMetricsNamed("jvm.memory.limit")
metrics.hasMetricsNamed("jvm.memory.used_after_last_gc")
then: "resource attributes are read from the environment"
def foo = findResourceAttribute(traces, "foo")
.map { it.stringValue }
.findAny()
foo.isPresent()
foo.get() == "bar"
then: "service name is autodetected"
def serviceName = findResourceAttribute(traces, "service.name")
.map { it.stringValue }