Skip to content

Commit

Permalink
Add support for async methods in Spring MVC.
Browse files Browse the repository at this point in the history
Fixes #1226
  • Loading branch information
maciejwalkowiak committed Aug 17, 2021
1 parent a52db48 commit 24a875c
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

* Feat: Spring WebClient integration (#1621)
* Feat: OpenFeign integration (#1632)
* Feat: Add support for async methods in Spring MVC ()

## 5.1.0-beta.9

Expand Down
5 changes: 5 additions & 0 deletions sentry-spring/api/sentry-spring.api
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ public class io/sentry/spring/SentrySpringServletContainerInitializer : javax/se
public fun onStartup (Ljava/util/Set;Ljavax/servlet/ServletContext;)V
}

public final class io/sentry/spring/SentryTaskDecorator : org/springframework/core/task/TaskDecorator {
public fun <init> ()V
public fun decorate (Ljava/lang/Runnable;)Ljava/lang/Runnable;
}

public class io/sentry/spring/SentryUserFilter : javax/servlet/Filter {
public fun <init> (Lio/sentry/IHub;Ljava/util/List;)V
public fun doFilter (Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;Ljavax/servlet/FilterChain;)V
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.sentry.spring;

import io.sentry.IHub;
import io.sentry.Sentry;
import java.util.concurrent.Callable;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.Async;

/**
* Sets a current hub on a thread running a {@link Runnable} given by parameter. Used to propagate
* the current {@link IHub} on the thread executing async task - like MVC controller methods
* returning a {@link Callable} or Spring beans methods annotated with {@link Async}.
*/
public final class SentryTaskDecorator implements TaskDecorator {
@Override
public @NotNull Runnable decorate(final @NotNull Runnable runnable) {
final IHub oldState = Sentry.getCurrentHub();
final IHub newHub = Sentry.getCurrentHub().clone();
return () -> {
Sentry.setCurrentHub(newHub);
try {
runnable.run();
} finally {
Sentry.setCurrentHub(oldState);
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.sentry.SpanStatus
import io.sentry.spring.EnableSentry
import io.sentry.spring.SentryExceptionResolver
import io.sentry.spring.SentrySpringFilter
import io.sentry.spring.SentryTaskDecorator
import io.sentry.spring.SentryUserFilter
import io.sentry.spring.SentryUserProvider
import io.sentry.spring.tracing.SentryTracingConfiguration
Expand All @@ -23,8 +24,8 @@ import io.sentry.spring.tracing.SentryTransaction
import io.sentry.test.checkEvent
import io.sentry.test.checkTransaction
import io.sentry.transport.ITransport
import java.lang.Exception
import java.time.Duration
import java.util.concurrent.Callable
import org.assertj.core.api.Assertions.assertThat
import org.awaitility.kotlin.await
import org.junit.Before
Expand Down Expand Up @@ -237,6 +238,21 @@ class SentrySpringIntegrationTest {
}, anyOrNull())
}
}

@Test
fun `scope is applied to events triggered in async methods`() {
val restTemplate = TestRestTemplate().withBasicAuth("user", "password")

restTemplate.getForEntity("http://localhost:$port/callable", String::class.java)

await.untilAsserted {
verify(transport).send(checkEvent { event ->
assertThat(event.message!!.formatted).isEqualTo("this message should be in the scope of the request")
assertThat(event.request).isNotNull()
assertThat(event.request!!.url).isEqualTo("http://localhost:$port/callable")
}, anyOrNull())
}
}
}

@SpringBootApplication
Expand Down Expand Up @@ -278,6 +294,9 @@ open class App {
this.filter = SentryTracingFilter(hub)
this.order = Ordered.HIGHEST_PRECEDENCE + 1 // must run after SentrySpringFilter
}

@Bean
open fun sentryTaskDecorator() = SentryTaskDecorator()
}

@Service
Expand Down Expand Up @@ -330,6 +349,14 @@ class HelloController {
fun throwsHandled() {
throw CustomException("handled exception")
}

@GetMapping("/callable")
fun callable(): Callable<String> {
return Callable {
Sentry.captureMessage("this message should be in the scope of the request")
"from callable"
}
}
}

class CustomException(message: String) : RuntimeException(message)
Expand Down

0 comments on commit 24a875c

Please sign in to comment.