OtelSpringStarterSmokeTest.java 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /*
  2. * Copyright The OpenTelemetry Authors
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. package io.opentelemetry.smoketest;
  6. import static org.assertj.core.api.Assertions.assertThat;
  7. import io.opentelemetry.api.OpenTelemetry;
  8. import io.opentelemetry.api.common.AttributeKey;
  9. import io.opentelemetry.api.common.Attributes;
  10. import io.opentelemetry.api.trace.SpanKind;
  11. import io.opentelemetry.instrumentation.spring.autoconfigure.properties.OtelResourceProperties;
  12. import io.opentelemetry.instrumentation.spring.autoconfigure.properties.OtlpExporterProperties;
  13. import io.opentelemetry.instrumentation.spring.autoconfigure.properties.PropagationProperties;
  14. import io.opentelemetry.instrumentation.spring.autoconfigure.properties.SpringConfigProperties;
  15. import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
  16. import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
  17. import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
  18. import io.opentelemetry.sdk.logs.data.LogRecordData;
  19. import io.opentelemetry.sdk.resources.Resource;
  20. import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions;
  21. import io.opentelemetry.semconv.HttpAttributes;
  22. import io.opentelemetry.semconv.UrlAttributes;
  23. import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes;
  24. import io.opentelemetry.semconv.incubating.DbIncubatingAttributes;
  25. import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes;
  26. import io.opentelemetry.spring.smoketest.AbstractSpringStarterSmokeTest;
  27. import io.opentelemetry.spring.smoketest.OtelSpringStarterSmokeTestApplication;
  28. import io.opentelemetry.spring.smoketest.OtelSpringStarterSmokeTestController;
  29. import io.opentelemetry.spring.smoketest.SpringSmokeOtelConfiguration;
  30. import io.opentelemetry.spring.smoketest.SpringSmokeTestRunner;
  31. import java.util.Collections;
  32. import org.assertj.core.api.AbstractCharSequenceAssert;
  33. import org.assertj.core.api.AbstractIterableAssert;
  34. import org.junit.jupiter.api.BeforeEach;
  35. import org.junit.jupiter.api.MethodOrderer;
  36. import org.junit.jupiter.api.Test;
  37. import org.junit.jupiter.api.TestMethodOrder;
  38. import org.springframework.beans.factory.annotation.Autowired;
  39. import org.springframework.boot.test.context.SpringBootTest;
  40. import org.springframework.boot.test.web.client.TestRestTemplate;
  41. import org.springframework.context.annotation.Bean;
  42. import org.springframework.context.annotation.Configuration;
  43. import org.springframework.core.annotation.Order;
  44. import org.springframework.core.env.Environment;
  45. /**
  46. * This test class enforces the order of the tests to make sure that {@link #shouldSendTelemetry()},
  47. * which asserts the telemetry data from the application startup, is executed first.
  48. *
  49. * <p>The exporters are not reset using {@link org.junit.jupiter.api.BeforeEach}, because it would
  50. * prevent the telemetry data from the application startup to be asserted.
  51. */
  52. @SpringBootTest(
  53. classes = {
  54. OtelSpringStarterSmokeTestApplication.class,
  55. OtelSpringStarterSmokeTest.TestConfiguration.class,
  56. SpringSmokeOtelConfiguration.class
  57. },
  58. webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
  59. properties = {
  60. // The headers are simply set here to make sure that headers can be parsed
  61. "otel.exporter.otlp.headers.c=3"
  62. })
  63. @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
  64. class OtelSpringStarterSmokeTest extends AbstractSpringStarterSmokeTest {
  65. @Autowired private TestRestTemplate testRestTemplate;
  66. @Autowired private Environment environment;
  67. @Autowired private PropagationProperties propagationProperties;
  68. @Autowired private OtelResourceProperties otelResourceProperties;
  69. @Autowired private OtlpExporterProperties otlpExporterProperties;
  70. @Autowired OpenTelemetry openTelemetry;
  71. @BeforeEach
  72. void initOpenTelemetry() {
  73. testing = new SpringSmokeTestRunner(openTelemetry);
  74. }
  75. @Configuration(proxyBeanMethods = false)
  76. static class TestConfiguration {
  77. @Bean
  78. @Order(1)
  79. AutoConfigurationCustomizerProvider hiddenPropagatorCustomizer() {
  80. return customizer ->
  81. customizer.addResourceCustomizer(
  82. (resource, config) ->
  83. resource.merge(
  84. Resource.create(
  85. Attributes.of(
  86. AttributeKey.booleanKey("keyFromResourceCustomizer"), false))));
  87. }
  88. @Bean
  89. @Order(2)
  90. AutoConfigurationCustomizerProvider propagatorCustomizer() {
  91. return customizer ->
  92. customizer.addResourceCustomizer(
  93. (resource, config) ->
  94. resource.merge(
  95. Resource.create(
  96. Attributes.of(
  97. AttributeKey.booleanKey("keyFromResourceCustomizer"), true))));
  98. }
  99. }
  100. @Test
  101. void propertyConversion() {
  102. ConfigProperties configProperties =
  103. SpringConfigProperties.create(
  104. environment,
  105. otlpExporterProperties,
  106. otelResourceProperties,
  107. propagationProperties,
  108. DefaultConfigProperties.createFromMap(
  109. Collections.singletonMap("otel.exporter.otlp.headers", "a=1,b=2")));
  110. assertThat(configProperties.getMap("otel.exporter.otlp.headers"))
  111. .containsEntry("a", "1")
  112. .containsEntry("b", "2")
  113. .containsEntry("c", "3");
  114. assertThat(configProperties.getList("otel.propagators")).containsExactly("b3");
  115. }
  116. @Test
  117. @org.junit.jupiter.api.Order(1)
  118. void shouldSendTelemetry() {
  119. testRestTemplate.getForObject(OtelSpringStarterSmokeTestController.PING, String.class);
  120. // Span
  121. testing.waitAndAssertTraces(
  122. traceAssert ->
  123. traceAssert.hasSpansSatisfyingExactly(
  124. spanDataAssert ->
  125. spanDataAssert
  126. .hasKind(SpanKind.CLIENT)
  127. .hasAttribute(
  128. DbIncubatingAttributes.DB_STATEMENT,
  129. "create table test_table (id bigint not null, primary key (id))")),
  130. traceAssert ->
  131. traceAssert.hasSpansSatisfyingExactly(
  132. clientSpan ->
  133. clientSpan
  134. .hasKind(SpanKind.CLIENT)
  135. .hasAttributesSatisfying(
  136. a -> assertThat(a.get(UrlAttributes.URL_FULL)).endsWith("/ping")),
  137. serverSpan ->
  138. serverSpan
  139. .hasKind(SpanKind.SERVER)
  140. .hasResourceSatisfying(
  141. r ->
  142. r.hasAttribute(
  143. AttributeKey.booleanKey("keyFromResourceCustomizer"), true)
  144. .hasAttribute(
  145. AttributeKey.stringKey("attributeFromYaml"), "true")
  146. .hasAttribute(
  147. OpenTelemetryAssertions.satisfies(
  148. ServiceIncubatingAttributes.SERVICE_INSTANCE_ID,
  149. AbstractCharSequenceAssert::isNotBlank)))
  150. .hasAttribute(HttpAttributes.HTTP_REQUEST_METHOD, "GET")
  151. .hasAttribute(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 200L)
  152. .hasAttribute(HttpAttributes.HTTP_ROUTE, "/ping")));
  153. // Metric
  154. testing.waitAndAssertMetrics(
  155. OtelSpringStarterSmokeTestController.METER_SCOPE_NAME,
  156. OtelSpringStarterSmokeTestController.TEST_HISTOGRAM,
  157. AbstractIterableAssert::isNotEmpty);
  158. // Log
  159. LogRecordData firstLog = testing.getExportedLogRecords().get(0);
  160. assertThat(firstLog.getBody().asString())
  161. .as("Should instrument logs")
  162. .startsWith("Starting ")
  163. .contains(this.getClass().getSimpleName());
  164. assertThat(firstLog.getAttributes().asMap())
  165. .as("Should capture code attributes")
  166. .containsEntry(
  167. CodeIncubatingAttributes.CODE_NAMESPACE, "org.springframework.boot.StartupInfoLogger");
  168. }
  169. @Test
  170. void restTemplate() {
  171. assertClient(OtelSpringStarterSmokeTestController.REST_TEMPLATE);
  172. }
  173. @Test
  174. void restClient() {
  175. assertClient(OtelSpringStarterSmokeTestController.REST_CLIENT);
  176. }
  177. private void assertClient(String url) {
  178. testing.clearAllExportedData();
  179. testRestTemplate.getForObject(url, String.class);
  180. testing.waitAndAssertTraces(
  181. traceAssert ->
  182. traceAssert.hasSpansSatisfyingExactly(
  183. clientSpan ->
  184. clientSpan
  185. .hasKind(SpanKind.CLIENT)
  186. .hasAttributesSatisfying(
  187. a -> assertThat(a.get(UrlAttributes.URL_FULL)).endsWith(url)),
  188. serverSpan ->
  189. serverSpan
  190. .hasKind(SpanKind.SERVER)
  191. .hasAttribute(HttpAttributes.HTTP_ROUTE, url),
  192. nestedClientSpan ->
  193. nestedClientSpan
  194. .hasKind(SpanKind.CLIENT)
  195. .hasAttributesSatisfying(
  196. a -> assertThat(a.get(UrlAttributes.URL_FULL)).endsWith("/ping")),
  197. nestedServerSpan ->
  198. nestedServerSpan
  199. .hasKind(SpanKind.SERVER)
  200. .hasAttribute(HttpAttributes.HTTP_ROUTE, "/ping")));
  201. }
  202. }