Skip to content

Commit

Permalink
Add tests for continueTrace in spring filters
Browse files Browse the repository at this point in the history
  • Loading branch information
adinauer committed Jun 20, 2023
1 parent 15ad792 commit 3cb799c
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import io.sentry.TraceContext
import io.sentry.TransactionContext
import io.sentry.TransactionOptions
import io.sentry.protocol.SentryId
import io.sentry.protocol.SentryTransaction
import io.sentry.protocol.TransactionNameSource
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import org.assertj.core.api.Assertions.assertThat
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.check
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
Expand Down Expand Up @@ -51,7 +53,7 @@ class SentryTracingFilterTest {
whenever(hub.options).thenReturn(options)
}

fun getSut(isEnabled: Boolean = true, status: Int = 200, sentryTraceHeader: String? = null): SentryTracingFilter {
fun getSut(isEnabled: Boolean = true, status: Int = 200, sentryTraceHeader: String? = null, baggageHeaders: List<String>? = null): SentryTracingFilter {
request.requestURI = "/product/12"
request.method = "POST"
request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/product/{id}")
Expand All @@ -61,6 +63,9 @@ class SentryTracingFilterTest {
request.addHeader("sentry-trace", sentryTraceHeader)
whenever(hub.startTransaction(any(), check<TransactionOptions> { it.isBindToScope })).thenAnswer { SentryTracer(it.arguments[0] as TransactionContext, hub) }
}
if (baggageHeaders != null) {
request.addHeader("baggage", baggageHeaders)
}
response.status = status
whenever(hub.startTransaction(any(), check<TransactionOptions> { assertTrue(it.isBindToScope) })).thenAnswer { SentryTracer(it.arguments[0] as TransactionContext, hub) }
whenever(hub.isEnabled).thenReturn(isEnabled)
Expand Down Expand Up @@ -256,4 +261,26 @@ class SentryTracingFilterTest {
anyOrNull()
)
}

@Test
fun `continues incoming trace even is performance is disabled`() {
val parentSpanId = SpanId()
val sentryTraceHeaderString = "2722d9f6ec019ade60c776169d9a8904-$parentSpanId-1"
val baggageHeaderStrings = listOf("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET")
fixture.options.enableTracing = false
val filter = fixture.getSut(sentryTraceHeader = sentryTraceHeaderString, baggageHeaders = baggageHeaderStrings)

filter.doFilter(fixture.request, fixture.response, fixture.chain)

verify(fixture.chain).doFilter(fixture.request, fixture.response)

verify(fixture.hub).continueTrace(eq(sentryTraceHeaderString), eq(baggageHeaderStrings))

verify(fixture.hub, never()).captureTransaction(
anyOrNull<SentryTransaction>(),
anyOrNull<TraceContext>(),
anyOrNull(),
anyOrNull()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ import io.sentry.TraceContext
import io.sentry.TransactionContext
import io.sentry.TransactionOptions
import io.sentry.protocol.SentryId
import io.sentry.protocol.SentryTransaction
import io.sentry.protocol.TransactionNameSource
import io.sentry.spring.jakarta.webflux.AbstractSentryWebFilter.SENTRY_HUB_KEY
import org.assertj.core.api.Assertions.assertThat
import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.check
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
Expand Down Expand Up @@ -58,12 +61,15 @@ class SentryWebFluxTracingFilterTest {
whenever(hub.options).thenReturn(options)
}

fun getSut(isEnabled: Boolean = true, status: HttpStatus = HttpStatus.OK, sentryTraceHeader: String? = null, method: HttpMethod = HttpMethod.POST): SentryWebFilter {
fun getSut(isEnabled: Boolean = true, status: HttpStatus = HttpStatus.OK, sentryTraceHeader: String? = null, baggageHeaders: List<String>? = null, method: HttpMethod = HttpMethod.POST): SentryWebFilter {
var requestBuilder = MockServerHttpRequest.method(method, "/product/{id}", 12)
if (sentryTraceHeader != null) {
requestBuilder = requestBuilder.header("sentry-trace", sentryTraceHeader)
whenever(hub.startTransaction(any(), check<TransactionOptions> { it.isBindToScope })).thenAnswer { SentryTracer(it.arguments[0] as TransactionContext, hub) }
}
if (baggageHeaders != null) {
requestBuilder = requestBuilder.header("baggage", *baggageHeaders.toTypedArray())
}
request = requestBuilder.build()
exchange = MockServerWebExchange.builder(request).build()
exchange.attributes.put(SENTRY_HUB_KEY, hub)
Expand Down Expand Up @@ -291,4 +297,28 @@ class SentryWebFluxTracingFilterTest {
)
}
}

@Test
fun `continues incoming trace even is performance is disabled`() {
val parentSpanId = SpanId()
val sentryTraceHeaderString = "2722d9f6ec019ade60c776169d9a8904-$parentSpanId-1"
val baggageHeaderStrings = listOf("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET")
fixture.options.enableTracing = false
val filter = fixture.getSut(sentryTraceHeader = sentryTraceHeaderString, baggageHeaders = baggageHeaderStrings)

withMockHub {
filter.filter(fixture.exchange, fixture.chain).block()

verify(fixture.chain).filter(fixture.exchange)

verify(fixture.hub, never()).captureTransaction(
anyOrNull<SentryTransaction>(),
anyOrNull<TraceContext>(),
anyOrNull(),
anyOrNull()
)

verify(fixture.hub).continueTrace(eq(sentryTraceHeaderString), eq(baggageHeaderStrings))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,44 +73,58 @@ protected void doFilterInternal(
final @NotNull FilterChain filterChain)
throws ServletException, IOException {

if (hub.isEnabled() && shouldTraceRequest(httpRequest)) {
final String sentryTraceHeader = httpRequest.getHeader(SentryTraceHeader.SENTRY_TRACE_HEADER);
final List<String> baggageHeader =
if (hub.isEnabled()) {
final @Nullable String sentryTraceHeader =
httpRequest.getHeader(SentryTraceHeader.SENTRY_TRACE_HEADER);
final @Nullable List<String> baggageHeader =
Collections.list(httpRequest.getHeaders(BaggageHeader.BAGGAGE_HEADER));
final @Nullable PropagationContext propagationContext =
hub.continueTrace(sentryTraceHeader, baggageHeader);

// at this stage we are not able to get real transaction name
final ITransaction transaction = startTransaction(httpRequest, propagationContext);
try {
if (hub.getOptions().isTracingEnabled() && shouldTraceRequest(httpRequest)) {
doFilterWithTransaction(httpRequest, httpResponse, filterChain, propagationContext);
} else {
filterChain.doFilter(httpRequest, httpResponse);
} catch (Throwable e) {
// exceptions that are not handled by Spring
transaction.setStatus(SpanStatus.INTERNAL_ERROR);
throw e;
} finally {
// after all filters run, templated path pattern is available in request attribute
final String transactionName = transactionNameProvider.provideTransactionName(httpRequest);
final TransactionNameSource transactionNameSource =
transactionNameProvider.provideTransactionSource();
// 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) {
transaction.setName(transactionName, transactionNameSource);
transaction.setOperation(TRANSACTION_OP);
// if exception has been thrown, transaction status is already set to INTERNAL_ERROR, and
// httpResponse.getStatus() returns 200.
if (transaction.getStatus() == null) {
transaction.setStatus(SpanStatus.fromHttpStatusCode(httpResponse.getStatus()));
}
transaction.finish();
}
}
} else {
filterChain.doFilter(httpRequest, httpResponse);
}
}

private void doFilterWithTransaction(
HttpServletRequest httpRequest,
HttpServletResponse httpResponse,
FilterChain filterChain,
final @Nullable PropagationContext propagationContext)
throws IOException, ServletException {
// at this stage we are not able to get real transaction name
final ITransaction transaction = startTransaction(httpRequest, propagationContext);
try {
filterChain.doFilter(httpRequest, httpResponse);
} catch (Throwable e) {
// exceptions that are not handled by Spring
transaction.setStatus(SpanStatus.INTERNAL_ERROR);
throw e;
} finally {
// after all filters run, templated path pattern is available in request attribute
final String transactionName = transactionNameProvider.provideTransactionName(httpRequest);
final TransactionNameSource transactionNameSource =
transactionNameProvider.provideTransactionSource();
// 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) {
transaction.setName(transactionName, transactionNameSource);
transaction.setOperation(TRANSACTION_OP);
// if exception has been thrown, transaction status is already set to INTERNAL_ERROR, and
// httpResponse.getStatus() returns 200.
if (transaction.getStatus() == null) {
transaction.setStatus(SpanStatus.fromHttpStatusCode(httpResponse.getStatus()));
}
transaction.finish();
}
}
}

private boolean shouldTraceRequest(final @NotNull HttpServletRequest request) {
return hub.getOptions().isTraceOptionsRequests()
|| !HttpMethod.OPTIONS.name().equals(request.getMethod());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ import io.sentry.TraceContext
import io.sentry.TransactionContext
import io.sentry.TransactionOptions
import io.sentry.protocol.SentryId
import io.sentry.protocol.SentryTransaction
import io.sentry.protocol.TransactionNameSource
import org.assertj.core.api.Assertions.assertThat
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.check
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
Expand All @@ -42,14 +45,15 @@ class SentryTracingFilterTest {
val transactionNameProvider = mock<TransactionNameProvider>()
val options = SentryOptions().apply {
dsn = "https://key@sentry.io/proj"
enableTracing = true
}
val logger = mock<ILogger>()

init {
whenever(hub.options).thenReturn(options)
}

fun getSut(isEnabled: Boolean = true, status: Int = 200, sentryTraceHeader: String? = null): SentryTracingFilter {
fun getSut(isEnabled: Boolean = true, status: Int = 200, sentryTraceHeader: String? = null, baggageHeaders: List<String>? = null): SentryTracingFilter {
request.requestURI = "/product/12"
request.method = "POST"
request.setAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/product/{id}")
Expand All @@ -59,6 +63,9 @@ class SentryTracingFilterTest {
request.addHeader("sentry-trace", sentryTraceHeader)
whenever(hub.startTransaction(any(), check<TransactionOptions> { it.isBindToScope })).thenAnswer { SentryTracer(it.arguments[0] as TransactionContext, hub) }
}
if (baggageHeaders != null) {
request.addHeader("baggage", baggageHeaders)
}
response.status = status
whenever(hub.startTransaction(any(), check<TransactionOptions> { assertTrue(it.isBindToScope) })).thenAnswer { SentryTracer(it.arguments[0] as TransactionContext, hub) }
whenever(hub.isEnabled).thenReturn(isEnabled)
Expand Down Expand Up @@ -209,7 +216,8 @@ class SentryTracingFilterTest {
verify(fixture.chain).doFilter(fixture.request, fixture.response)

verify(fixture.hub).isEnabled
verify(fixture.hub).options
verify(fixture.hub, times(2)).options
verify(fixture.hub).continueTrace(anyOrNull(), anyOrNull())
verifyNoMoreInteractions(fixture.hub)
verify(fixture.transactionNameProvider, never()).provideTransactionName(any())
}
Expand Down Expand Up @@ -253,4 +261,26 @@ class SentryTracingFilterTest {
anyOrNull()
)
}

@Test
fun `continues incoming trace even is performance is disabled`() {
val parentSpanId = SpanId()
val sentryTraceHeaderString = "2722d9f6ec019ade60c776169d9a8904-$parentSpanId-1"
val baggageHeaderStrings = listOf("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET")
fixture.options.enableTracing = false
val filter = fixture.getSut(sentryTraceHeader = sentryTraceHeaderString, baggageHeaders = baggageHeaderStrings)

filter.doFilter(fixture.request, fixture.response, fixture.chain)

verify(fixture.chain).doFilter(fixture.request, fixture.response)

verify(fixture.hub).continueTrace(eq(sentryTraceHeaderString), eq(baggageHeaderStrings))

verify(fixture.hub, never()).captureTransaction(
anyOrNull<SentryTransaction>(),
anyOrNull<TraceContext>(),
anyOrNull(),
anyOrNull()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ import io.sentry.TraceContext
import io.sentry.TransactionContext
import io.sentry.TransactionOptions
import io.sentry.protocol.SentryId
import io.sentry.protocol.SentryTransaction
import io.sentry.protocol.TransactionNameSource
import io.sentry.spring.webflux.SentryWebFilter.SENTRY_HUB_KEY
import org.assertj.core.api.Assertions.assertThat
import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.check
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
Expand Down Expand Up @@ -58,12 +61,15 @@ class SentryWebFluxTracingFilterTest {
whenever(hub.options).thenReturn(options)
}

fun getSut(isEnabled: Boolean = true, status: HttpStatus = HttpStatus.OK, sentryTraceHeader: String? = null, method: HttpMethod = HttpMethod.POST): SentryWebFilter {
fun getSut(isEnabled: Boolean = true, status: HttpStatus = HttpStatus.OK, sentryTraceHeader: String? = null, baggageHeaders: List<String>? = null, method: HttpMethod = HttpMethod.POST): SentryWebFilter {
var requestBuilder = MockServerHttpRequest.method(method, "/product/{id}", 12)
if (sentryTraceHeader != null) {
requestBuilder = requestBuilder.header("sentry-trace", sentryTraceHeader)
whenever(hub.startTransaction(any(), check<TransactionOptions> { it.isBindToScope })).thenAnswer { SentryTracer(it.arguments[0] as TransactionContext, hub) }
}
if (baggageHeaders != null) {
requestBuilder = requestBuilder.header("baggage", *baggageHeaders.toTypedArray())
}
request = requestBuilder.build()
exchange = MockServerWebExchange.builder(request).build()
exchange.attributes.put(SENTRY_HUB_KEY, hub)
Expand Down Expand Up @@ -292,4 +298,28 @@ class SentryWebFluxTracingFilterTest {
)
}
}

@Test
fun `continues incoming trace even is performance is disabled`() {
val parentSpanId = SpanId()
val sentryTraceHeaderString = "2722d9f6ec019ade60c776169d9a8904-$parentSpanId-1"
val baggageHeaderStrings = listOf("sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET")
fixture.options.enableTracing = false
val filter = fixture.getSut(sentryTraceHeader = sentryTraceHeaderString, baggageHeaders = baggageHeaderStrings)

withMockHub {
filter.filter(fixture.exchange, fixture.chain).block()

verify(fixture.chain).filter(fixture.exchange)

verify(fixture.hub, never()).captureTransaction(
anyOrNull<SentryTransaction>(),
anyOrNull<TraceContext>(),
anyOrNull(),
anyOrNull()
)

verify(fixture.hub).continueTrace(eq(sentryTraceHeaderString), eq(baggageHeaderStrings))
}
}
}

0 comments on commit 3cb799c

Please sign in to comment.