Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Feat: OkHttp callback for Customising the Span (#1478)
* Feat: Add breadcrumb in Spring RestTemplate integration (#1481)
* Fix: Cloning Stack (#1483)
* Feat: Add coroutines support (#1479)

# 5.0.0-beta.4

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 @@ -80,6 +80,8 @@ object Config {
private val retrofit2Group = "com.squareup.retrofit2"
val retrofit2 = "$retrofit2Group:retrofit:$retrofit2Version"
val retrofit2Gson = "$retrofit2Group:converter-gson:$retrofit2Version"

val coroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
}

object AnnotationProcessors {
Expand Down
16 changes: 16 additions & 0 deletions sentry-kotlin-extensions/api/sentry-kotlin-extensions.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
public final class io/sentry/kotlin/SentryContext : kotlin/coroutines/AbstractCoroutineContextElement, kotlinx/coroutines/ThreadContextElement {
public static final field Key Lio/sentry/kotlin/SentryContext$Key;
public fun <init> ()V
public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
public fun restoreThreadContext (Lkotlin/coroutines/CoroutineContext;Lio/sentry/IHub;)V
public synthetic fun restoreThreadContext (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Object;)V
public fun updateThreadContext (Lkotlin/coroutines/CoroutineContext;)Lio/sentry/IHub;
public synthetic fun updateThreadContext (Lkotlin/coroutines/CoroutineContext;)Ljava/lang/Object;
}

public final class io/sentry/kotlin/SentryContext$Key : kotlin/coroutines/CoroutineContext$Key {
}

66 changes: 66 additions & 0 deletions sentry-kotlin-extensions/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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.QualityPlugins.detektPlugin)
}

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(project(":sentry"))
implementation(Config.Libs.coroutinesCore)

compileOnly(Config.CompileOnly.nopen)
errorprone(Config.CompileOnly.nopenChecker)
errorprone(Config.CompileOnly.errorprone)
errorproneJavac(Config.CompileOnly.errorProneJavac8)
compileOnly(Config.CompileOnly.jetbrainsAnnotations)

// tests
testImplementation(project(":sentry-test-support"))
testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(Config.TestLibs.kotlinTestJunit)
testImplementation(Config.TestLibs.mockitoKotlin)
}

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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.sentry.kotlin

import io.sentry.IHub
import io.sentry.Sentry
import kotlin.coroutines.AbstractCoroutineContextElement
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.ThreadContextElement

/**
* Sentry context element for [CoroutineContext].
*/
class SentryContext : ThreadContextElement<IHub>, AbstractCoroutineContextElement(Key) {

companion object Key : CoroutineContext.Key<SentryContext>

private val hub: IHub = Sentry.getCurrentHub().clone()

override fun updateThreadContext(context: CoroutineContext): IHub {
val oldState = Sentry.getCurrentHub()
Sentry.setCurrentHub(hub)
return oldState
}

override fun restoreThreadContext(context: CoroutineContext, oldState: IHub) {
Sentry.setCurrentHub(oldState)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.sentry.kotlin

import io.sentry.Sentry
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

class SentryContextTest {

@BeforeTest
fun init() {
Sentry.init("https://key@sentry.io/123")
}

@AfterTest
fun close() {
Sentry.close()
}

@Test
fun testContextIsNotPassedByDefaultBetweenCoroutines() = runBlocking {
Sentry.setTag("parent", "parentValue")
val c1 = launch(SentryContext()) {
Sentry.setTag("c1", "c1value")
assertEquals("c1value", getTag("c1"))
assertEquals("parentValue", getTag("parent"))
assertNull(getTag("c2"))
}
val c2 = launch(SentryContext()) {
Sentry.setTag("c2", "c2value")
assertEquals("c2value", getTag("c2"))
assertEquals("parentValue", getTag("parent"))
assertNull(getTag("c1"))
}
listOf(c1, c2).joinAll()
assertNull(getTag("c1"))
assertNull(getTag("c2"))
}

@Test
fun testContextIsPassedToChildCoroutines() = runBlocking {
val c1 = launch(SentryContext()) {
Sentry.setTag("c1", "c1value")
assertEquals("c1value", getTag("c1"))
assertNull(getTag("c2"))
launch() {
Sentry.setTag("c2", "c2value")
assertEquals("c2value", getTag("c2"))
assertEquals("c1value", getTag("c1"))
}.join()
}
c1.join()
}

@Test
fun testContextPassedWhileOnMainThread() {
Sentry.setTag("myKey", "myValue")
runBlocking {
assertEquals("myValue", getTag("myKey"))
}
}

@Test
fun testContextCanBePassedWhileOnMainThread() {
Sentry.setTag("myKey", "myValue")
runBlocking(SentryContext()) {
assertEquals("myValue", getTag("myKey"))
}
}

@Test
fun testContextMayBeEmpty() {
runBlocking(SentryContext()) {
assertNull(getTag("myKey"))
}
}

private fun getTag(tag: String): String? {
var value: String? = null
Sentry.configureScope {
value = it.tags[tag]
}
return value
}
}
3 changes: 3 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ public final class io/sentry/Scope : java/lang/Cloneable {
public fun getLevel ()Lio/sentry/SentryLevel;
public fun getRequest ()Lio/sentry/protocol/Request;
public fun getSpan ()Lio/sentry/ISpan;
public fun getTags ()Ljava/util/Map;
public fun getTransaction ()Lio/sentry/ITransaction;
public fun getTransactionName ()Ljava/lang/String;
public fun getUser ()Lio/sentry/protocol/User;
Expand Down Expand Up @@ -558,6 +559,7 @@ public final class io/sentry/Sentry {
public static fun configureScope (Lio/sentry/ScopeCallback;)V
public static fun endSession ()V
public static fun flush (J)V
public static fun getCurrentHub ()Lio/sentry/IHub;
public static fun getLastEventId ()Lio/sentry/protocol/SentryId;
public static fun getSpan ()Lio/sentry/ISpan;
public static fun init ()V
Expand All @@ -572,6 +574,7 @@ public final class io/sentry/Sentry {
public static fun pushScope ()V
public static fun removeExtra (Ljava/lang/String;)V
public static fun removeTag (Ljava/lang/String;)V
public static fun setCurrentHub (Lio/sentry/IHub;)V
public static fun setExtra (Ljava/lang/String;Ljava/lang/String;)V
public static fun setFingerprint (Ljava/util/List;)V
public static fun setLevel (Lio/sentry/SentryLevel;)V
Expand Down
8 changes: 5 additions & 3 deletions sentry/src/main/java/io/sentry/Scope.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.sentry.protocol.Contexts;
import io.sentry.protocol.Request;
import io.sentry.protocol.User;
import io.sentry.util.CollectionUtils;
import io.sentry.util.Objects;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -337,9 +338,10 @@ public void clear() {
*
* @return the tags map
*/
@NotNull
Map<String, String> getTags() {
return tags;
@ApiStatus.Internal
@SuppressWarnings("NullAway") // tags are never null
public @NotNull Map<String, String> getTags() {
return CollectionUtils.newConcurrentHashMap(tags);
}

/**
Expand Down
8 changes: 7 additions & 1 deletion sentry/src/main/java/io/sentry/Sentry.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ private Sentry() {}
*
* @return the hub
*/
static @NotNull IHub getCurrentHub() {
@ApiStatus.Internal // exposed for the coroutines integration in SentryContext
public static @NotNull IHub getCurrentHub() {
if (globalHubMode) {
return mainHub;
}
Expand All @@ -45,6 +46,11 @@ private Sentry() {}
return hub;
}

@ApiStatus.Internal // exposed for the coroutines integration in SentryContext
public static void setCurrentHub(final @NotNull IHub hub) {
currentHub.set(hub);
}

/**
* Check if the current Hub is enabled/active.
*
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ rootProject.buildFileName = "build.gradle.kts"

include(
"sentry",
"sentry-kotlin-extensions",
"sentry-android-core",
"sentry-android-ndk",
"sentry-android",
Expand Down