Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report_java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ body:
- sentry-jdbc
- sentry-apollo
- sentry-apollo-3
- sentry-apollo-4
- sentry-apollo-5
- sentry-kotlin-extensions
- sentry-opentelemetry-agent
- sentry-opentelemetry-agentless
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Features

- Add `sentry-apollo-5` integration for Apollo Kotlin 5, providing HTTP tracing and failed GraphQL request reporting ([#6074](https://github.com/getsentry/sentry-java/pull/6074))

### Fixes

- `SentryTraced` now checks for its owning transaction dynamically rather than once per app process. The latter caused `SentryTraced` spans to be dropped process-wide once the original transaction finished ([#6057](https://github.com/getsentry/sentry-java/pull/6057))
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Sentry SDK for Java and Android
| sentry-apollo | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-apollo?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-apollo) | 21 |
| sentry-apollo-3 | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-apollo-3?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-apollo-3) | 21 |
| sentry-apollo-4 | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-apollo-4?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-apollo-4) | 21 |
| sentry-apollo-5 | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-apollo-5?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-apollo-5) | 21 |
| sentry-kotlin-extensions | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-kotlin-extensions?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-kotlin-extensions) | 21 |
| sentry-ktor-client | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-ktor-client?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-ktor-client) | 21 |
| sentry-servlet | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-servlet?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-servlet) | |
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ object Config {
val SENTRY_OPENFEIGN_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.openfeign"
val SENTRY_APOLLO3_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo3"
val SENTRY_APOLLO4_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo4"
val SENTRY_APOLLO5_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo5"
val SENTRY_APOLLO_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.apollo"
val SENTRY_GRAPHQL_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.graphql"
val SENTRY_GRAPHQL_CORE_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.graphql-core"
Expand Down
5 changes: 5 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ apollo2-coroutines = { module = "com.apollographql.apollo:apollo-coroutines-supp
apollo2-runtime = { module = "com.apollographql.apollo:apollo-runtime", version.ref = "apollo" }
apollo3-kotlin = { module = "com.apollographql.apollo3:apollo-runtime", version = "3.8.6" }
apollo4-kotlin = { module = "com.apollographql.apollo:apollo-runtime", version = "4.1.1" }
# sentry-apollo-5 compiles against 5.0.0 (supported floor) and tests against the latest 5.x
apollo5-kotlin = { module = "com.apollographql.apollo:apollo-runtime", version = "5.0.0" }
apollo5-kotlin-latest = { module = "com.apollographql.apollo:apollo-runtime", version = "5.1.0" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.3.0" }
androidx-annotation = { module = "androidx.annotation:annotation", version = "1.9.1" }
androidx-activity = { module = "androidx.activity:activity", version = "1.8.2" }
Expand Down Expand Up @@ -270,6 +273,8 @@ mockito-kotlin-spring7 = { module = "org.mockito.kotlin:mockito-kotlin", version
mockito-inline = { module = "org.mockito:mockito-inline", version = "4.8.0" }
msgpack = { module = "org.msgpack:msgpack-core", version = "0.9.8" }
okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
# apollo-runtime 5.x pulls in okhttp 5.x; mockwebserver 4.x is incompatible with that classpath
okhttp-mockwebserver5 = { module = "com.squareup.okhttp3:mockwebserver", version = "5.3.0" }
okio = { module = "com.squareup.okio:okio", version = "1.13.0" }
roboelectric = { module = "org.robolectric:robolectric", version = "4.15" }

Expand Down
39 changes: 39 additions & 0 deletions sentry-apollo-5/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# sentry-apollo-5

This module provides HTTP tracing and failed GraphQL request reporting for [Apollo Kotlin 5](https://www.apollographql.com/docs/kotlin/).

Please consult the documentation on how to install and use this integration in the Sentry Docs for [Android](https://docs.sentry.io/platforms/android/integrations/apollo5/) or [Java](https://docs.sentry.io/platforms/java/tracing/instrumentation/apollo5/).

## Usage

Add `io.sentry:sentry-apollo-5` and install the integration while building the Apollo client:

```kotlin
val apolloClient =
ApolloClient.Builder()
.serverUrl("https://example.com/graphql")
.sentryTracing()
.build()
```

The builder extension installs the Apollo interceptor before the cache and the Sentry HTTP interceptor.

Apollo rejects builder HTTP interceptors when a custom `NetworkTransport` is configured, so do not use `sentryTracing()` in that case. Add `SentryApollo5Interceptor` to the client builder and `SentryApollo5HttpInterceptor` to the custom transport manually.

Operation metadata is propagated between these interceptors through Apollo's execution context. Apollo's default HTTP request composer copies this context automatically. A custom `HttpRequestComposer` must copy it explicitly when building the HTTP request:

```kotlin
HttpRequest.Builder(method, url)
.addExecutionContext(apolloRequest.executionContext)
.build()
```

An entirely custom `NetworkTransport` must expose the metadata to `SentryApollo5HttpInterceptor` in the same way.

## Known limitations

- Failed GraphQL request detection matches the raw JSON response body for an `errors` field.
- Multipart and incremental responses retain the Apollo 4 failed-request inspection limitation.
- WebSocket subscriptions are not instrumented.
- Batching behavior depends on HTTP interceptor ordering.
- Normalized-cache hits do not create HTTP spans.
51 changes: 51 additions & 0 deletions sentry-apollo-5/api/sentry-apollo-5.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
public final class io/sentry/apollo5/BuildConfig {
public static final field SENTRY_APOLLO5_SDK_NAME Ljava/lang/String;
public static final field VERSION_NAME Ljava/lang/String;
}

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

public final class io/sentry/apollo5/SentryApollo5ClientException$Companion {
}

public final class io/sentry/apollo5/SentryApollo5HttpInterceptor : com/apollographql/apollo/network/http/HttpInterceptor {
public static final field Companion Lio/sentry/apollo5/SentryApollo5HttpInterceptor$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/apollo5/SentryApollo5HttpInterceptor$BeforeSpanCallback;)V
public fun <init> (Lio/sentry/IScopes;Lio/sentry/apollo5/SentryApollo5HttpInterceptor$BeforeSpanCallback;Z)V
public fun <init> (Lio/sentry/IScopes;Lio/sentry/apollo5/SentryApollo5HttpInterceptor$BeforeSpanCallback;ZLjava/util/List;)V
public synthetic fun <init> (Lio/sentry/IScopes;Lio/sentry/apollo5/SentryApollo5HttpInterceptor$BeforeSpanCallback;ZLjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun dispose ()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/apollo5/SentryApollo5HttpInterceptor$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/apollo5/SentryApollo5HttpInterceptor$Companion {
}

public final class io/sentry/apollo5/SentryApollo5Interceptor : 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;
}

public final class io/sentry/apollo5/SentryApolloBuilderExtensionsKt {
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/apollo5/SentryApollo5HttpInterceptor$BeforeSpanCallback;)Lcom/apollographql/apollo/ApolloClient$Builder;
public static final fun sentryTracing (Lcom/apollographql/apollo/ApolloClient$Builder;ZLjava/util/List;Lio/sentry/apollo5/SentryApollo5HttpInterceptor$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/apollo5/SentryApollo5HttpInterceptor$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/apollo5/SentryApollo5HttpInterceptor$BeforeSpanCallback;ILjava/lang/Object;)Lcom/apollographql/apollo/ApolloClient$Builder;
}

73 changes: 73 additions & 0 deletions sentry-apollo-5/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import net.ltgt.gradle.errorprone.errorprone
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
`java-library`
id("io.sentry.javadoc")
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
alias(libs.plugins.buildconfig)
id("io.sentry.animalsniffer.android")
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
compilerOptions.languageVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
compilerOptions.apiVersion = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
}

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

compileOnly(libs.apollo5.kotlin)
compileOnly(libs.jetbrains.annotations)
compileOnly(libs.nopen.annotations)
errorprone(libs.errorprone.core)
errorprone(libs.nopen.checker)
errorprone(libs.nullaway)

// tests
testImplementation(projects.sentryTestSupport)
testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(libs.apollo5.kotlin.latest)
testImplementation(libs.google.truth)
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.kotlinx.coroutines)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.mockito.inline)
testImplementation(libs.okhttp.mockwebserver5)
}

tasks.withType<JavaCompile>().configureEach {
options.errorprone {
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
option("NullAway:AnnotatedPackages", "io.sentry")
}
}

buildConfig {
useJavaOutput()
packageName("io.sentry.apollo5")
buildConfigField(
"String",
"SENTRY_APOLLO5_SDK_NAME",
"\"${Config.Sentry.SENTRY_APOLLO5_SDK_NAME}\"",
)
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
}

tasks.jar {
manifest {
attributes(
"Sentry-Version-Name" to project.version,
"Sentry-SDK-Name" to Config.Sentry.SENTRY_APOLLO5_SDK_NAME,
"Sentry-SDK-Package-Name" to "maven:io.sentry:sentry-apollo-5",
"Implementation-Vendor" to "Sentry",
"Implementation-Title" to project.name,
"Implementation-Version" to project.version,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.sentry.apollo5

import com.apollographql.apollo.api.ExecutionContext

internal data class SentryApollo5OperationContext(
val operationId: String,
val operationName: String,
val operationType: String,
val variables: String?,
) : ExecutionContext.Element {
override val key: ExecutionContext.Key<*>
get() = Key

companion object Key : ExecutionContext.Key<SentryApollo5OperationContext>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.sentry.apollo5

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