Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatic performance transactions for Webflux #1807

Closed
marandaneto opened this issue Nov 16, 2021 · 12 comments · Fixed by #2597
Closed

Automatic performance transactions for Webflux #1807

marandaneto opened this issue Nov 16, 2021 · 12 comments · Fixed by #2597
Assignees
Labels
feature request OpenTelemetryDoesIt Redirect to OpenTelemetry as it already provides the required functionality performance Performance API issues Platform: Java WebFlux
Projects

Comments

@marandaneto
Copy link
Contributor

Similar to SentryTracingFilter but for Webflux

@koenpunt
Copy link

koenpunt commented Nov 16, 2021

I've tried something similar as what is in SentryTracingFilter, but I think it needs some specifics to bind it to the reactor context.

Click to expand code
package oopen.timemachine.config

import io.sentry.CustomSamplingContext
import io.sentry.HubAdapter
import io.sentry.ITransaction
import io.sentry.SentryLevel
import io.sentry.SentryTraceHeader
import io.sentry.SpanStatus
import io.sentry.TransactionContext
import io.sentry.exception.InvalidSentryTraceHeaderException
import org.springframework.http.server.reactive.ServerHttpRequest
import org.springframework.stereotype.Component
import org.springframework.web.reactive.HandlerMapping
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import org.springframework.web.util.pattern.PathPattern
import reactor.core.publisher.Mono

@Component
class SentryTransactionWebFilter(private val hub: HubAdapter = HubAdapter.getInstance()) : WebFilter {
    companion object {
        private const val TRANSACTION_OP = "http.server"
    }

    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
        val sentryTraceHeader = exchange.request.headers.getFirst(SentryTraceHeader.SENTRY_TRACE_HEADER)

        // at this stage we are not able to get real transaction name
        val transaction = startTransaction(exchange.request, sentryTraceHeader);

        return chain.filter(exchange).doFinally {
            // after all filters run, templated path pattern is available in request attribute
            val transactionName = provideTransactionName(exchange);
            // 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.name = transactionName;
                transaction.operation = TRANSACTION_OP;
                // if exception has been thrown, transaction status is already set to INTERNAL_ERROR, and
                // httpResponse.getStatus() returns 200.
                if (transaction.status == null) {
                    transaction.status = SpanStatus.fromHttpStatusCode(exchange.response.rawStatusCode ?: 0);
                }
                transaction.finish();
            }
        }.doOnError {
            transaction.status = SpanStatus.INTERNAL_ERROR
            transaction.throwable = it
        }
    }

    private fun startTransaction(request: ServerHttpRequest, sentryTraceHeader: String?): ITransaction {
        val name = "${request.methodValue} ${request.uri}"
        val customSamplingContext = CustomSamplingContext()
        customSamplingContext["request"] = request
        if (sentryTraceHeader != null) {
            try {
                val contexts = TransactionContext.fromSentryTrace(
                    name, TRANSACTION_OP, SentryTraceHeader(sentryTraceHeader)
                )
                return hub.startTransaction(contexts, customSamplingContext, true)
            } catch (e: InvalidSentryTraceHeaderException) {
                hub.options.logger
                    .log(SentryLevel.DEBUG, e, "Failed to parse Sentry trace header: %s", e.message)
            }
        }
        return hub.startTransaction(name, "http.server", customSamplingContext, true)
    }

    // Could be `io.sentry.spring.webflux.TransactionNameProvider`, but that class is private.
    private fun provideTransactionName(
        serverWebExchange: ServerWebExchange
    ): String? {
        val pattern = serverWebExchange.getAttribute<PathPattern>(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)
        return if (pattern != null) {
            serverWebExchange.request.methodValue + " " + pattern.patternString
        } else {
            null
        }
    }
}

@marandaneto
Copy link
Contributor Author

marandaneto commented Nov 16, 2021

@koenpunt if you feel submitting a PR, you can make TransactionNameProvider public with an @ApiStatus.Internal annotation, so it can be used by the SDK.

@marandaneto
Copy link
Contributor Author

@maciejwalkowiak can likely guide you as well, since he has made the WebFlux integration.

@maciejwalkowiak
Copy link
Contributor

First I need to figure out how to fix Webflux integration for error handlers. Either we stay with Reactor hook that re-binds Hub to current thread, or we move to using Reactor context.

@koenpunt
Copy link

Any pointers for how to get the project to build in IntelliJ? I keep getting this error on gradle sync;

This version of the Android Support plugin for IntelliJ IDEA (or Android Studio) cannot open this project, please retry with version 2020.3.1 or newer.

@marandaneto
Copy link
Contributor Author

Any pointers for how to get the project to build in IntelliJ? I keep getting this error on gradle sync;

This version of the Android Support plugin for IntelliJ IDEA (or Android Studio) cannot open this project, please retry with version 2020.3.1 or newer.

that's because the latest Android Gradle Plugin isn't compatible with IDEA but Android Studio, you can comment the Android projects as a temporary workaround ->
https://github.com/getsentry/sentry-java/blob/main/settings.gradle.kts#L9-L14
https://github.com/getsentry/sentry-java/blob/main/settings.gradle.kts#L28

@philipphofmann philipphofmann added this to To do in kanban via automation Nov 17, 2021
@yhware
Copy link
Contributor

yhware commented Feb 14, 2022

Hi, is there any update on this issue? WebFlux transaction support would be a great feature to have.

@marandaneto
Copy link
Contributor Author

Hi, is there any update on this issue? WebFlux transaction support would be a great feature to have.

Not yet, someone tried to do a Draft PR tho #1809

@adinauer
Copy link
Member

We want to wait for #1819 before moving on with this.

@adinauer
Copy link
Member

adinauer commented Feb 7, 2023

For anyone waiting for this. Please give our OpenTelemetry Integration a try.

@adinauer adinauer added the OpenTelemetryDoesIt Redirect to OpenTelemetry as it already provides the required functionality label Feb 7, 2023
@adinauer adinauer self-assigned this Mar 10, 2023
kanban automation moved this from To do to Done Mar 14, 2023
@adinauer
Copy link
Member

6.16.0 has been released, adding support for WebFlux Performance. If you decide to give it a try, please give us feedback 🙏

@koenpunt
Copy link

By now we've removed sentry from the project and rely on newrelic. But good to know it's implemented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request OpenTelemetryDoesIt Redirect to OpenTelemetry as it already provides the required functionality performance Performance API issues Platform: Java WebFlux
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

6 participants