diff --git a/src/main/java/com/knecon/fforesight/tracing/DefaultTracingAutoConfiguration.java b/src/main/java/com/knecon/fforesight/tracing/DefaultTracingAutoConfiguration.java index 2231479..11dc03b 100644 --- a/src/main/java/com/knecon/fforesight/tracing/DefaultTracingAutoConfiguration.java +++ b/src/main/java/com/knecon/fforesight/tracing/DefaultTracingAutoConfiguration.java @@ -1,26 +1,27 @@ package com.knecon.fforesight.tracing; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.PropertySource; +import org.springframework.http.server.observation.ServerRequestObservationContext; + import io.micrometer.context.ContextRegistry; import io.micrometer.observation.ObservationPredicate; import io.micrometer.tracing.Tracer; import io.micrometer.tracing.contextpropagation.ObservationAwareSpanThreadLocalAccessor; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.actuate.autoconfigure.tracing.ConditionalOnEnabledTracing; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.PropertySource; -import org.springframework.http.server.observation.ServerRequestObservationContext; @AutoConfiguration @ConditionalOnEnabledTracing @Import({ TaskExecutionConfiguration.class, TracingRabbitConfiguration.class, - WebMvcConfiguration.class + WebMvcConfiguration.class, + TracingSafetyCheck.class }) @PropertySource("classpath:tracing-task.properties") @Slf4j @@ -29,12 +30,18 @@ public class DefaultTracingAutoConfiguration { @Autowired Tracer tracer; + @Autowired + TracingSafetyCheck tracingSafetyCheck; + @PostConstruct public void postConstruct() { - ContextRegistry.getInstance().registerThreadLocalAccessor(new ObservationAwareSpanThreadLocalAccessor(tracer)); - - log.info("Tracing AutoConfiguration Loaded!"); + if (tracingSafetyCheck.isEndpointResolvable()) { + ContextRegistry.getInstance().registerThreadLocalAccessor(new ObservationAwareSpanThreadLocalAccessor(tracer)); + log.info("Tracing AutoConfiguration Loaded!"); + } else { + log.info("Hostname is not resolvable, tracing is disabled."); + } } diff --git a/src/main/java/com/knecon/fforesight/tracing/OpenTelemetryConfig.java b/src/main/java/com/knecon/fforesight/tracing/OpenTelemetryConfig.java new file mode 100644 index 0000000..22e9b33 --- /dev/null +++ b/src/main/java/com/knecon/fforesight/tracing/OpenTelemetryConfig.java @@ -0,0 +1,22 @@ +package com.knecon.fforesight.tracing; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Configuration +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OpenTelemetryConfig { + + @Value("${management.otlp.tracing.endpoint}") + private String otlpEndpoint; + + @Value("${management.tracing.enabled}") + private boolean tracingEnabled; + +} \ No newline at end of file diff --git a/src/main/java/com/knecon/fforesight/tracing/TracingSafetyCheck.java b/src/main/java/com/knecon/fforesight/tracing/TracingSafetyCheck.java new file mode 100644 index 0000000..d5108ad --- /dev/null +++ b/src/main/java/com/knecon/fforesight/tracing/TracingSafetyCheck.java @@ -0,0 +1,45 @@ +package com.knecon.fforesight.tracing; + +import java.net.HttpURLConnection; +import java.net.URL; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import lombok.Builder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Service +@Builder +@Getter +@Slf4j +public class TracingSafetyCheck { + + @Autowired + private OpenTelemetryConfig openTelemetryConfig; + + + public boolean isEndpointResolvable() { + + String endpoint = openTelemetryConfig.getOtlpEndpoint(); + boolean resolvable = endpoint != null && canResolveEndpoint(endpoint); + log.info("Endpoint {} is resolvable: {}", endpoint, resolvable); + return resolvable; + } + + + private boolean canResolveEndpoint(String endpoint) { + + try { + URL url = new URL(endpoint); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("HEAD"); + int responseCode = connection.getResponseCode(); + return (200 <= responseCode && responseCode <= 399); + } catch (Exception e) { + return false; + } + } + +} \ No newline at end of file diff --git a/src/test/java/com/knecon/fforesight/tracing/ObservationEnabledTest.java b/src/test/java/com/knecon/fforesight/tracing/ObservationEnabledTest.java index 758147f..ab4cd9f 100644 --- a/src/test/java/com/knecon/fforesight/tracing/ObservationEnabledTest.java +++ b/src/test/java/com/knecon/fforesight/tracing/ObservationEnabledTest.java @@ -1,8 +1,9 @@ package com.knecon.fforesight.tracing; -import com.knecon.fforesight.tenantcommons.RabbitTemplateMultiCustomizer; -import com.knecon.fforesight.tenantcommons.SimpleMessageListenerContainerCustomizer; -import lombok.extern.slf4j.Slf4j; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.amqp.rabbit.core.RabbitTemplate; @@ -11,26 +12,29 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; +import com.knecon.fforesight.tenantcommons.RabbitTemplateMultiCustomizer; +import com.knecon.fforesight.tenantcommons.SimpleMessageListenerContainerCustomizer; + +import lombok.extern.slf4j.Slf4j; @ExtendWith(OutputCaptureExtension.class) @Slf4j public class ObservationEnabledTest { + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues("management.tracing.enabled=true") + .withPropertyValues("spring.profiles.active=test") + .withUserConfiguration(SharedTestConfiguration.class, DefaultTracingAutoConfiguration.class, OpenTelemetryConfig.class); - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("management.tracing.enabled=true") - .withUserConfiguration(SharedTestConfiguration.class, DefaultTracingAutoConfiguration.class); @Test public void testTracingAutoConfigurationLoaded(CapturedOutput output) { + this.contextRunner.run(context -> { assertThat(output.getOut()).contains("Tracing AutoConfiguration Loaded!"); }); } + @Test public void testRabbitTracingEnabled() { diff --git a/src/test/java/com/knecon/fforesight/tracing/SharedTestConfiguration.java b/src/test/java/com/knecon/fforesight/tracing/SharedTestConfiguration.java index cbf61c3..5c6d069 100644 --- a/src/test/java/com/knecon/fforesight/tracing/SharedTestConfiguration.java +++ b/src/test/java/com/knecon/fforesight/tracing/SharedTestConfiguration.java @@ -1,16 +1,31 @@ package com.knecon.fforesight.tracing; +import org.mockito.Mockito; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + import io.micrometer.tracing.Tracer; import io.micrometer.tracing.test.simple.SimpleTracer; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.context.annotation.Bean; @AutoConfiguration +@Profile("test") public class SharedTestConfiguration { + @Bean Tracer simpleTracer() { + return new SimpleTracer(); } + + @Bean + public TracingSafetyCheck tracingSafetyCheck() { + + TracingSafetyCheck mock = Mockito.mock(TracingSafetyCheck.class); + Mockito.when(mock.isEndpointResolvable()).thenReturn(true); + return mock; + } + } diff --git a/src/test/java/com/knecon/fforesight/tracing/TaskExecutionTracingTest.java b/src/test/java/com/knecon/fforesight/tracing/TaskExecutionTracingTest.java index 6876be4..be7c37e 100644 --- a/src/test/java/com/knecon/fforesight/tracing/TaskExecutionTracingTest.java +++ b/src/test/java/com/knecon/fforesight/tracing/TaskExecutionTracingTest.java @@ -1,11 +1,11 @@ package com.knecon.fforesight.tracing; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.tracing.Span; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.test.simple.SimpleTracer; -import lombok.extern.slf4j.Slf4j; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + import org.assertj.core.api.BDDAssertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -18,11 +18,12 @@ import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.Tracer; +import io.micrometer.tracing.test.simple.SimpleTracer; +import lombok.extern.slf4j.Slf4j; @ExtendWith(OutputCaptureExtension.class) @Slf4j @@ -30,18 +31,20 @@ public class TaskExecutionTracingTest { private static final String ASYNC_SPAN_NAME = "Async Span"; - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("management.tracing.enabled=true") + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues("management.tracing.enabled=true") .withConfiguration(AutoConfigurations.of(TaskExecutionAutoConfiguration.class)) - .withUserConfiguration(SharedTestConfiguration.class, AsyncLogic.class, DefaultTracingAutoConfiguration.class); + .withPropertyValues("spring.profiles.active=test") + .withUserConfiguration(SharedTestConfiguration.class, AsyncLogic.class, DefaultTracingAutoConfiguration.class, OpenTelemetryConfig.class); + private final ObservationRegistry observationRegistry = ObservationRegistry.create(); @Test public void asyncMethodTraceTest(CapturedOutput output) { + this.contextRunner.run(context -> { - SimpleTracer tracer = (SimpleTracer)context.getBean(Tracer.class); - var asyncLogic = context.getBean(AsyncLogic.class); + SimpleTracer tracer = (SimpleTracer) context.getBean(Tracer.class); + AsyncLogic asyncLogic = context.getBean(AsyncLogic.class); Observation firstSpan = Observation.createNotStarted("First span", observationRegistry).highCardinalityKeyValue("test", "test 1"); try (Observation.Scope scope = firstSpan.start().openScope()) { @@ -54,18 +57,18 @@ public class TaskExecutionTracingTest { String spanIdFromFuture = future.get(1, TimeUnit.SECONDS); log.info("Async in test with span - after call"); BDDAssertions.then(spanIdFromFuture).isEqualTo(secondSpan.context().spanId()); - } - finally { + } finally { secondSpan.end(); } log.info("Async in test with observation - after call"); - } - finally { + } finally { firstSpan.stop(); } - var tracerSpans = tracer.getSpans().stream().toList(); + var tracerSpans = tracer.getSpans() + .stream() + .toList(); assertThat(tracerSpans.size()).isEqualTo(2); assertThat(tracerSpans.get(1).getName()).isEqualTo(ASYNC_SPAN_NAME); @@ -73,17 +76,23 @@ public class TaskExecutionTracingTest { }); } + @Component static class AsyncLogic { + @Autowired Tracer tracer; + @Async("applicationTaskExecutor") public Future asyncCall() { + log.info("TASK EXECUTOR"); tracer.nextSpan().name(ASYNC_SPAN_NAME).tag("test", "test 3").start().end(); String spanId = tracer.currentSpan().context().spanId(); return CompletableFuture.supplyAsync(() -> spanId); } + } + }