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

Support for performance tracking with Apollo on Android #1331

Closed
alexmetcalfe opened this issue Mar 17, 2021 · 5 comments · Fixed by #1705
Closed

Support for performance tracking with Apollo on Android #1331

alexmetcalfe opened this issue Mar 17, 2021 · 5 comments · Fixed by #1705
Assignees
Labels
enhancement New feature or request performance Performance API issues Platform: Android

Comments

@alexmetcalfe
Copy link

We use Apollo android extensively through our app.

This uses OkHttp under the hood which means I assume each request will be traced in the performance dashboard once this is complete -> #1330

However with GraphQL each request posts to the same URL. The body of the post contains the query which tells the backend what to do eg a query or mutation...

This I believe would make it difficult to understand what each network request is doing...

In the meantime we can do something similar to what the OkHttp interceptor is doing but it would be great if this came out of the box for us.

@alexmetcalfe alexmetcalfe changed the title Support for Apollo on Android Support for performance tracking with Apollo on Android Mar 17, 2021
@bruno-garcia bruno-garcia added enhancement New feature or request Platform: Android performance Performance API issues labels Mar 18, 2021
@marandaneto
Copy link
Contributor

marandaneto commented Mar 18, 2021

thanks for reporting @alexmetcalfe

sounds doable, looks like the Apollo Android lib offers interceptors similar to OkHttp
https://www.apollographql.com/docs/ios/initialization/#example-advanced-client-setup maybe with that we could gather more info and create enriched Spans OOTB (it's an iOS docs but pretty sure Android has something similar)

@alexmetcalfe
Copy link
Author

Managed to hack together something today based of your OkHttp interceptor, seems to work well so far!

class SentryApolloInterceptor(
        private val hub: IHub = HubAdapter.getInstance()
) : ApolloInterceptor {
    override fun interceptAsync(request: ApolloInterceptor.InterceptorRequest, chain: ApolloInterceptorChain, dispatcher: Executor, callBack: ApolloInterceptor.CallBack) {
        val operationName = request.operation.name().name()
        val operationType = if(request.operation is Query) "query" else "mutation"
        val operationDescription = "$operationType $operationName"

        // read transaction from the bound scope
        val span = hub.span?.startChild("apollo.http.client", operationDescription)

        var newRequest = request
        span?.toSentryTrace()?.let {
            newRequest = request.toBuilder().requestHeaders(
                    request.requestHeaders.toBuilder()
                            .addHeader(it.name, it.value)
                            .build()
            ).build()
        }

        span?.spanContext?.tags?.put("operationId", request.operation.operationId())
        span?.spanContext?.tags?.put("variables", request.operation.variables().valueMap().toString())

        chain.proceedAsync(newRequest, dispatcher, object : ApolloInterceptor.CallBack {
            override fun onResponse(response: ApolloInterceptor.InterceptorResponse) {
                val httpResponse = response.httpResponse.get()
                span?.finish(SpanStatus.fromHttpStatusCode(httpResponse.code, SpanStatus.INTERNAL_ERROR))

                val breadcrumb = Breadcrumb(operationDescription)
                httpResponse.request.body?.contentLength().ifHasValidLength {
                    breadcrumb.setData("requestBodySize", it)
                }
                httpResponse.body?.contentLength().ifHasValidLength {
                    breadcrumb.setData("responseBodySize", it)
                }
                hub.addBreadcrumb(breadcrumb)
                callBack.onResponse(response)
            }

            override fun onFetch(sourceType: ApolloInterceptor.FetchSourceType?) {
                callBack.onFetch(sourceType)
            }

            override fun onFailure(e: ApolloException) {
                callBack.onFailure(e)
            }

            override fun onCompleted() {
                callBack.onCompleted()
            }
        })
    }

    override fun dispose() {
        // no-op
    }

    private fun Long?.ifHasValidLength(fn: (Long) -> Unit) {
        if (this != null && this != -1L) {
            fn.invoke(this)
        }
    }
}

@bruno-garcia
Copy link
Member

Thanks awesome @alexmetcalfe! Thank you for sharing this

@marandaneto
Copy link
Contributor

nicely done, @alexmetcalfe

be aware that if it throws, I guess you won't be associating the error to the Span nor finishing the Span as errored, by the callback signatures, I'd guess you need to do something here -> onFailure(e: ApolloException)

similar to https://github.com/getsentry/sentry-java/blob/main/sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt#L34-L38

@alexmetcalfe
Copy link
Author

Yeah good point, had not covered the failure side of things yet 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request performance Performance API issues Platform: Android
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants