Skip to content

Commit 46cb541

Browse files
lcianadinauer
andauthored
Refactor ReactorUtils into its own sentry-reactor module (#4155)
* Refactor `ReactorUtils` into its own `sentry-reactor` module * comment out failing tests * add dependency to sentry-samples-spring-boot-webflux-jakarta * fixes (#4160) * update * update * update * changelog * update * readme * Update README.md * Update CHANGELOG.md --------- Co-authored-by: Alexander Dinauer <adinauer@users.noreply.github.com>
1 parent 9180dc5 commit 46cb541

File tree

21 files changed

+239
-43
lines changed

21 files changed

+239
-43
lines changed

.github/ISSUE_TEMPLATE/bug_report_java.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ body:
3434
- sentry-openfeign
3535
- sentry-apache-http-client-5
3636
- sentry-okhttp
37+
- sentry-reactor
3738
- other
3839
validations:
3940
required: true

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Behavioural Changes
6+
7+
- The class `io.sentry.spring.jakarta.webflux.ReactorUtils` is now deprecated, please use `io.sentry.reactor.SentryReactorUtils` in the new `sentry-reactor` module instead ([#4155](https://github.com/getsentry/sentry-java/pull/4155))
8+
- The new module will be exposed as an `api` dependency when using `sentry-spring-boot-jakarta` (Spring Boot 3) or `sentry-spring-jakarta` (Spring 6).
9+
Therefore, if you're using one of those modules, changing your imports will suffice.
10+
311
## 8.2.0
412

513
### Breaking Changes

buildSrc/src/main/java/Config.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ object Config {
259259
val SENTRY_SERVLET_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.servlet.jakarta"
260260
val SENTRY_COMPOSE_HELPER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.compose.helper"
261261
val SENTRY_OKHTTP_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.okhttp"
262+
val SENTRY_REACTOR_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.reactor"
262263
val group = "io.sentry"
263264
val description = "SDK for sentry.io"
264265
val versionNameProp = "versionName"

sentry-reactor/README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# sentry-reactor
2+
3+
This module provides a set of utilities to use Sentry with [Reactor](https://projectreactor.io/).
4+
5+
## Setup
6+
7+
Please refer to the documentation on how to set up our [Java SDK](https://docs.sentry.io/platforms/java/),
8+
or our [Spring](https://docs.sentry.io/platforms/java/guides/spring/)
9+
or [Spring Boot](https://docs.sentry.io/platforms/java/guides/spring-boot/) integrations if you're using Spring WebFlux.
10+
11+
If you're using our Spring Boot SDK with Spring Boot (`sentry-spring-boot` or `sentry-spring-boot-jakarta`), this module will be available and used under the hood to automatically instrument WebFlux.
12+
If you're using our Spring SDK (`sentry-spring` or `sentry-spring-jakarta`), you need to configure WebFlux as we do in [SentryWebFluxAutoConfiguration](https://github.com/getsentry/sentry-java/blob/a5098280b52aec28c71c150e286b5c937767634d/sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryWebfluxAutoConfiguration.java) for Spring Boot.
13+
14+
Otherwise, read on to find out how to set up and use the integration.
15+
16+
Add the latest version of `io.sentry.reactor` as a dependency.
17+
Make sure you're using `io.micrometer:context-propagation:1.0.2` or later, and `io.projectreactor:reactor-core:3.5.3` or later.
18+
19+
Then, enable automatic context propagation:
20+
```java
21+
import reactor.core.publisher.Hooks;
22+
// ...
23+
Hooks.enableAutomaticContextPropagation();
24+
```
25+
26+
## Usage
27+
28+
You can use the utilities provided by this module to wrap `Mono` and `Flux` objects to enable correct errors, breadcrumbs and tracing in your application.
29+
30+
For normal use cases, you should wrap your operations on `Mono` or `Flux` objects using the `withSentry` function.
31+
This will fork the *current scopes* and use them throughout the stream's execution context.
32+
33+
For example:
34+
```java
35+
import reactor.core.publisher.Mono;
36+
import io.sentry.Sentry;
37+
import io.sentry.ISpan;
38+
import io.sentry.ITransaction;
39+
import io.sentry.TransactionOptions;
40+
41+
TransactionOptions txOptions = new TransactionOptions();
42+
txOptions.setBindToScope(true);
43+
ITransaction tx = Sentry.startTransaction("Transaction", "op", txOptions);
44+
ISpan child = tx.startChild("Outside Mono", "op")
45+
Sentry.captureMessage("Message outside Mono")
46+
child.finish()
47+
String result = SentryReactorUtils.withSentry(
48+
Mono.just("hello")
49+
.map({ (it) ->
50+
ISpan span = Sentry.getCurrentScopes().transaction.startChild("Inside Mono", "map");
51+
Sentry.captureMessage("Message inside Mono");
52+
span.finish();
53+
return it;
54+
})
55+
).block();
56+
System.out.println(result);
57+
tx.finish();
58+
```
59+
60+
For more complex use cases, you can also use `withSentryForkedRoots` to fork the root scopes or `withSentryScopes` to wrap the operation in arbitrary scopes.
61+
62+
For more information on scopes and scope forking, please consult our [scopes documentation](https://docs.sentry.io/platforms/java/enriching-events/scopes).
63+
64+
Examples of usage of this module (with Spring WebFlux) are provided in
65+
[sentry-samples-spring-boot-webflux](https://github.com/getsentry/sentry-java/tree/main/sentry-samples/sentry-samples-spring-boot-webflux)
66+
and
67+
[sentry-samples-spring-boot-webflux-jakarta](https://github.com/getsentry/sentry-java/tree/main/sentry-samples/sentry-samples-spring-boot-webflux-jakarta)
68+
.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
public final class io/sentry/reactor/BuildConfig {
2+
public static final field SENTRY_REACTOR_SDK_NAME Ljava/lang/String;
3+
public static final field VERSION_NAME Ljava/lang/String;
4+
}
5+
6+
public final class io/sentry/reactor/SentryReactorThreadLocalAccessor : io/micrometer/context/ThreadLocalAccessor {
7+
public static final field KEY Ljava/lang/String;
8+
public fun <init> ()V
9+
public fun getValue ()Lio/sentry/IScopes;
10+
public synthetic fun getValue ()Ljava/lang/Object;
11+
public fun key ()Ljava/lang/Object;
12+
public fun reset ()V
13+
public fun setValue (Lio/sentry/IScopes;)V
14+
public synthetic fun setValue (Ljava/lang/Object;)V
15+
}
16+
17+
public class io/sentry/reactor/SentryReactorUtils {
18+
public fun <init> ()V
19+
public static fun withSentry (Lreactor/core/publisher/Flux;)Lreactor/core/publisher/Flux;
20+
public static fun withSentry (Lreactor/core/publisher/Mono;)Lreactor/core/publisher/Mono;
21+
public static fun withSentryForkedRoots (Lreactor/core/publisher/Flux;)Lreactor/core/publisher/Flux;
22+
public static fun withSentryForkedRoots (Lreactor/core/publisher/Mono;)Lreactor/core/publisher/Mono;
23+
public static fun withSentryScopes (Lreactor/core/publisher/Flux;Lio/sentry/IScopes;)Lreactor/core/publisher/Flux;
24+
public static fun withSentryScopes (Lreactor/core/publisher/Mono;Lio/sentry/IScopes;)Lreactor/core/publisher/Mono;
25+
}
26+

sentry-reactor/build.gradle.kts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import net.ltgt.gradle.errorprone.errorprone
2+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3+
4+
plugins {
5+
`java-library`
6+
kotlin("jvm")
7+
jacoco
8+
id(Config.QualityPlugins.errorProne)
9+
id(Config.QualityPlugins.gradleVersions)
10+
id(Config.BuildPlugins.buildConfig) version Config.BuildPlugins.buildConfigVersion
11+
}
12+
13+
configure<JavaPluginExtension> {
14+
sourceCompatibility = JavaVersion.VERSION_17
15+
targetCompatibility = JavaVersion.VERSION_17
16+
}
17+
18+
tasks.withType<KotlinCompile>().configureEach {
19+
kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString()
20+
kotlinOptions.languageVersion = Config.kotlinCompatibleLanguageVersion
21+
}
22+
23+
dependencies {
24+
api(projects.sentry)
25+
compileOnly(Config.Libs.reactorCore)
26+
compileOnly(Config.Libs.contextPropagation)
27+
28+
compileOnly(Config.CompileOnly.nopen)
29+
errorprone(Config.CompileOnly.nopenChecker)
30+
errorprone(Config.CompileOnly.errorprone)
31+
errorprone(Config.CompileOnly.errorProneNullAway)
32+
compileOnly(Config.CompileOnly.jetbrainsAnnotations)
33+
34+
// tests
35+
testImplementation(projects.sentryTestSupport)
36+
testImplementation(kotlin(Config.kotlinStdLib))
37+
testImplementation(Config.TestLibs.kotlinTestJunit)
38+
testImplementation(Config.TestLibs.mockitoKotlin)
39+
40+
testImplementation(Config.Libs.reactorCore)
41+
testImplementation(Config.Libs.contextPropagation)
42+
43+
testImplementation(platform("org.junit:junit-bom:5.10.0"))
44+
testImplementation("org.junit.jupiter:junit-jupiter")
45+
}
46+
47+
configure<SourceSetContainer> {
48+
test {
49+
java.srcDir("src/test/java")
50+
}
51+
}
52+
53+
jacoco {
54+
toolVersion = Config.QualityPlugins.Jacoco.version
55+
}
56+
57+
tasks.jacocoTestReport {
58+
reports {
59+
xml.required.set(true)
60+
html.required.set(false)
61+
}
62+
}
63+
64+
tasks {
65+
jacocoTestCoverageVerification {
66+
violationRules {
67+
rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } }
68+
}
69+
}
70+
check {
71+
dependsOn(jacocoTestCoverageVerification)
72+
dependsOn(jacocoTestReport)
73+
}
74+
}
75+
76+
buildConfig {
77+
useJavaOutput()
78+
packageName("io.sentry.reactor")
79+
buildConfigField("String", "SENTRY_REACTOR_SDK_NAME", "\"${Config.Sentry.SENTRY_REACTOR_SDK_NAME}\"")
80+
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
81+
}
82+
83+
val generateBuildConfig by tasks
84+
tasks.withType<JavaCompile>().configureEach {
85+
dependsOn(generateBuildConfig)
86+
options.errorprone {
87+
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
88+
option("NullAway:AnnotatedPackages", "io.sentry")
89+
}
90+
}
91+
92+
repositories {
93+
mavenCentral()
94+
}

sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/webflux/SentryReactorThreadLocalAccessor.java renamed to sentry-reactor/src/main/java/io/sentry/reactor/SentryReactorThreadLocalAccessor.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
package io.sentry.spring.jakarta.webflux;
1+
package io.sentry.reactor;
22

33
import io.micrometer.context.ThreadLocalAccessor;
44
import io.sentry.IScopes;
55
import io.sentry.NoOpScopes;
66
import io.sentry.Sentry;
7-
import org.jetbrains.annotations.ApiStatus;
87

9-
@ApiStatus.Experimental
108
public final class SentryReactorThreadLocalAccessor implements ThreadLocalAccessor<IScopes> {
119

1210
public static final String KEY = "sentry-scopes";

sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/webflux/ReactorUtils.java renamed to sentry-reactor/src/main/java/io/sentry/reactor/SentryReactorUtils.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
package io.sentry.spring.jakarta.webflux;
1+
package io.sentry.reactor;
22

3+
import com.jakewharton.nopen.annotation.Open;
34
import io.sentry.IScopes;
45
import io.sentry.Sentry;
5-
import org.jetbrains.annotations.ApiStatus;
66
import org.jetbrains.annotations.NotNull;
77
import reactor.core.publisher.Flux;
88
import reactor.core.publisher.Mono;
99
import reactor.util.context.Context;
1010

11-
@ApiStatus.Experimental
12-
public final class ReactorUtils {
11+
@Open
12+
public class SentryReactorUtils {
1313

1414
/**
1515
* Writes the current Sentry {@link IScopes} to the {@link Context} and uses {@link
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.sentry.reactor.SentryReactorThreadLocalAccessor

sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/webflux/ReactorUtilsTest.kt renamed to sentry-reactor/src/test/kotlin/io/sentry/reactor/SentryReactorUtilsTest.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package io.sentry.spring.jakarta.webflux
1+
package io.sentry.reactor
22

33
import io.sentry.IScopes
44
import io.sentry.NoOpScopes
@@ -18,11 +18,12 @@ import kotlin.test.assertEquals
1818
import kotlin.test.assertNotSame
1919
import kotlin.test.assertSame
2020

21-
class ReactorUtilsTest {
21+
class SentryReactorUtilsTest {
2222

2323
@BeforeTest
2424
fun setup() {
2525
Hooks.enableAutomaticContextPropagation()
26+
Sentry.init("https://key@sentry.io/proj")
2627
}
2728

2829
@AfterTest
@@ -34,7 +35,7 @@ class ReactorUtilsTest {
3435
fun `propagates scopes inside mono`() {
3536
val scopesToUse = mock<IScopes>()
3637
var scopesInside: IScopes? = null
37-
val mono = ReactorUtils.withSentryScopes(
38+
val mono = SentryReactorUtils.withSentryScopes(
3839
Mono.just("hello")
3940
.publishOn(Schedulers.boundedElastic())
4041
.map { it ->
@@ -52,7 +53,7 @@ class ReactorUtilsTest {
5253
fun `propagates scopes inside flux`() {
5354
val scopesToUse = mock<IScopes>()
5455
var scopesInside: IScopes? = null
55-
val flux = ReactorUtils.withSentryScopes(
56+
val flux = SentryReactorUtils.withSentryScopes(
5657
Flux.just("hello")
5758
.publishOn(Schedulers.boundedElastic())
5859
.map { it ->
@@ -101,7 +102,7 @@ class ReactorUtilsTest {
101102
val mockScopes = mock<IScopes>()
102103
whenever(mockScopes.forkedCurrentScope(any())).thenReturn(mock<IScopes>())
103104
Sentry.setCurrentScopes(mockScopes)
104-
ReactorUtils.withSentry(Mono.just("hello")).block()
105+
SentryReactorUtils.withSentry(Mono.just("hello")).block()
105106

106107
verify(mockScopes).forkedCurrentScope(any())
107108
}
@@ -111,7 +112,7 @@ class ReactorUtilsTest {
111112
val mockScopes = mock<IScopes>()
112113
whenever(mockScopes.forkedCurrentScope(any())).thenReturn(mock<IScopes>())
113114
Sentry.setCurrentScopes(mockScopes)
114-
ReactorUtils.withSentry(Flux.just("hello")).blockFirst()
115+
SentryReactorUtils.withSentry(Flux.just("hello")).blockFirst()
115116

116117
verify(mockScopes).forkedCurrentScope(any())
117118
}

0 commit comments

Comments
 (0)