Skip to content

Commit 64d302b

Browse files
Use SpringServletTransactionNameProvider as fallback for Spring WebMVC (#4263)
* Assume http.client for span op if not a root span * changelog * Use SpringServletTransactionNameProvider as fallback * changelog * review changes * Format code --------- Co-authored-by: Sentry Github Bot <[email protected]>
1 parent 6be3488 commit 64d302b

File tree

15 files changed

+241
-8
lines changed

15 files changed

+241
-8
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
- Reduce excessive CPU usage when serializing breadcrumbs to disk for ANRs ([#4181](https://github.com/getsentry/sentry-java/pull/4181))
1313
- Ensure app start type is set, even when ActivityLifecycleIntegration is not running ([#4250](https://github.com/getsentry/sentry-java/pull/4250))
14+
- Use `SpringServletTransactionNameProvider` as fallback for Spring WebMVC ([#4263](https://github.com/getsentry/sentry-java/pull/4263))
15+
- In certain cases the SDK was not able to provide a transaction name automatically and thus did not finish the transaction for the request.
16+
- We now first try `SpringMvcTransactionNameProvider` which would provide the route as transaction name.
17+
- If that does not return anything, we try `SpringServletTransactionNameProvider` next, which returns the URL of the request.
1418

1519
### Behavioral Changes
1620

sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import io.sentry.spring.jakarta.exception.SentryExceptionParameterAdviceConfiguration;
3333
import io.sentry.spring.jakarta.opentelemetry.SentryOpenTelemetryAgentWithoutAutoInitConfiguration;
3434
import io.sentry.spring.jakarta.opentelemetry.SentryOpenTelemetryNoAgentConfiguration;
35+
import io.sentry.spring.jakarta.tracing.CombinedTransactionNameProvider;
3536
import io.sentry.spring.jakarta.tracing.SentryAdviceConfiguration;
3637
import io.sentry.spring.jakarta.tracing.SentrySpanPointcutConfiguration;
3738
import io.sentry.spring.jakarta.tracing.SentryTracingFilter;
@@ -42,6 +43,7 @@
4243
import io.sentry.transport.ITransportGate;
4344
import io.sentry.transport.apache.ApacheHttpClientTransportFactory;
4445
import jakarta.servlet.http.HttpServletRequest;
46+
import java.util.Arrays;
4547
import java.util.List;
4648
import java.util.Optional;
4749
import org.aspectj.lang.ProceedingJoinPoint;
@@ -342,7 +344,10 @@ static class SentryMvcModeConfig {
342344
@Bean
343345
@ConditionalOnMissingBean(TransactionNameProvider.class)
344346
public @NotNull TransactionNameProvider transactionNameProvider() {
345-
return new SpringMvcTransactionNameProvider();
347+
return new CombinedTransactionNameProvider(
348+
Arrays.asList(
349+
new SpringMvcTransactionNameProvider(),
350+
new SpringServletTransactionNameProvider()));
346351
}
347352
}
348353

sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import io.sentry.spring.exception.SentryExceptionParameterAdviceConfiguration;
3333
import io.sentry.spring.opentelemetry.SentryOpenTelemetryAgentWithoutAutoInitConfiguration;
3434
import io.sentry.spring.opentelemetry.SentryOpenTelemetryNoAgentConfiguration;
35+
import io.sentry.spring.tracing.CombinedTransactionNameProvider;
3536
import io.sentry.spring.tracing.SentryAdviceConfiguration;
3637
import io.sentry.spring.tracing.SentrySpanPointcutConfiguration;
3738
import io.sentry.spring.tracing.SentryTracingFilter;
@@ -41,6 +42,7 @@
4142
import io.sentry.spring.tracing.TransactionNameProvider;
4243
import io.sentry.transport.ITransportGate;
4344
import io.sentry.transport.apache.ApacheHttpClientTransportFactory;
45+
import java.util.Arrays;
4446
import java.util.List;
4547
import java.util.Optional;
4648
import javax.servlet.http.HttpServletRequest;
@@ -327,7 +329,10 @@ static class SentryMvcModeConfig {
327329
@Bean
328330
@ConditionalOnMissingBean(TransactionNameProvider.class)
329331
public @NotNull TransactionNameProvider transactionNameProvider() {
330-
return new SpringMvcTransactionNameProvider();
332+
return new CombinedTransactionNameProvider(
333+
Arrays.asList(
334+
new SpringMvcTransactionNameProvider(),
335+
new SpringServletTransactionNameProvider()));
331336
}
332337
}
333338

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,13 @@ public class io/sentry/spring/jakarta/opentelemetry/SentryOpenTelemetryNoAgentCo
220220
public fun sentryOpenTelemetryOptionsConfiguration ()Lio/sentry/Sentry$OptionsConfiguration;
221221
}
222222

223+
public final class io/sentry/spring/jakarta/tracing/CombinedTransactionNameProvider : io/sentry/spring/jakarta/tracing/TransactionNameProvider {
224+
public fun <init> (Ljava/util/List;)V
225+
public fun provideTransactionName (Ljakarta/servlet/http/HttpServletRequest;)Ljava/lang/String;
226+
public fun provideTransactionNameAndSource (Ljakarta/servlet/http/HttpServletRequest;)Lio/sentry/spring/jakarta/tracing/TransactionNameWithSource;
227+
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
228+
}
229+
223230
public class io/sentry/spring/jakarta/tracing/SentryAdviceConfiguration {
224231
public fun <init> ()V
225232
public fun sentrySpanAdvice ()Lorg/aopalliance/aop/Advice;
@@ -300,9 +307,16 @@ public final class io/sentry/spring/jakarta/tracing/SpringServletTransactionName
300307

301308
public abstract interface class io/sentry/spring/jakarta/tracing/TransactionNameProvider {
302309
public abstract fun provideTransactionName (Ljakarta/servlet/http/HttpServletRequest;)Ljava/lang/String;
310+
public fun provideTransactionNameAndSource (Ljakarta/servlet/http/HttpServletRequest;)Lio/sentry/spring/jakarta/tracing/TransactionNameWithSource;
303311
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
304312
}
305313

314+
public final class io/sentry/spring/jakarta/tracing/TransactionNameWithSource {
315+
public fun <init> (Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V
316+
public fun getTransactionName ()Ljava/lang/String;
317+
public fun getTransactionNameSource ()Lio/sentry/protocol/TransactionNameSource;
318+
}
319+
306320
public abstract class io/sentry/spring/jakarta/webflux/AbstractSentryWebFilter : org/springframework/web/server/WebFilter {
307321
public static final field SENTRY_HUB_KEY Ljava/lang/String;
308322
public static final field SENTRY_SCOPES_KEY Ljava/lang/String;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.sentry.spring.jakarta.tracing;
2+
3+
import io.sentry.protocol.TransactionNameSource;
4+
import jakarta.servlet.http.HttpServletRequest;
5+
import java.util.List;
6+
import org.jetbrains.annotations.ApiStatus;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.jetbrains.annotations.Nullable;
9+
10+
/**
11+
* Resolves transaction name using other transaction name providers by invoking them in order. If a
12+
* provider returns no transaction name, the next one is invoked.
13+
*/
14+
@ApiStatus.Internal
15+
public final class CombinedTransactionNameProvider implements TransactionNameProvider {
16+
17+
private final @NotNull List<TransactionNameProvider> providers;
18+
19+
public CombinedTransactionNameProvider(final @NotNull List<TransactionNameProvider> providers) {
20+
this.providers = providers;
21+
}
22+
23+
@Override
24+
public @Nullable String provideTransactionName(@NotNull HttpServletRequest request) {
25+
for (TransactionNameProvider provider : providers) {
26+
String transactionName = provider.provideTransactionName(request);
27+
if (transactionName != null) {
28+
return transactionName;
29+
}
30+
}
31+
32+
return null;
33+
}
34+
35+
@Override
36+
@ApiStatus.Internal
37+
public @NotNull TransactionNameSource provideTransactionSource() {
38+
return TransactionNameSource.CUSTOM;
39+
}
40+
41+
@ApiStatus.Internal
42+
@Override
43+
public @NotNull TransactionNameWithSource provideTransactionNameAndSource(
44+
@NotNull HttpServletRequest request) {
45+
for (TransactionNameProvider provider : providers) {
46+
String transactionName = provider.provideTransactionName(request);
47+
if (transactionName != null) {
48+
final @NotNull TransactionNameSource source = provider.provideTransactionSource();
49+
return new TransactionNameWithSource(transactionName, source);
50+
}
51+
}
52+
53+
return new TransactionNameWithSource(null, TransactionNameSource.CUSTOM);
54+
}
55+
}

sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/tracing/SentryTracingFilter.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,11 @@ private void doFilterWithTransaction(
145145
} finally {
146146
if (shouldFinishTransaction(httpRequest) && transaction != null) {
147147
// after all filters run, templated path pattern is available in request attribute
148-
final String transactionName = transactionNameProvider.provideTransactionName(httpRequest);
149-
final TransactionNameSource transactionNameSource =
150-
transactionNameProvider.provideTransactionSource();
148+
final @NotNull TransactionNameWithSource transactionNameWithSource =
149+
transactionNameProvider.provideTransactionNameAndSource(httpRequest);
150+
final @Nullable String transactionName = transactionNameWithSource.getTransactionName();
151+
final @NotNull TransactionNameSource transactionNameSource =
152+
transactionNameWithSource.getTransactionNameSource();
151153
// if transaction name is not resolved, the request has not been processed by a controller
152154
// and we should not report it to Sentry
153155
if (transactionName != null) {

sentry-spring-jakarta/src/main/java/io/sentry/spring/jakarta/tracing/TransactionNameProvider.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,12 @@ public interface TransactionNameProvider {
2727
default TransactionNameSource provideTransactionSource() {
2828
return TransactionNameSource.CUSTOM;
2929
}
30+
31+
@NotNull
32+
@ApiStatus.Internal
33+
default TransactionNameWithSource provideTransactionNameAndSource(
34+
final @NotNull HttpServletRequest request) {
35+
return new TransactionNameWithSource(
36+
provideTransactionName(request), provideTransactionSource());
37+
}
3038
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.sentry.spring.jakarta.tracing;
2+
3+
import io.sentry.protocol.TransactionNameSource;
4+
import org.jetbrains.annotations.ApiStatus;
5+
import org.jetbrains.annotations.NotNull;
6+
import org.jetbrains.annotations.Nullable;
7+
8+
@ApiStatus.Internal
9+
public final class TransactionNameWithSource {
10+
private final @Nullable String transactionName;
11+
private final @NotNull TransactionNameSource transactionNameSource;
12+
13+
public TransactionNameWithSource(
14+
final @Nullable String transactionName,
15+
final @NotNull TransactionNameSource transactionNameSource) {
16+
this.transactionName = transactionName;
17+
this.transactionNameSource = transactionNameSource;
18+
}
19+
20+
public @Nullable String getTransactionName() {
21+
return transactionName;
22+
}
23+
24+
public @NotNull TransactionNameSource getTransactionNameSource() {
25+
return transactionNameSource;
26+
}
27+
}

sentry-spring-jakarta/src/test/kotlin/io/sentry/spring/jakarta/tracing/SentryTracingFilterTest.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ class SentryTracingFilterTest {
6565
request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/product/{id}")
6666
whenever(transactionNameProvider.provideTransactionName(request)).thenReturn("POST /product/{id}")
6767
whenever(transactionNameProvider.provideTransactionSource()).thenReturn(TransactionNameSource.CUSTOM)
68+
whenever(transactionNameProvider.provideTransactionNameAndSource(request)).thenReturn(
69+
TransactionNameWithSource(
70+
"POST /product/{id}",
71+
TransactionNameSource.CUSTOM
72+
)
73+
)
6874
if (sentryTraceHeader != null) {
6975
request.addHeader("sentry-trace", sentryTraceHeader)
7076
whenever(scopes.startTransaction(any(), check<TransactionOptions> { it.isBindToScope })).thenAnswer { SentryTracer(it.arguments[0] as TransactionContext, scopes) }

sentry-spring/api/sentry-spring.api

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,13 @@ public class io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfigurat
212212
public fun sentryOpenTelemetryOptionsConfiguration ()Lio/sentry/Sentry$OptionsConfiguration;
213213
}
214214

215+
public final class io/sentry/spring/tracing/CombinedTransactionNameProvider : io/sentry/spring/tracing/TransactionNameProvider {
216+
public fun <init> (Ljava/util/List;)V
217+
public fun provideTransactionName (Ljavax/servlet/http/HttpServletRequest;)Ljava/lang/String;
218+
public fun provideTransactionNameAndSource (Ljavax/servlet/http/HttpServletRequest;)Lio/sentry/spring/tracing/TransactionNameWithSource;
219+
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
220+
}
221+
215222
public class io/sentry/spring/tracing/SentryAdviceConfiguration {
216223
public fun <init> ()V
217224
public fun sentrySpanAdvice ()Lorg/aopalliance/aop/Advice;
@@ -291,9 +298,16 @@ public final class io/sentry/spring/tracing/SpringServletTransactionNameProvider
291298

292299
public abstract interface class io/sentry/spring/tracing/TransactionNameProvider {
293300
public abstract fun provideTransactionName (Ljavax/servlet/http/HttpServletRequest;)Ljava/lang/String;
301+
public fun provideTransactionNameAndSource (Ljavax/servlet/http/HttpServletRequest;)Lio/sentry/spring/tracing/TransactionNameWithSource;
294302
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
295303
}
296304

305+
public final class io/sentry/spring/tracing/TransactionNameWithSource {
306+
public fun <init> (Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V
307+
public fun getTransactionName ()Ljava/lang/String;
308+
public fun getTransactionNameSource ()Lio/sentry/protocol/TransactionNameSource;
309+
}
310+
297311
public class io/sentry/spring/webflux/SentryRequestResolver {
298312
public fun <init> (Lio/sentry/IScopes;)V
299313
public fun resolveSentryRequest (Lorg/springframework/http/server/reactive/ServerHttpRequest;)Lio/sentry/protocol/Request;

0 commit comments

Comments
 (0)