Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@

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

### Behavioral Changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.sentry.spring.jakarta.exception.SentryExceptionParameterAdviceConfiguration;
import io.sentry.spring.jakarta.opentelemetry.SentryOpenTelemetryAgentWithoutAutoInitConfiguration;
import io.sentry.spring.jakarta.opentelemetry.SentryOpenTelemetryNoAgentConfiguration;
import io.sentry.spring.jakarta.tracing.CombinedTransactionNameProvider;
import io.sentry.spring.jakarta.tracing.SentryAdviceConfiguration;
import io.sentry.spring.jakarta.tracing.SentrySpanPointcutConfiguration;
import io.sentry.spring.jakarta.tracing.SentryTracingFilter;
Expand All @@ -42,6 +43,7 @@
import io.sentry.transport.ITransportGate;
import io.sentry.transport.apache.ApacheHttpClientTransportFactory;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.aspectj.lang.ProceedingJoinPoint;
Expand Down Expand Up @@ -342,7 +344,10 @@ static class SentryMvcModeConfig {
@Bean
@ConditionalOnMissingBean(TransactionNameProvider.class)
public @NotNull TransactionNameProvider transactionNameProvider() {
return new SpringMvcTransactionNameProvider();
return new CombinedTransactionNameProvider(
Arrays.asList(
new SpringMvcTransactionNameProvider(),
new SpringServletTransactionNameProvider()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.sentry.spring.exception.SentryExceptionParameterAdviceConfiguration;
import io.sentry.spring.opentelemetry.SentryOpenTelemetryAgentWithoutAutoInitConfiguration;
import io.sentry.spring.opentelemetry.SentryOpenTelemetryNoAgentConfiguration;
import io.sentry.spring.tracing.CombinedTransactionNameProvider;
import io.sentry.spring.tracing.SentryAdviceConfiguration;
import io.sentry.spring.tracing.SentrySpanPointcutConfiguration;
import io.sentry.spring.tracing.SentryTracingFilter;
Expand All @@ -41,6 +42,7 @@
import io.sentry.spring.tracing.TransactionNameProvider;
import io.sentry.transport.ITransportGate;
import io.sentry.transport.apache.ApacheHttpClientTransportFactory;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -327,7 +329,10 @@ static class SentryMvcModeConfig {
@Bean
@ConditionalOnMissingBean(TransactionNameProvider.class)
public @NotNull TransactionNameProvider transactionNameProvider() {
return new SpringMvcTransactionNameProvider();
return new CombinedTransactionNameProvider(
Arrays.asList(
new SpringMvcTransactionNameProvider(),
new SpringServletTransactionNameProvider()));
}
}

Expand Down
14 changes: 14 additions & 0 deletions sentry-spring-jakarta/api/sentry-spring-jakarta.api
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@ public class io/sentry/spring/jakarta/opentelemetry/SentryOpenTelemetryNoAgentCo
public fun sentryOpenTelemetryOptionsConfiguration ()Lio/sentry/Sentry$OptionsConfiguration;
}

public final class io/sentry/spring/jakarta/tracing/CombinedTransactionNameProvider : io/sentry/spring/jakarta/tracing/TransactionNameProvider {
public fun <init> (Ljava/util/List;)V
public fun provideTransactionName (Ljakarta/servlet/http/HttpServletRequest;)Ljava/lang/String;
public fun provideTransactionNameAndSource (Ljakarta/servlet/http/HttpServletRequest;)Lio/sentry/spring/jakarta/tracing/TransactionNameWithSource;
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
}

public class io/sentry/spring/jakarta/tracing/SentryAdviceConfiguration {
public fun <init> ()V
public fun sentrySpanAdvice ()Lorg/aopalliance/aop/Advice;
Expand Down Expand Up @@ -300,9 +307,16 @@ public final class io/sentry/spring/jakarta/tracing/SpringServletTransactionName

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

public final class io/sentry/spring/jakarta/tracing/TransactionNameWithSource {
public fun <init> (Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V
public fun getTransactionName ()Ljava/lang/String;
public fun getTransactionNameSource ()Lio/sentry/protocol/TransactionNameSource;
}

public abstract class io/sentry/spring/jakarta/webflux/AbstractSentryWebFilter : org/springframework/web/server/WebFilter {
public static final field SENTRY_HUB_KEY Ljava/lang/String;
public static final field SENTRY_SCOPES_KEY Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.sentry.spring.jakarta.tracing;

import io.sentry.protocol.TransactionNameSource;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Resolves transaction name using other transaction name providers by invoking them in order. If a
* provider returns no transaction name, the next one is invoked.
*/
@ApiStatus.Internal
public final class CombinedTransactionNameProvider implements TransactionNameProvider {

private final @NotNull List<TransactionNameProvider> providers;

public CombinedTransactionNameProvider(final @NotNull List<TransactionNameProvider> providers) {
this.providers = providers;
}

@Override
public @Nullable String provideTransactionName(@NotNull HttpServletRequest request) {
for (TransactionNameProvider provider : providers) {
String transactionName = provider.provideTransactionName(request);
if (transactionName != null) {
return transactionName;
}
}

return null;
}

@Override
@ApiStatus.Internal
public @NotNull TransactionNameSource provideTransactionSource() {
return TransactionNameSource.CUSTOM;
}

@ApiStatus.Internal
@Override
public @NotNull TransactionNameWithSource provideTransactionNameAndSource(
@NotNull HttpServletRequest request) {
for (TransactionNameProvider provider : providers) {
String transactionName = provider.provideTransactionName(request);
if (transactionName != null) {
final @NotNull TransactionNameSource source = provider.provideTransactionSource();
return new TransactionNameWithSource(transactionName, source);
}
}

return new TransactionNameWithSource(null, TransactionNameSource.CUSTOM);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,11 @@ private void doFilterWithTransaction(
} finally {
if (shouldFinishTransaction(httpRequest) && transaction != null) {
// after all filters run, templated path pattern is available in request attribute
final String transactionName = transactionNameProvider.provideTransactionName(httpRequest);
final TransactionNameSource transactionNameSource =
transactionNameProvider.provideTransactionSource();
final @NotNull TransactionNameWithSource transactionNameWithSource =
transactionNameProvider.provideTransactionNameAndSource(httpRequest);
final @Nullable String transactionName = transactionNameWithSource.getTransactionName();
final @NotNull TransactionNameSource transactionNameSource =
transactionNameWithSource.getTransactionNameSource();
// if transaction name is not resolved, the request has not been processed by a controller
// and we should not report it to Sentry
if (transactionName != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,12 @@ public interface TransactionNameProvider {
default TransactionNameSource provideTransactionSource() {
return TransactionNameSource.CUSTOM;
}

@NotNull
@ApiStatus.Internal
default TransactionNameWithSource provideTransactionNameAndSource(
final @NotNull HttpServletRequest request) {
return new TransactionNameWithSource(
provideTransactionName(request), provideTransactionSource());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.sentry.spring.jakarta.tracing;

import io.sentry.protocol.TransactionNameSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@ApiStatus.Internal
public final class TransactionNameWithSource {
private final @Nullable String transactionName;
private final @NotNull TransactionNameSource transactionNameSource;

public TransactionNameWithSource(
final @Nullable String transactionName,
final @NotNull TransactionNameSource transactionNameSource) {
this.transactionName = transactionName;
this.transactionNameSource = transactionNameSource;
}

public @Nullable String getTransactionName() {
return transactionName;
}

public @NotNull TransactionNameSource getTransactionNameSource() {
return transactionNameSource;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ class SentryTracingFilterTest {
request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/product/{id}")
whenever(transactionNameProvider.provideTransactionName(request)).thenReturn("POST /product/{id}")
whenever(transactionNameProvider.provideTransactionSource()).thenReturn(TransactionNameSource.CUSTOM)
whenever(transactionNameProvider.provideTransactionNameAndSource(request)).thenReturn(
TransactionNameWithSource(
"POST /product/{id}",
TransactionNameSource.CUSTOM
)
)
if (sentryTraceHeader != null) {
request.addHeader("sentry-trace", sentryTraceHeader)
whenever(scopes.startTransaction(any(), check<TransactionOptions> { it.isBindToScope })).thenAnswer { SentryTracer(it.arguments[0] as TransactionContext, scopes) }
Expand Down
14 changes: 14 additions & 0 deletions sentry-spring/api/sentry-spring.api
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,13 @@ public class io/sentry/spring/opentelemetry/SentryOpenTelemetryNoAgentConfigurat
public fun sentryOpenTelemetryOptionsConfiguration ()Lio/sentry/Sentry$OptionsConfiguration;
}

public final class io/sentry/spring/tracing/CombinedTransactionNameProvider : io/sentry/spring/tracing/TransactionNameProvider {
public fun <init> (Ljava/util/List;)V
public fun provideTransactionName (Ljavax/servlet/http/HttpServletRequest;)Ljava/lang/String;
public fun provideTransactionNameAndSource (Ljavax/servlet/http/HttpServletRequest;)Lio/sentry/spring/tracing/TransactionNameWithSource;
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
}

public class io/sentry/spring/tracing/SentryAdviceConfiguration {
public fun <init> ()V
public fun sentrySpanAdvice ()Lorg/aopalliance/aop/Advice;
Expand Down Expand Up @@ -291,9 +298,16 @@ public final class io/sentry/spring/tracing/SpringServletTransactionNameProvider

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

public final class io/sentry/spring/tracing/TransactionNameWithSource {
public fun <init> (Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V
public fun getTransactionName ()Ljava/lang/String;
public fun getTransactionNameSource ()Lio/sentry/protocol/TransactionNameSource;
}

public class io/sentry/spring/webflux/SentryRequestResolver {
public fun <init> (Lio/sentry/IScopes;)V
public fun resolveSentryRequest (Lorg/springframework/http/server/reactive/ServerHttpRequest;)Lio/sentry/protocol/Request;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.sentry.spring.tracing;

import io.sentry.protocol.TransactionNameSource;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Resolves transaction name using other transaction name providers by invoking them in order. If a
* provider returns no transaction name, the next one is invoked.
*/
@ApiStatus.Internal
public final class CombinedTransactionNameProvider implements TransactionNameProvider {

private final @NotNull List<TransactionNameProvider> providers;

public CombinedTransactionNameProvider(final @NotNull List<TransactionNameProvider> providers) {
this.providers = providers;
}

@Override
public @Nullable String provideTransactionName(final @NotNull HttpServletRequest request) {
for (TransactionNameProvider provider : providers) {
String transactionName = provider.provideTransactionName(request);
if (transactionName != null) {
return transactionName;
}
}

return null;
}

@Override
@ApiStatus.Internal
public @NotNull TransactionNameSource provideTransactionSource() {
return TransactionNameSource.CUSTOM;
}

@ApiStatus.Internal
@Override
public @NotNull TransactionNameWithSource provideTransactionNameAndSource(
@NotNull HttpServletRequest request) {
for (TransactionNameProvider provider : providers) {
String transactionName = provider.provideTransactionName(request);
if (transactionName != null) {
final @NotNull TransactionNameSource source = provider.provideTransactionSource();
return new TransactionNameWithSource(transactionName, source);
}
}

return new TransactionNameWithSource(null, TransactionNameSource.CUSTOM);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,11 @@ private void doFilterWithTransaction(
} finally {
if (shouldFinishTransaction(httpRequest) && transaction != null) {
// after all filters run, templated path pattern is available in request attribute
final String transactionName = transactionNameProvider.provideTransactionName(httpRequest);
final TransactionNameSource transactionNameSource =
transactionNameProvider.provideTransactionSource();
final @NotNull TransactionNameWithSource transactionNameWithSource =
transactionNameProvider.provideTransactionNameAndSource(httpRequest);
final @Nullable String transactionName = transactionNameWithSource.getTransactionName();
final @NotNull TransactionNameSource transactionNameSource =
transactionNameWithSource.getTransactionNameSource();
// if transaction name is not resolved, the request has not been processed by a controller
// and we should not report it to Sentry
if (transactionName != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,12 @@ public interface TransactionNameProvider {
default TransactionNameSource provideTransactionSource() {
return TransactionNameSource.CUSTOM;
}

@NotNull
@ApiStatus.Internal
default TransactionNameWithSource provideTransactionNameAndSource(
final @NotNull HttpServletRequest request) {
return new TransactionNameWithSource(
provideTransactionName(request), provideTransactionSource());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.sentry.spring.tracing;

import io.sentry.protocol.TransactionNameSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@ApiStatus.Internal
public final class TransactionNameWithSource {
private final @Nullable String transactionName;
private final @NotNull TransactionNameSource transactionNameSource;

public TransactionNameWithSource(
final @Nullable String transactionName,
final @NotNull TransactionNameSource transactionNameSource) {
this.transactionName = transactionName;
this.transactionNameSource = transactionNameSource;
}

public @Nullable String getTransactionName() {
return transactionName;
}

public @NotNull TransactionNameSource getTransactionNameSource() {
return transactionNameSource;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class SentryTracingFilterTest {
request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/product/{id}")
whenever(transactionNameProvider.provideTransactionName(request)).thenReturn("POST /product/{id}")
whenever(transactionNameProvider.provideTransactionSource()).thenReturn(TransactionNameSource.CUSTOM)
whenever(transactionNameProvider.provideTransactionNameAndSource(request)).thenReturn(TransactionNameWithSource("POST /product/{id}", TransactionNameSource.CUSTOM))
if (sentryTraceHeader != null) {
request.addHeader("sentry-trace", sentryTraceHeader)
whenever(scopes.startTransaction(any(), check<TransactionOptions> { it.isBindToScope })).thenAnswer { SentryTracer(it.arguments[0] as TransactionContext, scopes) }
Expand Down
Loading