Skip to content

Commit

Permalink
add support for OTEL_RESOURCE_ATTRIBUTES, OTEL_SERVICE_NAME, OTEL_EXP…
Browse files Browse the repository at this point in the history
…ORTER_OTLP_HEADERS and OTEL_EXPORTER_OTLP_PROTOCOL for spring boot starter (#9950)
  • Loading branch information
zeitlinger committed Dec 21, 2023
1 parent 67facf3 commit 87615cc
Show file tree
Hide file tree
Showing 30 changed files with 772 additions and 193 deletions.
104 changes: 79 additions & 25 deletions instrumentation/spring/spring-boot-autoconfigure/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

| 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 |
##### Enabling/Disabling Exporters

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 -->

##### Resource Properties
##### Enabling/Disabling Features

| 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 | 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 |

`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)
##### Resource Properties

| Feature | Property | Default Value |
| -------- |---------------------------------------------------------------------| ---------------------- |
| Resource | otel.springboot.resource.enabled | `true` |
| | otel.resource.attributes (old: otel.springboot.resource.attributes) | `empty map` |

`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 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
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;
Expand All @@ -30,8 +35,10 @@
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;
Expand All @@ -56,6 +63,19 @@ public 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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -23,7 +25,7 @@
@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 {
Expand All @@ -43,4 +45,19 @@ public io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter otelJaegerSpanExp
}
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@
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;

/** Configures {@link LoggingSpanExporter} bean for tracing. */
@Configuration
@EnableConfigurationProperties(LoggingExporterProperties.class)
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@Conditional(LoggingMetricExporterAutoConfiguration.AnyPropertyEnabled.class)
@Conditional(LoggingMetricExporterAutoConfiguration.CustomCondition.class)
@ConditionalOnClass(LoggingMetricExporter.class)
public class LoggingMetricExporterAutoConfiguration {

Expand All @@ -30,16 +30,18 @@ public LoggingMetricExporter otelLoggingMetricExporter() {
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 {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@

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;

/** Configures {@link LoggingSpanExporter} bean for tracing. */
@Configuration
@EnableConfigurationProperties(LoggingExporterProperties.class)
@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class)
@Conditional(LoggingSpanExporterAutoConfiguration.AnyPropertyEnabled.class)
@Conditional(LoggingSpanExporterAutoConfiguration.CustomCondition.class)
@ConditionalOnClass(LoggingSpanExporter.class)
public class LoggingSpanExporterAutoConfiguration {

Expand All @@ -31,16 +31,18 @@ public LoggingSpanExporter otelLoggingSpanExporter() {
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 {}
}
}
Loading

0 comments on commit 87615cc

Please sign in to comment.