-
-
Notifications
You must be signed in to change notification settings - Fork 442
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: Performance support for Android Apollo (#1705)
- Loading branch information
1 parent
87e9f5f
commit 1f575c6
Showing
10 changed files
with
984 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
112 changes: 112 additions & 0 deletions
112
sentry-apollo/src/main/java/io/sentry/apollo/SentryApolloInterceptor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
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 | ||
} | ||
} |
Oops, something went wrong.