Skip to content

Commit

Permalink
Merge 1947daf into bd5f90e
Browse files Browse the repository at this point in the history
  • Loading branch information
adinauer committed Feb 27, 2023
2 parents bd5f90e + 1947daf commit a864e2f
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 13 deletions.
1 change: 1 addition & 0 deletions sentry-spring-jakarta/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ dependencies {
testImplementation(Config.Libs.springBoot3StarterWebflux)
testImplementation(Config.Libs.springBoot3StarterSecurity)
testImplementation(Config.Libs.springBoot3StarterAop)
testImplementation(Config.Libs.contextPropagation)
testImplementation(Config.TestLibs.awaitility)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,48 +13,101 @@
public final class ReactorUtils {

/**
* Writes the Sentry {@link IHub} to the {@link Context} and uses {@link io.micrometer.context.ThreadLocalAccessor} to propagate it.
* Writes the current Sentry {@link IHub} to the {@link Context} and uses {@link io.micrometer.context.ThreadLocalAccessor} to propagate it.
*
* This requires
* - reactor.core.publisher.Hooks#enableAutomaticContextPropagation() to be enabled
* - having `io.micrometer:context-propagation:1.0.2` or newer as dependency
* - having `io.projectreactor:reactor-core:3.5.3` or newer as dependency
* - having `io.micrometer:context-propagation:1.0.2+` (provided by Spring Boot 3.0.3+)
* - having `io.projectreactor:reactor-core:3.5.3+` (provided by Spring Boot 3.0.3+)
*/
@ApiStatus.Experimental
public static <T> Mono<T> withSentry(Mono<T> mono) {
public static <T> Mono<T> withSentry(final @NotNull Mono<T> mono) {
final @NotNull IHub oldHub = Sentry.getCurrentHub();
final @NotNull IHub clonedHub = oldHub.clone();
return withSentryHub(mono, clonedHub);
}

/**
* Writes a new Sentry {@link IHub} cloned from the main hub to the {@link Context} and uses {@link io.micrometer.context.ThreadLocalAccessor} to propagate it.
*
* This requires
* - reactor.core.publisher.Hooks#enableAutomaticContextPropagation() to be enabled
* - having `io.micrometer:context-propagation:1.0.2+` (provided by Spring Boot 3.0.3+)
* - having `io.projectreactor:reactor-core:3.5.3+` (provided by Spring Boot 3.0.3+)
*/
@ApiStatus.Experimental
public static <T> Mono<T> withFreshSentry(final @NotNull Mono<T> mono) {
final @NotNull IHub hub = Sentry.getNewHub();
return withSentryHub(mono, hub);
}

/**
* Writes the given Sentry {@link IHub} to the {@link Context} and uses {@link io.micrometer.context.ThreadLocalAccessor} to propagate it.
*
* This requires
* - reactor.core.publisher.Hooks#enableAutomaticContextPropagation() to be enabled
* - having `io.micrometer:context-propagation:1.0.2+` (provided by Spring Boot 3.0.3+)
* - having `io.projectreactor:reactor-core:3.5.3+` (provided by Spring Boot 3.0.3+)
*/
@ApiStatus.Experimental
public static <T> Mono<T> withSentryHub(final @NotNull Mono<T> mono, final @NotNull IHub hub) {
/**
* WARNING: Cannot set the clonedHub as current hub.
* WARNING: Cannot set the hub as current.
* It would be used by others to clone again causing shared hubs and scopes and thus
* leading to issues like unrelated breadcrumbs showing up in events.
*/
// Sentry.setCurrentHub(clonedHub);

return Mono.deferContextual(ctx -> mono).contextWrite(Context.of(SentryReactorThreadLocalAccessor.KEY, clonedHub));
return Mono.deferContextual(ctx -> mono).contextWrite(Context.of(SentryReactorThreadLocalAccessor.KEY, hub));
}

/**
* Writes the Sentry {@link IHub} to the {@link Context} and uses {@link io.micrometer.context.ThreadLocalAccessor} to propagate it.
* Writes the current Sentry {@link IHub} to the {@link Context} and uses {@link io.micrometer.context.ThreadLocalAccessor} to propagate it.
*
* This requires
* - reactor.core.publisher.Hooks#enableAutomaticContextPropagation() to be enabled
* - having `io.micrometer:context-propagation:1.0.2` or newer as dependency
* - having `io.projectreactor:reactor-core:3.5.3` or newer as dependency
* - having `io.micrometer:context-propagation:1.0.2+` (provided by Spring Boot 3.0.3+)
* - having `io.projectreactor:reactor-core:3.5.3+` (provided by Spring Boot 3.0.3+)
*/
@ApiStatus.Experimental
public static <T> Flux<T> withSentry(Flux<T> flux) {
public static <T> Flux<T> withSentry(final @NotNull Flux<T> flux) {
final @NotNull IHub oldHub = Sentry.getCurrentHub();
final @NotNull IHub clonedHub = oldHub.clone();

return withSentryHub(flux, clonedHub);
}

/**
* Writes a new Sentry {@link IHub} cloned from the main hub to the {@link Context} and uses {@link io.micrometer.context.ThreadLocalAccessor} to propagate it.
*
* This requires
* - reactor.core.publisher.Hooks#enableAutomaticContextPropagation() to be enabled
* - having `io.micrometer:context-propagation:1.0.2+` (provided by Spring Boot 3.0.3+)
* - having `io.projectreactor:reactor-core:3.5.3+` (provided by Spring Boot 3.0.3+)
*/
@ApiStatus.Experimental
public static <T> Flux<T> withFreshSentry(final @NotNull Flux<T> flux) {
final @NotNull IHub hub = Sentry.getNewHub();
return withSentryHub(flux, hub);
}

/**
* Writes the given Sentry {@link IHub} to the {@link Context} and uses {@link io.micrometer.context.ThreadLocalAccessor} to propagate it.
*
* This requires
* - reactor.core.publisher.Hooks#enableAutomaticContextPropagation() to be enabled
* - having `io.micrometer:context-propagation:1.0.2+` (provided by Spring Boot 3.0.3+)
* - having `io.projectreactor:reactor-core:3.5.3+` (provided by Spring Boot 3.0.3+)
*/
@ApiStatus.Experimental
public static <T> Flux<T> withSentryHub(final @NotNull Flux<T> flux, final @NotNull IHub hub) {
/**
* WARNING: Cannot set the clonedHub as current hub.
* WARNING: Cannot set the hub as current.
* It would be used by others to clone again causing shared hubs and scopes and thus
* leading to issues like unrelated breadcrumbs showing up in events.
*/
// Sentry.setCurrentHub(clonedHub);

return Flux.deferContextual(ctx -> flux).contextWrite(Context.of(SentryReactorThreadLocalAccessor.KEY, clonedHub));
return Flux.deferContextual(ctx -> flux).contextWrite(Context.of(SentryReactorThreadLocalAccessor.KEY, hub));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public SentryWebFilterWithThreadLocalAccessor(final @NotNull IHub hub) {
public Mono<Void> filter(
final @NotNull ServerWebExchange serverWebExchange,
final @NotNull WebFilterChain webFilterChain) {
return ReactorUtils.withSentry(super.filter(serverWebExchange, webFilterChain));
return ReactorUtils.withFreshSentry(super.filter(serverWebExchange, webFilterChain));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package io.sentry.spring.jakarta.webflux

import io.sentry.IHub
import io.sentry.NoOpHub
import io.sentry.Sentry
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotSame
import kotlin.test.assertSame
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import reactor.core.publisher.Flux
import reactor.core.publisher.Hooks

class ReactorUtilsTest {

@BeforeTest
fun setup() {
Hooks.enableAutomaticContextPropagation()
}

@AfterTest
fun teardown() {
Sentry.setCurrentHub(NoOpHub.getInstance());
}

@Test
fun `propagates hub inside mono`() {
val hubToUse = mock<IHub>()
var hubInside: IHub? = null
val mono = ReactorUtils.withSentryHub(
Mono.just("hello")
.publishOn(Schedulers.boundedElastic())
.map { it ->
hubInside = Sentry.getCurrentHub()
it
},
hubToUse
)

assertEquals("hello", mono.block())
assertSame(hubToUse, hubInside)
}
@Test
fun `propagates hub inside flux`() {
val hubToUse = mock<IHub>()
var hubInside: IHub? = null
val flux = ReactorUtils.withSentryHub(
Flux.just("hello")
.publishOn(Schedulers.boundedElastic())
.map { it ->
hubInside = Sentry.getCurrentHub()
it
},
hubToUse
)

assertEquals("hello", flux.blockFirst())
assertSame(hubToUse, hubInside)
}

@Test
fun `without reactive utils hub is not propagated to mono`() {
val hubToUse = mock<IHub>()
var hubInside: IHub? = null
val mono = Mono.just("hello")
.publishOn(Schedulers.boundedElastic())
.map { it ->
hubInside = Sentry.getCurrentHub()
it
}

assertEquals("hello", mono.block())
assertNotSame(hubToUse, hubInside)
}

@Test
fun `without reactive utils hub is not propagated to flux`() {
val hubToUse = mock<IHub>()
var hubInside: IHub? = null
val flux = Flux.just("hello")
.publishOn(Schedulers.boundedElastic())
.map { it ->
hubInside = Sentry.getCurrentHub()
it
}

assertEquals("hello", flux.blockFirst())
assertNotSame(hubToUse, hubInside)
}

@Test
fun `clones hub for mono`() {
val mockHub = mock<IHub>()
whenever(mockHub.clone()).thenReturn(mock<IHub>())
Sentry.setCurrentHub(mockHub)
ReactorUtils.withSentry(Mono.just("hello")).block()

verify(mockHub).clone()
}

@Test
fun `clones hub for flux`() {
val mockHub = mock<IHub>()
whenever(mockHub.clone()).thenReturn(mock<IHub>())
Sentry.setCurrentHub(mockHub)
ReactorUtils.withSentry(Flux.just("hello")).blockFirst()

verify(mockHub).clone()
}
}
1 change: 1 addition & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,7 @@ public final class io/sentry/Sentry {
public static fun flush (J)V
public static fun getCurrentHub ()Lio/sentry/IHub;
public static fun getLastEventId ()Lio/sentry/protocol/SentryId;
public static fun getNewHub ()Lio/sentry/IHub;
public static fun getSpan ()Lio/sentry/ISpan;
public static fun init ()V
public static fun init (Lio/sentry/OptionsContainer;Lio/sentry/Sentry$OptionsConfiguration;)V
Expand Down
11 changes: 11 additions & 0 deletions sentry/src/main/java/io/sentry/Sentry.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ private Sentry() {}
return hub;
}

/**
* Returns a new hub which is cloned from the mainHub.
*
* @return the hub
*/
@ApiStatus.Internal
@ApiStatus.Experimental
public static @NotNull IHub getNewHub() {
return mainHub.clone();
}

@ApiStatus.Internal // exposed for the coroutines integration in SentryContext
public static void setCurrentHub(final @NotNull IHub hub) {
currentHub.set(hub);
Expand Down

0 comments on commit a864e2f

Please sign in to comment.