diff --git a/instrumentation/spring/spring-boot-autoconfigure/README.md b/instrumentation/spring/spring-boot-autoconfigure/README.md index 84800f7329fa..75db68e31b0a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/README.md +++ b/instrumentation/spring/spring-boot-autoconfigure/README.md @@ -1,18 +1,24 @@ # OpenTelemetry Spring Auto-Configuration -Auto-configures OpenTelemetry instrumentation for [spring-web](../spring-web-3.1), [spring-webmvc](../spring-webmvc-3.1), and [spring-webflux](../spring-webflux-5.0). Leverages Spring Aspect Oriented Programming, dependency injection, and bean post-processing to trace spring applications. To include all features listed below use the [opentelemetry-spring-starter](../starters/spring-starter/README.md). +Auto-configures OpenTelemetry instrumentation for [spring-web](../spring-web-3.1) +, [spring-webmvc](../spring-webmvc-3.1), and [spring-webflux](../spring-webflux-5.0). Leverages +Spring Aspect Oriented Programming, dependency injection, and bean post-processing to trace spring +applications. To include all features listed below use +the [opentelemetry-spring-starter](../starters/spring-starter/README.md). ## Quickstart ### Add these dependencies to your project. -Replace `OPENTELEMETRY_VERSION` with the latest stable [release](https://search.maven.org/search?q=g:io.opentelemetry). - - Minimum version: `0.17.0` +Replace `OPENTELEMETRY_VERSION` with the latest +stable [release](https://search.maven.org/search?q=g:io.opentelemetry). +- Minimum version: `0.17.0` For Maven add to your `pom.xml`: ```xml + @@ -27,10 +33,10 @@ For Maven add to your `pom.xml`: OPENTELEMETRY_VERSION - - - - + + + + io.opentelemetry opentelemetry-exporters-logging OPENTELEMETRY_VERSION @@ -57,16 +63,19 @@ implementation("io.opentelemetry:opentelemetry-exporters-otlp:OPENTELEMETRY_VERS The following dependencies are optional but are required to use the corresponding features. Replace `SPRING_VERSION` with the version of spring you're using. - - Minimum version: `3.1` + +- Minimum version: `3.1` Replace `SPRING_WEBFLUX_VERSION` with the version of spring-webflux you're using. - - Minimum version: `5.0` + +- Minimum version: `5.0` Replace `SLF4J_VERSION` with the version of slf4j you're using. For Maven add to your `pom.xml`: ```xml + @@ -144,31 +153,59 @@ implementation("io.opentelemetry:opentelemetry-extension-annotations:OPENTELEMET #### OpenTelemetry Auto Configuration - #### OpenTelemetry Tracer Auto Configuration -Provides a OpenTelemetry tracer bean (`io.opentelemetry.api.trace.Tracer`) if one does not exist in the application context of the spring project. This tracer bean will be used in all configurations listed below. Feel free to declare your own Opentelemetry tracer bean to overwrite this configuration. +Provides a OpenTelemetry tracer bean (`io.opentelemetry.api.trace.Tracer`) if one does not exist in +the application context of the spring project. This tracer bean will be used in all configurations +listed below. Feel free to declare your own Opentelemetry tracer bean to overwrite this +configuration. #### Spring Web Auto Configuration -Provides auto-configuration for the OpenTelemetry RestTemplate trace interceptor defined in [opentelemetry-spring-web-3.1](../spring-web-3.1). This auto-configuration instruments all requests sent using Spring RestTemplate beans by applying a RestTemplate bean post processor. This feature is supported for spring web versions 3.1+ and can be disabled by adding `opentelemetry.trace.httpclients.enabled=false` to your `resources/applications.properties` file. [Spring Web - RestTemplate Client Span](#spring-web---resttemplate-client-span) show cases a sample client span generated by this auto-configuration. Check out [opentelemetry-spring-web-3.1](../spring-web-3.1) to learn more about the OpenTelemetry RestTemplateInterceptor. +Provides auto-configuration for the OpenTelemetry RestTemplate trace interceptor defined +in [opentelemetry-spring-web-3.1](../spring-web-3.1). This auto-configuration instruments all +requests sent using Spring RestTemplate beans by applying a RestTemplate bean post processor. This +feature is supported for spring web versions 3.1+ and can be disabled by +adding `opentelemetry.trace.httpclients.enabled=false` to your `resources/applications.properties` +file. [Spring Web - RestTemplate Client Span](#spring-web---resttemplate-client-span) show cases a +sample client span generated by this auto-configuration. Check +out [opentelemetry-spring-web-3.1](../spring-web-3.1) to learn more about the OpenTelemetry +RestTemplateInterceptor. #### Spring Web MVC Auto Configuration -This feature auto-configures instrumentation for spring-webmvc controllers by adding a [WebMvcTracingFilter](../spring-webmvc-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/WebMvcTracingFilter.java) bean to the application context. This request filter implements the [OncePerRequestFilter](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/filter/OncePerRequestFilter.html) interface to capture OpenTelemetry server spans and propagate distribute tracing context if provided in the request. [Spring Web MVC - Server Span](#spring-web-mvc---server-span) show cases a sample span generated by the WebMvcTracingFilter. Check out [opentelemetry-spring-webmvc-3.1](../spring-webmvc-3.1/) to learn more about the OpenTelemetry WebMvcTracingFilter. +This feature auto-configures instrumentation for spring-webmvc controllers by adding +a [WebMvcTracingFilter](../spring-webmvc-3.1/library/src/main/java/io/opentelemetry/instrumentation/spring/webmvc/WebMvcTracingFilter.java) +bean to the application context. This request filter implements +the [OncePerRequestFilter](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/filter/OncePerRequestFilter.html) +interface to capture OpenTelemetry server spans and propagate distribute tracing context if provided +in the request. [Spring Web MVC - Server Span](#spring-web-mvc---server-span) show cases a sample +span generated by the WebMvcTracingFilter. Check +out [opentelemetry-spring-webmvc-3.1](../spring-webmvc-3.1/) to learn more about the OpenTelemetry +WebMvcTracingFilter. #### Spring WebFlux Auto Configuration -Provides auto-configurations for the OpenTelemetry WebClient ExchangeFilter defined in [opentelemetry-spring-webflux-5.0](../spring-webflux-5.0). This auto-configuration instruments all outgoing http requests sent using Spring's WebClient and WebClient Builder beans by applying a bean post processor. This feature is supported for spring webflux versions 5.0+ and can be disabled by adding `opentelemetry.trace.httpclients.enabled=false` to your `resources/applications.properties` file. [Spring Web-Flux - WebClient Span](#spring-web-flux---webclient-span) showcases a sample span generated by the WebClientFilter. Check out [opentelemetry-spring-webflux-5.0](../spring-webflux-5.0) to learn more about the OpenTelemetry WebClientFilter. +Provides auto-configurations for the OpenTelemetry WebClient ExchangeFilter defined +in [opentelemetry-spring-webflux-5.0](../spring-webflux-5.0). This auto-configuration instruments +all outgoing http requests sent using Spring's WebClient and WebClient Builder beans by applying a +bean post processor. This feature is supported for spring webflux versions 5.0+ and can be disabled +by adding `opentelemetry.trace.httpclients.enabled=false` to +your `resources/applications.properties` +file. [Spring Web-Flux - WebClient Span](#spring-web-flux---webclient-span) showcases a sample span +generated by the WebClientFilter. Check +out [opentelemetry-spring-webflux-5.0](../spring-webflux-5.0) to learn more about the OpenTelemetry +WebClientFilter. #### Manual Instrumentation Support - @WithSpan -This feature uses spring-aop to wrap methods annotated with `@WithSpan` in a span. The arguments -to the method can be captured as attributed on the created span by annotating the method -parameters with `@SpanAttribute`. +This feature uses spring-aop to wrap methods annotated with `@WithSpan` in a span. The arguments to +the method can be captured as attributed on the created span by annotating the method parameters +with `@SpanAttribute`. Note - This annotation can only be applied to bean methods managed by the spring application -context. Check out [spring-aop](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop) +context. Check +out [spring-aop](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop) to learn more about aspect weaving in spring. ##### Usage @@ -187,23 +224,23 @@ import io.opentelemetry.api.trace.SpanKind; @Component public class TracedClass { - @WithSpan - public void tracedMethod() { - } + @WithSpan + public void tracedMethod() { + } - @WithSpan(value="span name") - public void tracedMethodWithName() { - Span currentSpan = Span.current(); - currentSpan.addEvent("ADD EVENT TO tracedMethodWithName SPAN"); - currentSpan.setAttribute("isTestAttribute", true); - } + @WithSpan(value = "span name") + public void tracedMethodWithName() { + Span currentSpan = Span.current(); + currentSpan.addEvent("ADD EVENT TO tracedMethodWithName SPAN"); + currentSpan.setAttribute("isTestAttribute", true); + } - @WithSpan(kind = SpanKind.CLIENT) - public void tracedClientSpan() { - } + @WithSpan(kind = SpanKind.CLIENT) + public void tracedClientSpan() { + } - public void tracedMethodWithAttribute(@SpanAttribute("attributeName") String parameter) { - } + public void tracedMethodWithAttribute(@SpanAttribute("attributeName") String parameter) { + } } ``` @@ -213,80 +250,81 @@ public class TracedClass { The traces below were exported using Zipkin. ##### Spring Web MVC - Server Span + ```json { - "traceId":"0371febbbfa76b2e285a08b53a055d17", - "id":"9b782243ad7df179", - "kind":"SERVER", - "name":"webmvctracingfilter.dofilterinteral", - "timestamp":1596841405866633, - "duration":355648, - "localEndpoint":{ - "serviceName":"sample_trace", - "ipv4":"XXX.XXX.X.XXX" - }, - "tags":{ - "http.client_ip":"0:0:0:0:0:0:0:1", - "http.flavor":"1.1", - "http.method":"GET", - "http.status_code":"200", - "http.url":"/spring-webmvc/sample", - "http.user_agent":"PostmanRuntime/7.26.2", - "net.peer.ip":"0:0:0:0:0:0:0:1", - "net.peer.port":"33916", - "sampling.probability":"1.0" - } - } + "traceId": "0371febbbfa76b2e285a08b53a055d17", + "id": "9b782243ad7df179", + "kind": "SERVER", + "name": "webmvctracingfilter.dofilterinteral", + "timestamp": 1596841405866633, + "duration": 355648, + "localEndpoint": { + "serviceName": "sample_trace", + "ipv4": "XXX.XXX.X.XXX" + }, + "tags": { + "http.client_ip": "0:0:0:0:0:0:0:1", + "http.flavor": "1.1", + "http.method": "GET", + "http.status_code": "200", + "http.url": "/spring-webmvc/sample", + "http.user_agent": "PostmanRuntime/7.26.2", + "net.peer.ip": "0:0:0:0:0:0:0:1", + "net.peer.port": "33916", + "sampling.probability": "1.0" + } +} ``` ##### Spring Web - RestTemplate Client Span ```json { - "traceId":"0371febbbfa76b2e285a08b53a055d17", - "parentId":"9b782243ad7df179", - "id":"43990118a8bdbdf5", - "kind":"CLIENT", - "name":"http get", - "timestamp":1596841405949825, - "duration":21288, - "localEndpoint":{ - "serviceName":"sample_trace", - "ipv4":"XXX.XXX.X.XXX" - }, - "tags":{ - "http.method":"GET", - "http.status_code":"200", - "http.url":"/spring-web/sample/rest-template", - "net.peer.name":"localhost", - "net.peer.port":"8081" - } - } + "traceId": "0371febbbfa76b2e285a08b53a055d17", + "parentId": "9b782243ad7df179", + "id": "43990118a8bdbdf5", + "kind": "CLIENT", + "name": "http get", + "timestamp": 1596841405949825, + "duration": 21288, + "localEndpoint": { + "serviceName": "sample_trace", + "ipv4": "XXX.XXX.X.XXX" + }, + "tags": { + "http.method": "GET", + "http.status_code": "200", + "http.url": "/spring-web/sample/rest-template", + "net.peer.name": "localhost", + "net.peer.port": "8081" + } +} ``` ##### Spring Web-Flux - WebClient Span ```json { - "traceId":"0371febbbfa76b2e285a08b53a055d17", - "parentId":"9b782243ad7df179", - "id":"1b14a2fc89d7a762", - "kind":"CLIENT", - "name":"http post", - "timestamp":1596841406109125, - "duration":25137, - "localEndpoint":{ - "serviceName":"sample_trace", - "ipv4":"XXX.XXX.X.XXX" - }, - "tags":{ - "http.method":"POST", - "http.status_code":"200", - "http.url":"/spring-webflux/sample/web-client", - "net.peer.name":"localhost", - "net.peer.port":"8082" - } - } + "traceId": "0371febbbfa76b2e285a08b53a055d17", + "parentId": "9b782243ad7df179", + "id": "1b14a2fc89d7a762", + "kind": "CLIENT", + "name": "http post", + "timestamp": 1596841406109125, + "duration": 25137, + "localEndpoint": { + "serviceName": "sample_trace", + "ipv4": "XXX.XXX.X.XXX" + }, + "tags": { + "http.method": "POST", + "http.status_code": "200", + "http.url": "/spring-webflux/sample/web-client", + "net.peer.name": "localhost", + "net.peer.port": "8082" + } +} ``` ##### @WithSpan Instrumentation @@ -355,7 +393,8 @@ The traces below were exported using Zipkin. #### Spring Support -Auto-configuration is natively supported by Springboot applications. To enable these features in "vanilla" use `@EnableOpenTelemetryTracing` to complete a component scan of this package. +Auto-configuration is natively supported by Springboot applications. To enable these features in " +vanilla" use `@EnableOpenTelemetryTracing` to complete a component scan of this package. ##### Usage @@ -365,15 +404,24 @@ import org.springframework.context.annotation.Configuration; @Configuration @EnableOpenTelemetry -public class OpenTelemetryConfig {} +public class OpenTelemetryConfig { +} ``` #### Exporter Configurations -This package provides auto configurations for [OTLP](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/otlp), [Jaeger](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/jaeger), [Zipkin](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/zipkin), and [Logging](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/logging) Span Exporters. - -If an exporter is present in the classpath during runtime and a spring bean of the exporter is missing from the spring application context. An exporter bean is initialized and added to a simple span processor in the active tracer provider. Check out the implementation [here](./src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java). +This package provides auto configurations +for [OTLP](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/otlp) +, [Jaeger](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/jaeger) +, [Zipkin](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/zipkin), +and [Logging](https://github.com/open-telemetry/opentelemetry-java/tree/main/exporters/logging) Span +Exporters. +If an exporter is present in the classpath during runtime and a spring bean of the exporter is +missing from the spring application context. An exporter bean is initialized and added to a simple +span processor in the active tracer provider. Check out the +implementation [here](./src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java) +. #### Configuration Properties @@ -392,6 +440,26 @@ If an exporter is present in the classpath during runtime and a spring bean of t +##### 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` | + +`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 +application.properties like this: + +``` +otel.springboot.resource.attributes.environment=dev +otel.springboot.resource.attributes.xyz=foo +``` + ##### Exporter Properties | Feature | Property | Default Value | @@ -410,4 +478,6 @@ If an exporter is present in the classpath during runtime and a spring bean of t ### Starter Guide -Check out the opentelemetry [quick start](https://github.com/open-telemetry/opentelemetry-java/blob/main/QUICKSTART.md) to learn more about OpenTelemetry instrumentation. +Check out the +opentelemetry [quick start](https://github.com/open-telemetry/opentelemetry-java/blob/main/QUICKSTART.md) +to learn more about OpenTelemetry instrumentation. diff --git a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts index 967dee5e8bf5..745e5a1da443 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts +++ b/instrumentation/spring/spring-boot-autoconfigure/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-extension-annotations") compileOnly("io.opentelemetry:opentelemetry-extension-trace-propagators") compileOnly("io.opentelemetry:opentelemetry-extension-aws") + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-resources") compileOnly("io.opentelemetry:opentelemetry-exporter-logging") compileOnly("io.opentelemetry:opentelemetry-exporter-jaeger") compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") @@ -41,6 +42,7 @@ dependencies { testImplementation(project(":testing-common")) testImplementation("io.opentelemetry:opentelemetry-sdk") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-resources") testImplementation("io.opentelemetry:opentelemetry-extension-annotations") testImplementation("io.opentelemetry:opentelemetry-extension-trace-propagators") testImplementation("io.opentelemetry:opentelemetry-extension-aws") diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java index ab39fb2adcaf..5d96059a51b9 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java @@ -6,21 +6,26 @@ package io.opentelemetry.instrumentation.spring.autoconfigure; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.context.propagation.ContextPropagators; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; /** * Create {@link io.opentelemetry.api.trace.Tracer} bean if bean is missing. @@ -41,7 +46,8 @@ public static class OpenTelemetryBeanConfig { @ConditionalOnMissingBean public SdkTracerProvider sdkTracerProvider( SamplerProperties samplerProperties, - ObjectProvider> spanExportersProvider) { + ObjectProvider> spanExportersProvider, + Resource otelResource) { SdkTracerProviderBuilder tracerProviderBuilder = SdkTracerProvider.builder(); spanExportersProvider.getIfAvailable(Collections::emptyList).stream() @@ -49,10 +55,33 @@ public SdkTracerProvider sdkTracerProvider( .forEach(tracerProviderBuilder::addSpanProcessor); return tracerProviderBuilder + .setResource(otelResource) .setSampler(Sampler.traceIdRatioBased(samplerProperties.getProbability())) .build(); } + @Bean + @ConditionalOnMissingBean + public Resource otelResource( + Environment env, ObjectProvider>> resourceProviders) { + String applicationName = env.getProperty("spring.application.name"); + Resource resource = defaultResource(applicationName); + List> resourceCustomizers = + resourceProviders.getIfAvailable(Collections::emptyList); + for (Supplier resourceCustomizer : resourceCustomizers) { + resource = resource.merge(resourceCustomizer.get()); + } + return resource; + } + + private static Resource defaultResource(String applicationName) { + if (applicationName == null) { + return Resource.getDefault(); + } + return Resource.getDefault() + .merge(Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName))); + } + @Bean public OpenTelemetry openTelemetry( ObjectProvider propagatorsProvider, SdkTracerProvider tracerProvider) { diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourceAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourceAutoConfiguration.java new file mode 100644 index 000000000000..606e37673fad --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourceAutoConfiguration.java @@ -0,0 +1,79 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.sdk.extension.resources.ContainerResource; +import io.opentelemetry.sdk.extension.resources.HostResource; +import io.opentelemetry.sdk.extension.resources.OsResource; +import io.opentelemetry.sdk.extension.resources.ProcessResource; +import io.opentelemetry.sdk.extension.resources.ProcessRuntimeResource; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import java.util.Map; +import java.util.function.Supplier; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +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.Configuration; +import org.springframework.core.env.Environment; + +@Configuration +@EnableConfigurationProperties(OtelResourceProperties.class) +@AutoConfigureBefore(OpenTelemetryAutoConfiguration.class) +@ConditionalOnProperty(prefix = "otel.springboot.resource", name = "enabled", matchIfMissing = true) +public class OtelResourceAutoConfiguration { + + @Bean + public Supplier otelResourceProvider( + Environment env, OtelResourceProperties otelResourceProperties) { + return () -> { + AttributesBuilder attributesBuilder = Attributes.builder(); + for (Map.Entry entry : otelResourceProperties.getAttributes().entrySet()) { + attributesBuilder.put(entry.getKey(), entry.getValue()); + } + String otelServiceName = env.getProperty("otel.springboot.resource.attributes.service.name"); + if (otelServiceName != null) { + attributesBuilder.put(ResourceAttributes.SERVICE_NAME, otelServiceName); + } + Attributes attributes = attributesBuilder.build(); + return Resource.create(attributes); + }; + } + + @Bean + @ConditionalOnClass(OsResource.class) + public Supplier otelOsResourceProvider() { + return OsResource::get; + } + + @Bean + @ConditionalOnClass(ProcessResource.class) + public Supplier otelProcessResourceProvider() { + return ProcessResource::get; + } + + @Bean + @ConditionalOnClass(ProcessRuntimeResource.class) + public Supplier otelProcessRuntimeResourceProvider() { + return ProcessRuntimeResource::get; + } + + @Bean + @ConditionalOnClass(HostResource.class) + public Supplier otelHostResourceProvider() { + return HostResource::get; + } + + @Bean + @ConditionalOnClass(ContainerResource.class) + public Supplier otelContainerResource() { + return ContainerResource::get; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourceProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourceProperties.java new file mode 100644 index 000000000000..192c92e83656 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourceProperties.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import java.util.Collections; +import java.util.Map; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "otel.springboot.resource") +public class OtelResourceProperties { + private Map attributes = Collections.emptyMap(); + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 3a0d956a402d..2f9311698c1c 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -8,4 +8,5 @@ io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfigura io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.resttemplate.RestTemplateAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.httpclients.webclient.WebClientAutoConfiguration,\ io.opentelemetry.instrumentation.spring.autoconfigure.webmvc.WebMvcFilterAutoConfiguration,\ -io.opentelemetry.instrumentation.spring.autoconfigure.aspects.TraceAspectAutoConfiguration +io.opentelemetry.instrumentation.spring.autoconfigure.aspects.TraceAspectAutoConfiguration,\ +io.opentelemetry.instrumentation.spring.autoconfigure.OtelResourceAutoConfiguration diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java index 5bbb9515a5fb..812c0f87ce18 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java @@ -5,9 +5,11 @@ package io.opentelemetry.instrumentation.spring.autoconfigure; +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -69,4 +71,33 @@ void initializeOpenTelemetry() { .hasBean("customTracerProvider") .doesNotHaveBean("sdkTracerProvider")); } + + @Test + @DisplayName( + "when spring.application.name is set value should be passed to service name attribute") + void shouldDetermineServiceNameBySpringApplicationName() { + this.contextRunner + .withPropertyValues("spring.application.name=myapp-backend") + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .run( + context -> { + Resource otelResource = context.getBean("otelResource", Resource.class); + + assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("myapp-backend"); + }); + } + + @Test + @DisplayName( + "when spring application name and otel service name are not set service name should be default") + void hasDefaultServiceName() { + this.contextRunner + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .run( + context -> { + Resource otelResource = context.getBean("otelResource", Resource.class); + + assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("unknown_service:java"); + }); + } } diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourceAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourceAutoConfigurationTest.java new file mode 100644 index 000000000000..9f982127d810 --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourceAutoConfigurationTest.java @@ -0,0 +1,82 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.SERVICE_NAME; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.resources.Resource; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +public class OtelResourceAutoConfigurationTest { + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of( + OtelResourceAutoConfiguration.class, OpenTelemetryAutoConfiguration.class)); + + @Test + @DisplayName("when otel service name is set it should be set as service name attribute") + void shouldDetermineServiceNameByOtelServiceName() { + this.contextRunner + .withPropertyValues( + "otel.springboot.resource.attributes.service.name=otel-name-backend", + "otel.springboot.resource.enabled=true") + .run( + context -> { + Resource otelResource = context.getBean("otelResource", Resource.class); + + assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("otel-name-backend"); + }); + } + + @Test + @DisplayName( + "when otel.springboot.resource.enabled is not specified configuration should be initialized") + void shouldInitAutoConfigurationByDefault() { + this.contextRunner + .withPropertyValues("otel.springboot.resource.attributes.service.name=otel-name-backend") + .run( + context -> { + Resource otelResource = context.getBean("otelResource", Resource.class); + + assertThat(otelResource.getAttribute(SERVICE_NAME)).isEqualTo("otel-name-backend"); + }); + } + + @Test + @DisplayName( + "when otel.springboot.resource.enabled is set to false configuration should NOT be initialized") + void shouldNotInitAutoConfiguration() { + this.contextRunner + .withPropertyValues( + "otel.springboot.resource.attributes.service.name=otel-name-backend", + "otel.springboot.resource.enabled=false") + .run(context -> assertThat(context.containsBean("otelResourceProvider")).isFalse()); + } + + @Test + @DisplayName("when otel attributes are set in properties they should be put in resource") + void shouldInitializeAttributes() { + this.contextRunner + .withPropertyValues( + "otel.springboot.resource.attributes.xyz=foo", + "otel.springboot.resource.attributes.environment=dev", + "otel.springboot.resource.enabled=true") + .run( + context -> { + Resource otelResource = context.getBean("otelResource", Resource.class); + + assertThat(otelResource.getAttribute(AttributeKey.stringKey("environment"))) + .isEqualTo("dev"); + assertThat(otelResource.getAttribute(AttributeKey.stringKey("xyz"))).isEqualTo("foo"); + }); + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourcePropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourcePropertiesTest.java new file mode 100644 index 000000000000..16c04c22eb8a --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OtelResourcePropertiesTest.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +public class OtelResourcePropertiesTest { + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withPropertyValues("otel.springboot.resource.enabled=true") + .withConfiguration(AutoConfigurations.of(OtelResourceAutoConfiguration.class)); + + @Test + @DisplayName("when attributes are SET should set OtelResourceProperties with given attributes") + void hasAttributes() { + + this.contextRunner + .withPropertyValues( + "otel.springboot.resource.attributes.environment=dev", + "otel.springboot.resource.attributes.xyz=foo") + .run( + context -> { + OtelResourceProperties propertiesBean = context.getBean(OtelResourceProperties.class); + + assertThat(propertiesBean.getAttributes()) + .hasFieldOrPropertyWithValue("environment", "dev") + .hasFieldOrPropertyWithValue("xyz", "foo"); + }); + } + + @Test + @DisplayName("when attributes are DEFAULT should set OtelResourceProperties to default values") + void hasDefaultTypes() { + + this.contextRunner.run( + context -> + assertThat(context.getBean(OtelResourceProperties.class).getAttributes()).isEmpty()); + } +}