diff --git a/buildSrc/src/main/kotlin/sb-ot-demo.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/sb-ot-demo.java-conventions.gradle.kts index 4a803830..a053984e 100644 --- a/buildSrc/src/main/kotlin/sb-ot-demo.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/sb-ot-demo.java-conventions.gradle.kts @@ -79,7 +79,7 @@ tasks { withType().configureEach { options.errorprone { disableWarningsInGeneratedCode.set(true) - disable("Slf4jLoggerShouldBeNonStatic") + disable("Slf4jLoggerShouldBeNonStatic", "BooleanLiteral") } } diff --git a/spring-boot-3-demo-app-kotlin/src/test/kotlin/io/github/mfvanek/spring/boot3/kotlin/test/NewTraceIdTest.kt b/spring-boot-3-demo-app-kotlin/src/test/kotlin/io/github/mfvanek/spring/boot3/kotlin/test/NewTraceIdTest.kt new file mode 100644 index 00000000..05225fac --- /dev/null +++ b/spring-boot-3-demo-app-kotlin/src/test/kotlin/io/github/mfvanek/spring/boot3/kotlin/test/NewTraceIdTest.kt @@ -0,0 +1,88 @@ +package io.github.mfvanek.spring.boot3.kotlin.test + +import io.github.mfvanek.spring.boot3.kotlin.test.support.TestBase +import io.micrometer.tracing.Tracer +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired + +internal class NewTraceIdTest : TestBase() { + + @Autowired + private lateinit var tracer: Tracer + + @Suppress("NestedBlockDepth") + @DisplayName("Demonstration of how to create a new traceId using the span API") + @Test + fun canCreateNewTraceIdViaSpan() { + val firstSpan = tracer.startScopedSpan("first") + try { + val previousTraceId = tracer.currentSpan()?.context()?.traceId() + assertThat(previousTraceId) + .isEqualTo(firstSpan.context().traceId()) + + tracer.withSpan(null).use { + val newSpan = tracer.nextSpan().name("new") + try { + tracer.withSpan(newSpan.start()).use { + val newTraceId = tracer.currentSpan()?.context()?.traceId() + assertThat(newTraceId) + .isNotEqualTo(previousTraceId) + } + } catch (e: Exception) { + newSpan.error(e) + throw e + } finally { + newSpan.end() + } + } + + val lastTraceId = tracer.currentSpan()?.context()?.traceId() + assertThat(lastTraceId) + .isEqualTo(previousTraceId) + } catch (e: Exception) { + firstSpan.error(e) + throw e + } finally { + firstSpan.end() + } + } + + @DisplayName("Demonstration of creating a new traceId using the API traceContext") + @Test + @Suppress("NestedBlockDepth") + fun canCreateNewTraceIdViaContext() { + val firstSpan = tracer.startScopedSpan("first") + try { + val previousTraceId = tracer.currentSpan()?.context()?.traceId() + assertThat(previousTraceId) + .isEqualTo(firstSpan.context().traceId()) + + tracer.withSpan(null).use { + val newSpan = tracer.nextSpan() + val traceContext = tracer.traceContextBuilder() + .traceId(newSpan.context().traceId()) + .spanId(newSpan.context().spanId()) + .sampled(true) // Important! + .build() + tracer.currentTraceContext().newScope( + traceContext + ).use { // Scope from newScope must be closed + val newTraceId = tracer.currentSpan()?.context()?.traceId() + assertThat(newTraceId) + .isNotEqualTo(previousTraceId) + } + } + + val lastTraceId = tracer.currentSpan()?.context()?.traceId() + assertThat(lastTraceId) + .isEqualTo(previousTraceId) + } catch (e: Exception) { + firstSpan.error(e) + throw e + } finally { + firstSpan.end() + } + } +} diff --git a/spring-boot-3-demo-app/src/test/java/io/github/mfvanek/spring/boot3/test/NewTraceIdTest.java b/spring-boot-3-demo-app/src/test/java/io/github/mfvanek/spring/boot3/test/NewTraceIdTest.java new file mode 100644 index 00000000..2197f911 --- /dev/null +++ b/spring-boot-3-demo-app/src/test/java/io/github/mfvanek/spring/boot3/test/NewTraceIdTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020-2025. Ivan Vakhrushev and others. + * https://github.com/mfvanek/spring-boot-open-telemetry-demo + * + * Licensed under the Apache License 2.0 + */ + +package io.github.mfvanek.spring.boot3.test; + +import io.github.mfvanek.spring.boot3.test.support.TestBase; +import io.micrometer.tracing.CurrentTraceContext; +import io.micrometer.tracing.ScopedSpan; +import io.micrometer.tracing.Span; +import io.micrometer.tracing.TraceContext; +import io.micrometer.tracing.Tracer; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Objects; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings({"PMD.AvoidCatchingThrowable", "PMD.ExceptionAsFlowControl", "checkstyle:NestedTryDepth", "checkstyle:IllegalCatch"}) +class NewTraceIdTest extends TestBase { + + @Autowired + private Tracer tracer; + + @Test + @DisplayName("Demonstration of how to create a new traceId using the span API") + void canCreateNewTraceIdViaSpan() { + final ScopedSpan firstSpan = tracer.startScopedSpan("first"); + try { + final String previousTraceId = Objects.requireNonNull(tracer.currentSpan()).context().traceId(); + assertThat(previousTraceId) + .isEqualTo(firstSpan.context().traceId()); + + try (Tracer.SpanInScope ignored = tracer.withSpan(null)) { + final Span newSpan = tracer.nextSpan().name("new"); + try (Tracer.SpanInScope ignored2 = tracer.withSpan(newSpan.start())) { + final String newTraceId = Objects.requireNonNull(tracer.currentSpan()).context().traceId(); + assertThat(newTraceId) + .isNotEqualTo(previousTraceId); + } catch (Throwable e) { + newSpan.error(e); + throw e; + } finally { + newSpan.end(); + } + } + + final String lastTraceId = Objects.requireNonNull(tracer.currentSpan()).context().traceId(); + assertThat(lastTraceId) + .isEqualTo(previousTraceId); + } catch (Throwable e) { + firstSpan.error(e); + throw e; + } finally { + firstSpan.end(); + } + } + + @Test + @DisplayName("Demonstration of creating a new traceId using the API traceContext") + void canCreateNewTraceIdViaContext() { + final ScopedSpan firstSpan = tracer.startScopedSpan("first"); + try { + final String previousTraceId = Objects.requireNonNull(tracer.currentSpan()).context().traceId(); + assertThat(previousTraceId) + .isEqualTo(firstSpan.context().traceId()); + + try (Tracer.SpanInScope ignored = tracer.withSpan(null)) { + final Span newSpan = tracer.nextSpan(); + final TraceContext traceContext = tracer.traceContextBuilder() + .traceId(newSpan.context().traceId()) + .spanId(newSpan.context().spanId()) + .sampled(Boolean.TRUE) // Important! + .build(); + + try (CurrentTraceContext.Scope unused = tracer.currentTraceContext().newScope(traceContext)) { + final String newTraceId = Objects.requireNonNull(tracer.currentSpan()).context().traceId(); + assertThat(newTraceId) + .isNotEqualTo(previousTraceId); + } + } + + final String lastTraceId = Objects.requireNonNull(tracer.currentSpan()).context().traceId(); + assertThat(lastTraceId) + .isEqualTo(previousTraceId); + } catch (Throwable e) { + firstSpan.error(e); + throw e; + } finally { + firstSpan.end(); + } + } +}