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

Add GraphQL Apollo Kotlin 4 integration #4166

Merged
merged 12 commits into from
Feb 24, 2025
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

- Add Apollo 4 integration ([#4166](https://github.com/getsentry/sentry-java/pull/4166))

## 8.2.0

### Breaking Changes
Expand Down
2 changes: 2 additions & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ object Config {
val composeCoil = "io.coil-kt:coil-compose:2.6.0"

val apolloKotlin = "com.apollographql.apollo3:apollo-runtime:3.8.2"
val apolloKotlin4 = "com.apollographql.apollo:apollo-runtime:4.1.1"

val sentryNativeNdk = "io.sentry:sentry-native-ndk:0.7.20"

Expand Down Expand Up @@ -250,6 +251,7 @@ object Config {
val SENTRY_SPRING_BOOT_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot.jakarta"
val SENTRY_OPENTELEMETRY_AGENT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.agent"
val SENTRY_APOLLO3_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo3"
val SENTRY_APOLLO4_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo4"
val SENTRY_APOLLO_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo"
val SENTRY_GRAPHQL_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.graphql"
val SENTRY_GRAPHQL22_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.graphql22"
Expand Down
50 changes: 50 additions & 0 deletions sentry-apollo-4/api/sentry-apollo-4.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
public final class io/sentry/apollo4/BuildConfig {
public static final field SENTRY_APOLLO4_SDK_NAME Ljava/lang/String;
public static final field VERSION_NAME Ljava/lang/String;
}

public final class io/sentry/apollo4/SentryApollo4BuilderExtensionsKt {
public static final fun sentryTracing (Lcom/apollographql/apollo/ApolloClient$Builder;)Lcom/apollographql/apollo/ApolloClient$Builder;
public static final fun sentryTracing (Lcom/apollographql/apollo/ApolloClient$Builder;Lio/sentry/IScopes;)Lcom/apollographql/apollo/ApolloClient$Builder;
public static final fun sentryTracing (Lcom/apollographql/apollo/ApolloClient$Builder;Lio/sentry/IScopes;Z)Lcom/apollographql/apollo/ApolloClient$Builder;
public static final fun sentryTracing (Lcom/apollographql/apollo/ApolloClient$Builder;Lio/sentry/IScopes;ZLjava/util/List;)Lcom/apollographql/apollo/ApolloClient$Builder;
public static final fun sentryTracing (Lcom/apollographql/apollo/ApolloClient$Builder;Lio/sentry/IScopes;ZLjava/util/List;Lio/sentry/apollo4/SentryApollo4HttpInterceptor$BeforeSpanCallback;)Lcom/apollographql/apollo/ApolloClient$Builder;
public static final fun sentryTracing (Lcom/apollographql/apollo/ApolloClient$Builder;ZLjava/util/List;Lio/sentry/apollo4/SentryApollo4HttpInterceptor$BeforeSpanCallback;)Lcom/apollographql/apollo/ApolloClient$Builder;
public static synthetic fun sentryTracing$default (Lcom/apollographql/apollo/ApolloClient$Builder;Lio/sentry/IScopes;ZLjava/util/List;Lio/sentry/apollo4/SentryApollo4HttpInterceptor$BeforeSpanCallback;ILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder;
public static synthetic fun sentryTracing$default (Lcom/apollographql/apollo/ApolloClient$Builder;ZLjava/util/List;Lio/sentry/apollo4/SentryApollo4HttpInterceptor$BeforeSpanCallback;ILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder;
}

public final class io/sentry/apollo4/SentryApollo4ClientException : java/lang/Exception {
public static final field Companion Lio/sentry/apollo4/SentryApollo4ClientException$Companion;
public fun <init> (Ljava/lang/String;)V
}

public final class io/sentry/apollo4/SentryApollo4ClientException$Companion {
}

public final class io/sentry/apollo4/SentryApollo4HttpInterceptor : com/apollographql/apollo/network/http/HttpInterceptor {
public static final field Companion Lio/sentry/apollo4/SentryApollo4HttpInterceptor$Companion;
public static final field DEFAULT_CAPTURE_FAILED_REQUESTS Z
public fun <init> ()V
public fun <init> (Lio/sentry/IScopes;)V
public fun <init> (Lio/sentry/IScopes;Lio/sentry/apollo4/SentryApollo4HttpInterceptor$BeforeSpanCallback;)V
public fun <init> (Lio/sentry/IScopes;Lio/sentry/apollo4/SentryApollo4HttpInterceptor$BeforeSpanCallback;Z)V
public fun <init> (Lio/sentry/IScopes;Lio/sentry/apollo4/SentryApollo4HttpInterceptor$BeforeSpanCallback;ZLjava/util/List;)V
public synthetic fun <init> (Lio/sentry/IScopes;Lio/sentry/apollo4/SentryApollo4HttpInterceptor$BeforeSpanCallback;ZLjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun intercept (Lcom/apollographql/apollo/api/http/HttpRequest;Lcom/apollographql/apollo/network/http/HttpInterceptorChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public abstract interface class io/sentry/apollo4/SentryApollo4HttpInterceptor$BeforeSpanCallback {
public abstract fun execute (Lio/sentry/ISpan;Lcom/apollographql/apollo/api/http/HttpRequest;Lcom/apollographql/apollo/api/http/HttpResponse;)Lio/sentry/ISpan;
}

public final class io/sentry/apollo4/SentryApollo4HttpInterceptor$Companion {
}

public final class io/sentry/apollo4/SentryApollo4Interceptor : com/apollographql/apollo/interceptor/ApolloInterceptor {
public fun <init> ()V
public fun <init> (Lio/sentry/IScopes;)V
public synthetic fun <init> (Lio/sentry/IScopes;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun intercept (Lcom/apollographql/apollo/api/ApolloRequest;Lcom/apollographql/apollo/interceptor/ApolloInterceptorChain;)Lkotlinx/coroutines/flow/Flow;
}

89 changes: 89 additions & 0 deletions sentry-apollo-4/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
kotlinOptions.languageVersion = Config.kotlinCompatibleLanguageVersion
}

dependencies {
api(projects.sentry)
api(projects.sentryKotlinExtensions)

compileOnly(Config.Libs.apolloKotlin4)

compileOnly(Config.CompileOnly.nopen)
errorprone(Config.CompileOnly.nopenChecker)
errorprone(Config.CompileOnly.errorprone)
errorprone(Config.CompileOnly.errorProneNullAway)
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.mockWebserver)
testImplementation(Config.Libs.apolloKotlin4)
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
testImplementation("org.jetbrains.kotlin:kotlin-reflect:2.0.0")
}

configure<SourceSetContainer> {
test {
java.srcDir("src/test/java")
}
}

jacoco {
toolVersion = Config.QualityPlugins.Jacoco.version
}

tasks.jacocoTestReport {
reports {
xml.required.set(true)
html.required.set(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")
}
}

buildConfig {
useJavaOutput()
packageName("io.sentry.apollo4")
buildConfigField("String", "SENTRY_APOLLO4_SDK_NAME", "\"${Config.Sentry.SENTRY_APOLLO4_SDK_NAME}\"")
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.sentry.apollo4

/**
* Common constants used across the module
*/
internal const val OPERATION_ID_HEADER_NAME = "SENTRY-APOLLO-4-OPERATION-ID"
internal const val OPERATION_NAME_HEADER_NAME = "SENTRY-APOLLO-4-OPERATION-NAME"
internal const val OPERATION_TYPE_HEADER_NAME = "SENTRY-APOLLO-4-OPERATION-TYPE"
internal const val VARIABLES_HEADER_NAME = "SENTRY-APOLLO-4-VARIABLES"
internal val INTERNAL_HEADER_NAMES by lazy {
listOf(
OPERATION_ID_HEADER_NAME,
OPERATION_NAME_HEADER_NAME,
OPERATION_TYPE_HEADER_NAME,
VARIABLES_HEADER_NAME
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.sentry.apollo4

import com.apollographql.apollo.ApolloClient
import io.sentry.IScopes
import io.sentry.ScopesAdapter
import io.sentry.SentryOptions.DEFAULT_PROPAGATION_TARGETS
import io.sentry.apollo4.SentryApollo4HttpInterceptor.Companion.DEFAULT_CAPTURE_FAILED_REQUESTS

@JvmOverloads
fun ApolloClient.Builder.sentryTracing(
scopes: IScopes = ScopesAdapter.getInstance(),
captureFailedRequests: Boolean = DEFAULT_CAPTURE_FAILED_REQUESTS,
failedRequestTargets: List<String> = listOf(DEFAULT_PROPAGATION_TARGETS),
beforeSpan: SentryApollo4HttpInterceptor.BeforeSpanCallback? = null
): ApolloClient.Builder {
addInterceptor(SentryApollo4Interceptor())
addHttpInterceptor(
SentryApollo4HttpInterceptor(
scopes = scopes,
captureFailedRequests = captureFailedRequests,
failedRequestTargets = failedRequestTargets,
beforeSpan = beforeSpan
)
)
return this
}

fun ApolloClient.Builder.sentryTracing(
captureFailedRequests: Boolean = DEFAULT_CAPTURE_FAILED_REQUESTS,
failedRequestTargets: List<String> = listOf(DEFAULT_PROPAGATION_TARGETS),
beforeSpan: SentryApollo4HttpInterceptor.BeforeSpanCallback? = null
): ApolloClient.Builder {
return sentryTracing(
scopes = ScopesAdapter.getInstance(),
captureFailedRequests = captureFailedRequests,
failedRequestTargets = failedRequestTargets,
beforeSpan = beforeSpan
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.sentry.apollo4

/**
* Used for holding an Apollo4 client error, for example. An integration that does not throw when API
* returns 4xx, 5xx or the `errors` field.
*/
class SentryApollo4ClientException(message: String?) : Exception(message) {
companion object {
private const val serialVersionUID = 4312160066430858144L
}
}
Loading
Loading