Skip to content

Commit cc386a9

Browse files
authored
Merge 504a195 into 46b1782
2 parents 46b1782 + 504a195 commit cc386a9

File tree

19 files changed

+551
-527
lines changed

19 files changed

+551
-527
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- Base64 encode internal Apollo3 Headers ([#2707](https://github.com/getsentry/sentry-java/pull/2707))
8+
- Finish WebFlux transaction before popping scope ([#2724](https://github.com/getsentry/sentry-java/pull/2724))
89

910
## 6.19.1
1011

sentry-spring-boot-starter-jakarta/api/sentry-spring-boot-starter-jakarta.api

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,5 @@ public class io/sentry/spring/boot/jakarta/SentryProperties$Reactive {
5353
public class io/sentry/spring/boot/jakarta/SentryWebfluxAutoConfiguration {
5454
public fun <init> ()V
5555
public fun sentryWebExceptionHandler (Lio/sentry/IHub;)Lio/sentry/spring/jakarta/webflux/SentryWebExceptionHandler;
56-
public fun sentryWebTracingFilter ()Lio/sentry/spring/jakarta/webflux/SentryWebTracingFilter;
5756
}
5857

sentry-spring-boot-starter-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryWebfluxAutoConfiguration.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@
66
import io.sentry.spring.jakarta.webflux.SentryWebExceptionHandler;
77
import io.sentry.spring.jakarta.webflux.SentryWebFilter;
88
import io.sentry.spring.jakarta.webflux.SentryWebFilterWithThreadLocalAccessor;
9-
import io.sentry.spring.jakarta.webflux.SentryWebTracingFilter;
109
import org.jetbrains.annotations.ApiStatus;
1110
import org.jetbrains.annotations.NotNull;
1211
import org.springframework.boot.ApplicationRunner;
1312
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
1413
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
1514
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
1615
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
17-
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
1816
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
1917
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2018
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
@@ -77,14 +75,6 @@ static class SentryWebfluxFilterConfiguration {
7775
}
7876
}
7977

80-
@Bean
81-
@Order(SENTRY_SPRING_FILTER_PRECEDENCE + 1)
82-
@Conditional(SentryAutoConfiguration.SentryTracingCondition.class)
83-
@ConditionalOnMissingBean(name = "sentryWebTracingFilter")
84-
public @NotNull SentryWebTracingFilter sentryWebTracingFilter() {
85-
return new SentryWebTracingFilter();
86-
}
87-
8878
/** Configures exception handler that handles unhandled exceptions and sends them to Sentry. */
8979
@Bean
9080
public @NotNull SentryWebExceptionHandler sentryWebExceptionHandler(final @NotNull IHub hub) {

sentry-spring-boot-starter/api/sentry-spring-boot-starter.api

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,5 @@ public class io/sentry/spring/boot/SentryWebfluxAutoConfiguration {
4747
public fun sentryScheduleHookApplicationRunner ()Lorg/springframework/boot/ApplicationRunner;
4848
public fun sentryWebExceptionHandler (Lio/sentry/IHub;)Lio/sentry/spring/webflux/SentryWebExceptionHandler;
4949
public fun sentryWebFilter (Lio/sentry/IHub;)Lio/sentry/spring/webflux/SentryWebFilter;
50-
public fun sentryWebTracingFilter ()Lio/sentry/spring/webflux/SentryWebTracingFilter;
5150
}
5251

sentry-spring-boot-starter/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@
55
import io.sentry.spring.webflux.SentryScheduleHook;
66
import io.sentry.spring.webflux.SentryWebExceptionHandler;
77
import io.sentry.spring.webflux.SentryWebFilter;
8-
import io.sentry.spring.webflux.SentryWebTracingFilter;
98
import org.jetbrains.annotations.ApiStatus;
109
import org.jetbrains.annotations.NotNull;
1110
import org.springframework.boot.ApplicationRunner;
1211
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
1312
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
14-
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
1513
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
1614
import org.springframework.context.annotation.Bean;
17-
import org.springframework.context.annotation.Conditional;
1815
import org.springframework.context.annotation.Configuration;
1916
import org.springframework.core.Ordered;
2017
import org.springframework.core.annotation.Order;
@@ -45,14 +42,6 @@ public class SentryWebfluxAutoConfiguration {
4542
return new SentryWebFilter(hub);
4643
}
4744

48-
@Bean
49-
@Order(SENTRY_SPRING_FILTER_PRECEDENCE + 1)
50-
@Conditional(SentryAutoConfiguration.SentryTracingCondition.class)
51-
@ConditionalOnMissingBean(name = "sentryWebTracingFilter")
52-
public @NotNull SentryWebTracingFilter sentryWebTracingFilter() {
53-
return new SentryWebTracingFilter();
54-
}
55-
5645
/** Configures exception handler that handles unhandled exceptions and sends them to Sentry. */
5746
@Bean
5847
public @NotNull SentryWebExceptionHandler sentryWebExceptionHandler(final @NotNull IHub hub) {

sentry-spring-jakarta/api/sentry-spring-jakarta.api

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,12 @@ public abstract interface class io/sentry/spring/jakarta/tracing/TransactionName
163163
public abstract class io/sentry/spring/jakarta/webflux/AbstractSentryWebFilter : org/springframework/web/server/WebFilter {
164164
public static final field SENTRY_HUB_KEY Ljava/lang/String;
165165
public fun <init> (Lio/sentry/IHub;)V
166-
protected fun doFinally (Lio/sentry/IHub;)V
166+
protected fun doFinally (Lorg/springframework/web/server/ServerWebExchange;Lio/sentry/IHub;Lio/sentry/ITransaction;)V
167167
protected fun doFirst (Lorg/springframework/web/server/ServerWebExchange;Lio/sentry/IHub;)V
168+
protected fun doOnError (Lio/sentry/ITransaction;Ljava/lang/Throwable;)V
169+
protected fun maybeStartTransaction (Lio/sentry/IHub;Lorg/springframework/http/server/reactive/ServerHttpRequest;)Lio/sentry/ITransaction;
170+
protected fun shouldTraceRequest (Lio/sentry/IHub;Lorg/springframework/http/server/reactive/ServerHttpRequest;)Z
171+
protected fun startTransaction (Lio/sentry/IHub;Lorg/springframework/http/server/reactive/ServerHttpRequest;)Lio/sentry/ITransaction;
168172
}
169173

170174
public final class io/sentry/spring/jakarta/webflux/ReactorUtils {
@@ -215,8 +219,3 @@ public final class io/sentry/spring/jakarta/webflux/SentryWebFilterWithThreadLoc
215219
public fun filter (Lorg/springframework/web/server/ServerWebExchange;Lorg/springframework/web/server/WebFilterChain;)Lreactor/core/publisher/Mono;
216220
}
217221

218-
public class io/sentry/spring/jakarta/webflux/SentryWebTracingFilter : org/springframework/web/server/WebFilter {
219-
public fun <init> ()V
220-
public fun filter (Lorg/springframework/web/server/ServerWebExchange;Lorg/springframework/web/server/WebFilterChain;)Lreactor/core/publisher/Mono;
221-
}
222-

sentry-spring-jakarta/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ dependencies {
4747
testImplementation(kotlin(Config.kotlinStdLib))
4848
testImplementation(Config.TestLibs.kotlinTestJunit)
4949
testImplementation(Config.TestLibs.mockitoKotlin)
50+
testImplementation(Config.TestLibs.mockitoInline)
5051
testImplementation(Config.Libs.springBoot3StarterTest)
5152
testImplementation(Config.Libs.springBoot3StarterWeb)
5253
testImplementation(Config.Libs.springBoot3StarterWebflux)

sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/webflux/AbstractSentryWebFilter.java

Lines changed: 127 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,30 @@
33
import static io.sentry.TypeCheckHint.WEBFLUX_FILTER_REQUEST;
44
import static io.sentry.TypeCheckHint.WEBFLUX_FILTER_RESPONSE;
55

6+
import io.sentry.Baggage;
7+
import io.sentry.BaggageHeader;
68
import io.sentry.Breadcrumb;
9+
import io.sentry.CustomSamplingContext;
710
import io.sentry.Hint;
811
import io.sentry.IHub;
12+
import io.sentry.ITransaction;
13+
import io.sentry.NoOpHub;
14+
import io.sentry.Sentry;
15+
import io.sentry.SentryLevel;
16+
import io.sentry.SentryTraceHeader;
17+
import io.sentry.SpanStatus;
18+
import io.sentry.TransactionContext;
19+
import io.sentry.TransactionOptions;
20+
import io.sentry.exception.InvalidSentryTraceHeaderException;
21+
import io.sentry.protocol.TransactionNameSource;
922
import io.sentry.util.Objects;
23+
import java.util.List;
1024
import org.jetbrains.annotations.ApiStatus;
1125
import org.jetbrains.annotations.NotNull;
26+
import org.jetbrains.annotations.Nullable;
27+
import org.springframework.http.HttpHeaders;
28+
import org.springframework.http.HttpMethod;
29+
import org.springframework.http.HttpStatusCode;
1230
import org.springframework.http.server.reactive.ServerHttpRequest;
1331
import org.springframework.http.server.reactive.ServerHttpResponse;
1432
import org.springframework.web.server.ServerWebExchange;
@@ -19,29 +37,124 @@
1937
public abstract class AbstractSentryWebFilter implements WebFilter {
2038
private final @NotNull SentryRequestResolver sentryRequestResolver;
2139
public static final String SENTRY_HUB_KEY = "sentry-hub";
40+
private static final String TRANSACTION_OP = "http.server";
2241

2342
public AbstractSentryWebFilter(final @NotNull IHub hub) {
2443
Objects.requireNonNull(hub, "hub is required");
2544
this.sentryRequestResolver = new SentryRequestResolver(hub);
2645
}
2746

28-
protected void doFinally(final @NotNull IHub requestHub) {
29-
requestHub.popScope();
47+
protected @Nullable ITransaction maybeStartTransaction(
48+
final @NotNull IHub requestHub, final @NotNull ServerHttpRequest request) {
49+
if (requestHub.isEnabled()
50+
&& requestHub.getOptions().isTracingEnabled()
51+
&& shouldTraceRequest(requestHub, request)) {
52+
return startTransaction(requestHub, request);
53+
} else {
54+
return null;
55+
}
56+
}
57+
58+
protected void doFinally(
59+
final @NotNull ServerWebExchange serverWebExchange,
60+
final @NotNull IHub requestHub,
61+
final @Nullable ITransaction transaction) {
62+
if (transaction != null) {
63+
finishTransaction(serverWebExchange, transaction);
64+
}
65+
if (requestHub.isEnabled()) {
66+
requestHub.popScope();
67+
}
68+
Sentry.setCurrentHub(NoOpHub.getInstance());
3069
}
3170

3271
protected void doFirst(
3372
final @NotNull ServerWebExchange serverWebExchange, final @NotNull IHub requestHub) {
34-
serverWebExchange.getAttributes().put(SENTRY_HUB_KEY, requestHub);
35-
requestHub.pushScope();
36-
final ServerHttpRequest request = serverWebExchange.getRequest();
37-
final ServerHttpResponse response = serverWebExchange.getResponse();
38-
39-
final Hint hint = new Hint();
40-
hint.set(WEBFLUX_FILTER_REQUEST, request);
41-
hint.set(WEBFLUX_FILTER_RESPONSE, response);
42-
final String methodName = request.getMethod() != null ? request.getMethod().name() : "unknown";
43-
requestHub.addBreadcrumb(Breadcrumb.http(request.getURI().toString(), methodName), hint);
44-
requestHub.configureScope(
45-
scope -> scope.setRequest(sentryRequestResolver.resolveSentryRequest(request)));
73+
if (requestHub.isEnabled()) {
74+
serverWebExchange.getAttributes().put(SENTRY_HUB_KEY, requestHub);
75+
requestHub.pushScope();
76+
final ServerHttpRequest request = serverWebExchange.getRequest();
77+
final ServerHttpResponse response = serverWebExchange.getResponse();
78+
79+
final Hint hint = new Hint();
80+
hint.set(WEBFLUX_FILTER_REQUEST, request);
81+
hint.set(WEBFLUX_FILTER_RESPONSE, response);
82+
final String methodName =
83+
request.getMethod() != null ? request.getMethod().name() : "unknown";
84+
requestHub.addBreadcrumb(Breadcrumb.http(request.getURI().toString(), methodName), hint);
85+
requestHub.configureScope(
86+
scope -> scope.setRequest(sentryRequestResolver.resolveSentryRequest(request)));
87+
}
88+
}
89+
90+
protected void doOnError(final @Nullable ITransaction transaction, final @NotNull Throwable e) {
91+
if (transaction != null) {
92+
transaction.setStatus(SpanStatus.INTERNAL_ERROR);
93+
transaction.setThrowable(e);
94+
}
95+
}
96+
97+
protected boolean shouldTraceRequest(
98+
final @NotNull IHub hub, final @NotNull ServerHttpRequest request) {
99+
return hub.getOptions().isTraceOptionsRequests()
100+
|| !HttpMethod.OPTIONS.equals(request.getMethod());
101+
}
102+
103+
private void finishTransaction(ServerWebExchange exchange, ITransaction transaction) {
104+
String transactionName = TransactionNameProvider.provideTransactionName(exchange);
105+
if (transactionName != null) {
106+
transaction.setName(transactionName, TransactionNameSource.ROUTE);
107+
transaction.setOperation(TRANSACTION_OP);
108+
}
109+
if (transaction.getStatus() == null) {
110+
final @Nullable ServerHttpResponse response = exchange.getResponse();
111+
if (response != null) {
112+
final @Nullable HttpStatusCode statusCode = response.getStatusCode();
113+
if (statusCode != null) {
114+
transaction.setStatus(SpanStatus.fromHttpStatusCode(statusCode.value()));
115+
}
116+
}
117+
}
118+
transaction.finish();
119+
}
120+
121+
protected @NotNull ITransaction startTransaction(
122+
final @NotNull IHub hub, final @NotNull ServerHttpRequest request) {
123+
final @NotNull HttpHeaders headers = request.getHeaders();
124+
final @Nullable List<String> sentryTraceHeaders =
125+
headers.get(SentryTraceHeader.SENTRY_TRACE_HEADER);
126+
final @Nullable List<String> baggageHeaders = headers.get(BaggageHeader.BAGGAGE_HEADER);
127+
final @NotNull String name = request.getMethod() + " " + request.getURI().getPath();
128+
final @NotNull CustomSamplingContext customSamplingContext = new CustomSamplingContext();
129+
customSamplingContext.set("request", request);
130+
131+
final TransactionOptions transactionOptions = new TransactionOptions();
132+
transactionOptions.setCustomSamplingContext(customSamplingContext);
133+
transactionOptions.setBindToScope(true);
134+
135+
if (sentryTraceHeaders != null && sentryTraceHeaders.size() > 0) {
136+
final @NotNull Baggage baggage =
137+
Baggage.fromHeader(baggageHeaders, hub.getOptions().getLogger());
138+
try {
139+
final @NotNull TransactionContext contexts =
140+
TransactionContext.fromSentryTrace(
141+
name,
142+
TransactionNameSource.URL,
143+
TRANSACTION_OP,
144+
new SentryTraceHeader(sentryTraceHeaders.get(0)),
145+
baggage,
146+
null);
147+
148+
return hub.startTransaction(contexts, transactionOptions);
149+
} catch (InvalidSentryTraceHeaderException e) {
150+
hub.getOptions()
151+
.getLogger()
152+
.log(SentryLevel.DEBUG, e, "Failed to parse Sentry trace header: %s", e.getMessage());
153+
}
154+
}
155+
156+
return hub.startTransaction(
157+
new TransactionContext(name, TransactionNameSource.URL, TRANSACTION_OP),
158+
transactionOptions);
46159
}
47160
}

sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/webflux/SentryWebFilter.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
import com.jakewharton.nopen.annotation.Open;
44
import io.sentry.IHub;
5-
import io.sentry.NoOpHub;
5+
import io.sentry.ITransaction;
66
import io.sentry.Sentry;
77
import org.jetbrains.annotations.ApiStatus;
88
import org.jetbrains.annotations.NotNull;
9+
import org.jetbrains.annotations.Nullable;
10+
import org.springframework.http.server.reactive.ServerHttpRequest;
911
import org.springframework.web.server.ServerWebExchange;
1012
import org.springframework.web.server.WebFilterChain;
1113
import reactor.core.publisher.Mono;
@@ -24,13 +26,12 @@ public Mono<Void> filter(
2426
final @NotNull ServerWebExchange serverWebExchange,
2527
final @NotNull WebFilterChain webFilterChain) {
2628
@NotNull IHub requestHub = Sentry.cloneMainHub();
29+
final ServerHttpRequest request = serverWebExchange.getRequest();
30+
final @Nullable ITransaction transaction = maybeStartTransaction(requestHub, request);
2731
return webFilterChain
2832
.filter(serverWebExchange)
29-
.doFinally(
30-
__ -> {
31-
doFinally(requestHub);
32-
Sentry.setCurrentHub(NoOpHub.getInstance());
33-
})
33+
.doFinally(__ -> doFinally(serverWebExchange, requestHub, transaction))
34+
.doOnError(e -> doOnError(transaction, e))
3435
.doFirst(
3536
() -> {
3637
Sentry.setCurrentHub(requestHub);

sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/webflux/SentryWebFilterWithThreadLocalAccessor.java

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

33
import io.sentry.IHub;
4-
import io.sentry.NoOpHub;
4+
import io.sentry.ITransaction;
55
import io.sentry.Sentry;
66
import org.jetbrains.annotations.ApiStatus;
77
import org.jetbrains.annotations.NotNull;
8+
import org.jetbrains.annotations.Nullable;
89
import org.springframework.web.server.ServerWebExchange;
910
import org.springframework.web.server.WebFilterChain;
1011
import reactor.core.publisher.Mono;
@@ -21,17 +22,26 @@ public SentryWebFilterWithThreadLocalAccessor(final @NotNull IHub hub) {
2122
public Mono<Void> filter(
2223
final @NotNull ServerWebExchange serverWebExchange,
2324
final @NotNull WebFilterChain webFilterChain) {
25+
final @NotNull TransactionContainer transactionContainer = new TransactionContainer();
2426
return ReactorUtils.withSentryNewMainHubClone(
2527
webFilterChain
2628
.filter(serverWebExchange)
2729
.doFinally(
28-
__ -> {
29-
doFinally(Sentry.getCurrentHub());
30-
Sentry.setCurrentHub(NoOpHub.getInstance());
31-
})
30+
__ ->
31+
doFinally(
32+
serverWebExchange,
33+
Sentry.getCurrentHub(),
34+
transactionContainer.transaction))
35+
.doOnError(e -> doOnError(transactionContainer.transaction, e))
3236
.doFirst(
3337
() -> {
3438
doFirst(serverWebExchange, Sentry.getCurrentHub());
39+
transactionContainer.transaction =
40+
maybeStartTransaction(Sentry.getCurrentHub(), serverWebExchange.getRequest());
3541
}));
3642
}
43+
44+
private static class TransactionContainer {
45+
private volatile @Nullable ITransaction transaction;
46+
}
3747
}

0 commit comments

Comments
 (0)