-
-
Notifications
You must be signed in to change notification settings - Fork 458
Feat: Performance support for Android Apollo #1705
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
Changes from all commits
6af5200
d163f85
5d45523
59bd48b
1c34fa1
dca2176
ed3d254
aff550f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| public final class io/sentry/apollo/SentryApolloInterceptor : com/apollographql/apollo/interceptor/ApolloInterceptor { | ||
| public fun <init> ()V | ||
| public fun <init> (Lio/sentry/IHub;)V | ||
| public fun <init> (Lio/sentry/IHub;Lio/sentry/apollo/SentryApolloInterceptor$BeforeSpanCallback;)V | ||
| public synthetic fun <init> (Lio/sentry/IHub;Lio/sentry/apollo/SentryApolloInterceptor$BeforeSpanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V | ||
| public fun <init> (Lio/sentry/apollo/SentryApolloInterceptor$BeforeSpanCallback;)V | ||
| public fun dispose ()V | ||
| public fun interceptAsync (Lcom/apollographql/apollo/interceptor/ApolloInterceptor$InterceptorRequest;Lcom/apollographql/apollo/interceptor/ApolloInterceptorChain;Ljava/util/concurrent/Executor;Lcom/apollographql/apollo/interceptor/ApolloInterceptor$CallBack;)V | ||
| } | ||
|
|
||
| public abstract interface class io/sentry/apollo/SentryApolloInterceptor$BeforeSpanCallback { | ||
| public abstract fun execute (Lio/sentry/ISpan;Lcom/apollographql/apollo/interceptor/ApolloInterceptor$InterceptorRequest;Lcom/apollographql/apollo/interceptor/ApolloInterceptor$InterceptorResponse;)Lio/sentry/ISpan; | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import net.ltgt.gradle.errorprone.errorprone | ||
| import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | ||
|
|
||
| plugins { | ||
| `java-library` | ||
| kotlin("jvm") | ||
| jacoco | ||
| id(Config.QualityPlugins.errorProne) | ||
| id(Config.QualityPlugins.gradleVersions) | ||
| id(Config.BuildPlugins.buildConfig) version Config.BuildPlugins.buildConfigVersion | ||
| } | ||
|
|
||
| configure<JavaPluginConvention> { | ||
| sourceCompatibility = JavaVersion.VERSION_1_8 | ||
| targetCompatibility = JavaVersion.VERSION_1_8 | ||
| } | ||
|
|
||
| tasks.withType<KotlinCompile>().configureEach { | ||
| kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() | ||
| } | ||
|
|
||
| dependencies { | ||
| api(projects.sentry) | ||
| api(projects.sentryKotlinExtensions) | ||
|
|
||
| implementation(Config.Libs.apolloAndroid) | ||
|
|
||
| compileOnly(Config.CompileOnly.nopen) | ||
| errorprone(Config.CompileOnly.nopenChecker) | ||
| errorprone(Config.CompileOnly.errorprone) | ||
| errorprone(Config.CompileOnly.errorProneNullAway) | ||
| errorproneJavac(Config.CompileOnly.errorProneJavac8) | ||
| compileOnly(Config.CompileOnly.jetbrainsAnnotations) | ||
|
|
||
| // tests | ||
| testImplementation(projects.sentryTestSupport) | ||
| testImplementation(Config.Libs.coroutinesCore) | ||
| testImplementation(kotlin(Config.kotlinStdLib)) | ||
| testImplementation(Config.TestLibs.kotlinTestJunit) | ||
| testImplementation(Config.TestLibs.mockitoKotlin) | ||
| testImplementation(Config.TestLibs.mockitoInline) | ||
| testImplementation(Config.TestLibs.mockWebserver3) | ||
| testImplementation(Config.Libs.apolloCoroutines) | ||
| } | ||
|
|
||
| configure<SourceSetContainer> { | ||
| test { | ||
| java.srcDir("src/test/java") | ||
| } | ||
| } | ||
|
|
||
| jacoco { | ||
| toolVersion = Config.QualityPlugins.Jacoco.version | ||
| } | ||
|
|
||
| tasks.jacocoTestReport { | ||
| reports { | ||
| xml.isEnabled = true | ||
| html.isEnabled = false | ||
| } | ||
| } | ||
|
|
||
| tasks { | ||
| jacocoTestCoverageVerification { | ||
| violationRules { | ||
| rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } } | ||
| } | ||
| } | ||
| check { | ||
| dependsOn(jacocoTestCoverageVerification) | ||
| dependsOn(jacocoTestReport) | ||
| } | ||
| } | ||
|
|
||
| tasks.withType<JavaCompile>().configureEach { | ||
| options.errorprone { | ||
| check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR) | ||
| option("NullAway:AnnotatedPackages", "io.sentry") | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| package io.sentry.apollo | ||
|
|
||
| import com.apollographql.apollo.api.Mutation | ||
| import com.apollographql.apollo.api.Query | ||
| import com.apollographql.apollo.api.Subscription | ||
| import com.apollographql.apollo.exception.ApolloException | ||
| import com.apollographql.apollo.exception.ApolloHttpException | ||
| import com.apollographql.apollo.interceptor.ApolloInterceptor | ||
| import com.apollographql.apollo.interceptor.ApolloInterceptor.CallBack | ||
| import com.apollographql.apollo.interceptor.ApolloInterceptor.FetchSourceType | ||
| import com.apollographql.apollo.interceptor.ApolloInterceptor.InterceptorRequest | ||
| import com.apollographql.apollo.interceptor.ApolloInterceptor.InterceptorResponse | ||
| import com.apollographql.apollo.interceptor.ApolloInterceptorChain | ||
| import io.sentry.HubAdapter | ||
| import io.sentry.IHub | ||
| import io.sentry.ISpan | ||
| import io.sentry.SentryLevel | ||
| import io.sentry.SpanStatus | ||
| import java.util.concurrent.Executor | ||
|
|
||
| class SentryApolloInterceptor( | ||
| private val hub: IHub = HubAdapter.getInstance(), | ||
| private val beforeSpan: BeforeSpanCallback? = null | ||
| ) : ApolloInterceptor { | ||
|
|
||
| constructor(hub: IHub) : this(hub, null) | ||
| constructor(beforeSpan: BeforeSpanCallback) : this(HubAdapter.getInstance(), beforeSpan) | ||
|
|
||
| override fun interceptAsync(request: InterceptorRequest, chain: ApolloInterceptorChain, dispatcher: Executor, callBack: CallBack) { | ||
| val activeSpan = hub.span | ||
| if (activeSpan == null) { | ||
| chain.proceedAsync(request, dispatcher, callBack) | ||
| } else { | ||
| val span = startChild(request, activeSpan) | ||
| val sentryTraceHeader = span.toSentryTrace() | ||
|
|
||
| // we have no access to URI, no way to verify tracing origins | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we address this before we ship it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looks like a limitation of apollo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can do with the response object, |
||
| val headers = request.requestHeaders.toBuilder().addHeader(sentryTraceHeader.name, sentryTraceHeader.value).build() | ||
| val requestWithHeader = request.toBuilder().requestHeaders(headers).build() | ||
| span.setData("operationId", requestWithHeader.operation.operationId()) | ||
| span.setData("variables", requestWithHeader.operation.variables().valueMap().toString()) | ||
|
|
||
| chain.proceedAsync(requestWithHeader, dispatcher, object : CallBack { | ||
| override fun onResponse(response: InterceptorResponse) { | ||
| // onResponse is called only for statuses 2xx | ||
| span.status = response.httpResponse.map { SpanStatus.fromHttpStatusCode(it.code(), SpanStatus.UNKNOWN) } | ||
| .or(SpanStatus.UNKNOWN) | ||
|
|
||
| finish(span, requestWithHeader, response) | ||
| callBack.onResponse(response) | ||
| } | ||
|
|
||
| override fun onFetch(sourceType: FetchSourceType) { | ||
| callBack.onFetch(sourceType) | ||
| } | ||
|
|
||
| override fun onFailure(e: ApolloException) { | ||
| span.apply { | ||
| status = if (e is ApolloHttpException) SpanStatus.fromHttpStatusCode(e.code(), SpanStatus.INTERNAL_ERROR) else SpanStatus.INTERNAL_ERROR | ||
| throwable = e | ||
| } | ||
| finish(span, requestWithHeader) | ||
| callBack.onFailure(e) | ||
| } | ||
|
|
||
| override fun onCompleted() { | ||
| callBack.onCompleted() | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| override fun dispose() {} | ||
|
|
||
| private fun startChild(request: InterceptorRequest, activeSpan: ISpan): ISpan { | ||
| val operation = request.operation.name().name() | ||
| val operationType = when (request.operation) { | ||
| is Query -> "query" | ||
| is Mutation -> "mutation" | ||
| is Subscription -> "subscription" | ||
| else -> request.operation.javaClass.simpleName | ||
| } | ||
| val description = "$operationType $operation" | ||
| return activeSpan.startChild(operation, description) | ||
| } | ||
|
|
||
| private fun finish(span: ISpan, request: InterceptorRequest, response: InterceptorResponse? = null) { | ||
| var newSpan: ISpan = span | ||
| if (beforeSpan != null) { | ||
| try { | ||
| newSpan = beforeSpan.execute(span, request, response) | ||
| } catch (e: Exception) { | ||
| hub.options.logger.log(SentryLevel.ERROR, "An error occurred while executing beforeSpan on ApolloInterceptor", e) | ||
| } | ||
| } | ||
| newSpan.finish() | ||
| } | ||
|
|
||
| /** | ||
| * The BeforeSpan callback | ||
| */ | ||
| interface BeforeSpanCallback { | ||
| /** | ||
| * Mutates span before being added. | ||
| * | ||
| * @param span the span to mutate or drop | ||
| * @param request the HTTP request executed by okHttp | ||
| * @param response the HTTP response received by okHttp | ||
| */ | ||
| fun execute(span: ISpan, request: InterceptorRequest, response: InterceptorResponse?): ISpan | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.