diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cfdf341..ff46d1ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Add APM API ([#155](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/155)) + ### Dependencies - Bump Java SDK from v6.33.1 to v7.0.0 ([#157](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/157)) diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index fb608a17..2d798bfc 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -2,9 +2,9 @@ - SwallowedException:CocoaScopeProvider.kt$CocoaScopeProvider$e: Throwable + SwallowedException:ScopeAdapter.apple.kt$ScopeAdapter$e: Throwable SwallowedException:SentryLevel.kt$SentryLevel.Companion$throwable: Throwable - TooGenericExceptionCaught:CocoaScopeProvider.kt$CocoaScopeProvider$e: Throwable + TooGenericExceptionCaught:ScopeAdapter.apple.kt$ScopeAdapter$e: Throwable TooGenericExceptionCaught:SentryLevel.kt$SentryLevel.Companion$throwable: Throwable TooGenericExceptionThrown:SentryKMP.kt$Sentry$throw RuntimeException("Uncaught Exception from Kotlin Multiplatform.") diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index 818763de..45eb57f0 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -13,6 +13,10 @@ style: UnusedPrivateMember: excludes: - "**/Attachment.kt" + MagicNumber: + excludes: + - "**/SpanStatus*" + - "**/TransactionNameSource*" naming: MatchingDeclarationName: active: false @@ -21,12 +25,15 @@ complexity: excludes: [ "**/SentryKMP.kt", "**/SentryBridge.*", - "**/CocoaScopeProvider.kt", + "**/ScopeAdapter.*", "**/Scope.kt", - "**/JvmScopeProvider.kt", "**/Breadcrumb.kt", ] LongMethod: excludes: - - "**/SentryOptionsExtensions.*" + - "**/SentryOptions*" + CyclomaticComplexMethod: + excludes: + - "**/SpanStatus*" + - "**/SentryOptions*" diff --git a/sentry-kotlin-multiplatform/api/android/sentry-kotlin-multiplatform.api b/sentry-kotlin-multiplatform/api/android/sentry-kotlin-multiplatform.api index 89233344..063c8c16 100644 --- a/sentry-kotlin-multiplatform/api/android/sentry-kotlin-multiplatform.api +++ b/sentry-kotlin-multiplatform/api/android/sentry-kotlin-multiplatform.api @@ -45,6 +45,19 @@ public final class io/sentry/kotlin/multiplatform/HttpStatusCodeRange { public final class io/sentry/kotlin/multiplatform/HttpStatusCodeRange$Companion { } +public final class io/sentry/kotlin/multiplatform/SamplingContext { + public fun (Lio/sentry/kotlin/multiplatform/TransactionContext;Ljava/util/Map;)V + public final fun component1 ()Lio/sentry/kotlin/multiplatform/TransactionContext; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Lio/sentry/kotlin/multiplatform/TransactionContext;Ljava/util/Map;)Lio/sentry/kotlin/multiplatform/SamplingContext; + public static synthetic fun copy$default (Lio/sentry/kotlin/multiplatform/SamplingContext;Lio/sentry/kotlin/multiplatform/TransactionContext;Ljava/util/Map;ILjava/lang/Object;)Lio/sentry/kotlin/multiplatform/SamplingContext; + public fun equals (Ljava/lang/Object;)Z + public final fun getCustomSamplingContext ()Ljava/util/Map; + public final fun getTransactionContext ()Lio/sentry/kotlin/multiplatform/TransactionContext; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public abstract interface class io/sentry/kotlin/multiplatform/Scope { public abstract fun addAttachment (Lio/sentry/kotlin/multiplatform/Attachment;)V public abstract fun addBreadcrumb (Lio/sentry/kotlin/multiplatform/protocol/Breadcrumb;)V @@ -82,9 +95,14 @@ public final class io/sentry/kotlin/multiplatform/Sentry { public final fun close ()V public final fun configureScope (Lkotlin/jvm/functions/Function1;)V public final fun crash ()V + public final fun getSpan ()Lio/sentry/kotlin/multiplatform/Span; public final fun init (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V public final fun init (Lkotlin/jvm/functions/Function1;)V public final fun setUser (Lio/sentry/kotlin/multiplatform/protocol/User;)V + public final fun startTransaction (Lio/sentry/kotlin/multiplatform/TransactionContext;Ljava/util/Map;)Lio/sentry/kotlin/multiplatform/Span; + public final fun startTransaction (Lio/sentry/kotlin/multiplatform/TransactionContext;Ljava/util/Map;Z)Lio/sentry/kotlin/multiplatform/Span; + public final fun startTransaction (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/Span; + public final fun startTransaction (Ljava/lang/String;Ljava/lang/String;Z)Lio/sentry/kotlin/multiplatform/Span; } public abstract class io/sentry/kotlin/multiplatform/SentryBaseEvent { @@ -178,6 +196,7 @@ public class io/sentry/kotlin/multiplatform/SentryOptions { public final fun getSdk ()Lio/sentry/kotlin/multiplatform/protocol/SdkVersion; public final fun getSessionTrackingIntervalMillis ()J public final fun getTracesSampleRate ()Ljava/lang/Double; + public final fun getTracesSampler ()Lkotlin/jvm/functions/Function1; public final fun setAttachScreenshot (Z)V public final fun setAttachStackTrace (Z)V public final fun setAttachThreads (Z)V @@ -199,6 +218,78 @@ public class io/sentry/kotlin/multiplatform/SentryOptions { public final fun setSdk (Lio/sentry/kotlin/multiplatform/protocol/SdkVersion;)V public final fun setSessionTrackingIntervalMillis (J)V public final fun setTracesSampleRate (Ljava/lang/Double;)V + public final fun setTracesSampler (Lkotlin/jvm/functions/Function1;)V +} + +public abstract interface class io/sentry/kotlin/multiplatform/Span { + public abstract fun finish ()V + public abstract fun finish (Lio/sentry/kotlin/multiplatform/SpanStatus;)V + public abstract fun getData (Ljava/lang/String;)Ljava/lang/Object; + public abstract fun getDescription ()Ljava/lang/String; + public abstract fun getOperation ()Ljava/lang/String; + public abstract fun getParentSpanId ()Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public abstract fun getSpanId ()Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public abstract fun getStatus ()Lio/sentry/kotlin/multiplatform/SpanStatus; + public abstract fun getTag (Ljava/lang/String;)Ljava/lang/String; + public abstract fun isFinished ()Z + public abstract fun setData (Ljava/lang/String;Ljava/lang/Object;)V + public abstract fun setDescription (Ljava/lang/String;)V + public abstract fun setOperation (Ljava/lang/String;)V + public abstract fun setStatus (Lio/sentry/kotlin/multiplatform/SpanStatus;)V + public abstract fun setTag (Ljava/lang/String;Ljava/lang/String;)V + public abstract fun startChild (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/Span; + public abstract fun startChild (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/Span; +} + +public abstract interface class io/sentry/kotlin/multiplatform/SpanContext { + public abstract fun getDescription ()Ljava/lang/String; + public abstract fun getOperation ()Ljava/lang/String; + public abstract fun getParentSpanId ()Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public abstract fun getSampled ()Ljava/lang/Boolean; + public abstract fun getSpanId ()Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public abstract fun getTraceId ()Lio/sentry/kotlin/multiplatform/protocol/SentryId; +} + +public final class io/sentry/kotlin/multiplatform/SpanStatus : java/lang/Enum { + public static final field ABORTED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field ALREADY_EXISTS Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field CANCELLED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field Companion Lio/sentry/kotlin/multiplatform/SpanStatus$Companion; + public static final field DATA_LOSS Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field DEADLINE_EXCEEDED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field FAILED_PRECONDITION Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field INTERNAL_ERROR Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field INVALID_ARGUMENT Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field NOT_FOUND Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field OK Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field OUT_OF_RANGE Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field PERMISSION_DENIED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field RESOURCE_EXHAUSTED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field UNAUTHENTICATED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field UNAVAILABLE Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field UNIMPLEMENTED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field UNKNOWN Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field UNKNOWN_ERROR Lio/sentry/kotlin/multiplatform/SpanStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/SpanStatus; + public static fun values ()[Lio/sentry/kotlin/multiplatform/SpanStatus; +} + +public final class io/sentry/kotlin/multiplatform/SpanStatus$Companion { + public final fun fromHttpStatusCode (I)Lio/sentry/kotlin/multiplatform/SpanStatus; + public final fun fromHttpStatusCode (Ljava/lang/Integer;Lio/sentry/kotlin/multiplatform/SpanStatus;)Lio/sentry/kotlin/multiplatform/SpanStatus; +} + +public abstract interface class io/sentry/kotlin/multiplatform/TransactionContext : io/sentry/kotlin/multiplatform/SpanContext { + public abstract fun getName ()Ljava/lang/String; + public abstract fun getParentSampled ()Ljava/lang/Boolean; + public abstract fun getTransactionNameSource ()Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; +} + +public final class io/sentry/kotlin/multiplatform/TransactionContextKt { + public static final fun TransactionContext (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/TransactionContext; + public static final fun TransactionContext (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/TransactionContext; + public static final fun TransactionContext (Ljava/lang/String;Ljava/lang/String;Z)Lio/sentry/kotlin/multiplatform/TransactionContext; } public final class io/sentry/kotlin/multiplatform/protocol/Breadcrumb { @@ -327,6 +418,34 @@ public final class io/sentry/kotlin/multiplatform/protocol/SentryId$Companion { public final fun getEMPTY_ID ()Lio/sentry/kotlin/multiplatform/protocol/SentryId; } +public final class io/sentry/kotlin/multiplatform/protocol/SpanId { + public static final field Companion Lio/sentry/kotlin/multiplatform/protocol/SpanId$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public static synthetic fun copy$default (Lio/sentry/kotlin/multiplatform/protocol/SpanId;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public fun equals (Ljava/lang/Object;)Z + public final fun getSpanIdString ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/kotlin/multiplatform/protocol/SpanId$Companion { + public final fun getEMPTY_ID ()Lio/sentry/kotlin/multiplatform/protocol/SpanId; +} + +public final class io/sentry/kotlin/multiplatform/protocol/TransactionNameSource : java/lang/Enum { + public static final field COMPONENT Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static final field CUSTOM Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static final field ROUTE Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static final field TASK Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static final field URL Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static final field VIEW Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static fun values ()[Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; +} + public final class io/sentry/kotlin/multiplatform/protocol/User { public fun ()V public fun (Lio/sentry/kotlin/multiplatform/protocol/User;)V diff --git a/sentry-kotlin-multiplatform/api/jvm/sentry-kotlin-multiplatform.api b/sentry-kotlin-multiplatform/api/jvm/sentry-kotlin-multiplatform.api index e1f8a5cc..6aaeb105 100644 --- a/sentry-kotlin-multiplatform/api/jvm/sentry-kotlin-multiplatform.api +++ b/sentry-kotlin-multiplatform/api/jvm/sentry-kotlin-multiplatform.api @@ -42,6 +42,19 @@ public final class io/sentry/kotlin/multiplatform/HttpStatusCodeRange { public final class io/sentry/kotlin/multiplatform/HttpStatusCodeRange$Companion { } +public final class io/sentry/kotlin/multiplatform/SamplingContext { + public fun (Lio/sentry/kotlin/multiplatform/TransactionContext;Ljava/util/Map;)V + public final fun component1 ()Lio/sentry/kotlin/multiplatform/TransactionContext; + public final fun component2 ()Ljava/util/Map; + public final fun copy (Lio/sentry/kotlin/multiplatform/TransactionContext;Ljava/util/Map;)Lio/sentry/kotlin/multiplatform/SamplingContext; + public static synthetic fun copy$default (Lio/sentry/kotlin/multiplatform/SamplingContext;Lio/sentry/kotlin/multiplatform/TransactionContext;Ljava/util/Map;ILjava/lang/Object;)Lio/sentry/kotlin/multiplatform/SamplingContext; + public fun equals (Ljava/lang/Object;)Z + public final fun getCustomSamplingContext ()Ljava/util/Map; + public final fun getTransactionContext ()Lio/sentry/kotlin/multiplatform/TransactionContext; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public abstract interface class io/sentry/kotlin/multiplatform/Scope { public abstract fun addAttachment (Lio/sentry/kotlin/multiplatform/Attachment;)V public abstract fun addBreadcrumb (Lio/sentry/kotlin/multiplatform/protocol/Breadcrumb;)V @@ -79,9 +92,14 @@ public final class io/sentry/kotlin/multiplatform/Sentry { public final fun close ()V public final fun configureScope (Lkotlin/jvm/functions/Function1;)V public final fun crash ()V + public final fun getSpan ()Lio/sentry/kotlin/multiplatform/Span; public final fun init (Lio/sentry/kotlin/multiplatform/Context;Lkotlin/jvm/functions/Function1;)V public final fun init (Lkotlin/jvm/functions/Function1;)V public final fun setUser (Lio/sentry/kotlin/multiplatform/protocol/User;)V + public final fun startTransaction (Lio/sentry/kotlin/multiplatform/TransactionContext;Ljava/util/Map;)Lio/sentry/kotlin/multiplatform/Span; + public final fun startTransaction (Lio/sentry/kotlin/multiplatform/TransactionContext;Ljava/util/Map;Z)Lio/sentry/kotlin/multiplatform/Span; + public final fun startTransaction (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/Span; + public final fun startTransaction (Ljava/lang/String;Ljava/lang/String;Z)Lio/sentry/kotlin/multiplatform/Span; } public abstract class io/sentry/kotlin/multiplatform/SentryBaseEvent { @@ -175,6 +193,7 @@ public class io/sentry/kotlin/multiplatform/SentryOptions { public final fun getSdk ()Lio/sentry/kotlin/multiplatform/protocol/SdkVersion; public final fun getSessionTrackingIntervalMillis ()J public final fun getTracesSampleRate ()Ljava/lang/Double; + public final fun getTracesSampler ()Lkotlin/jvm/functions/Function1; public final fun setAttachScreenshot (Z)V public final fun setAttachStackTrace (Z)V public final fun setAttachThreads (Z)V @@ -196,6 +215,78 @@ public class io/sentry/kotlin/multiplatform/SentryOptions { public final fun setSdk (Lio/sentry/kotlin/multiplatform/protocol/SdkVersion;)V public final fun setSessionTrackingIntervalMillis (J)V public final fun setTracesSampleRate (Ljava/lang/Double;)V + public final fun setTracesSampler (Lkotlin/jvm/functions/Function1;)V +} + +public abstract interface class io/sentry/kotlin/multiplatform/Span { + public abstract fun finish ()V + public abstract fun finish (Lio/sentry/kotlin/multiplatform/SpanStatus;)V + public abstract fun getData (Ljava/lang/String;)Ljava/lang/Object; + public abstract fun getDescription ()Ljava/lang/String; + public abstract fun getOperation ()Ljava/lang/String; + public abstract fun getParentSpanId ()Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public abstract fun getSpanId ()Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public abstract fun getStatus ()Lio/sentry/kotlin/multiplatform/SpanStatus; + public abstract fun getTag (Ljava/lang/String;)Ljava/lang/String; + public abstract fun isFinished ()Z + public abstract fun setData (Ljava/lang/String;Ljava/lang/Object;)V + public abstract fun setDescription (Ljava/lang/String;)V + public abstract fun setOperation (Ljava/lang/String;)V + public abstract fun setStatus (Lio/sentry/kotlin/multiplatform/SpanStatus;)V + public abstract fun setTag (Ljava/lang/String;Ljava/lang/String;)V + public abstract fun startChild (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/Span; + public abstract fun startChild (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/Span; +} + +public abstract interface class io/sentry/kotlin/multiplatform/SpanContext { + public abstract fun getDescription ()Ljava/lang/String; + public abstract fun getOperation ()Ljava/lang/String; + public abstract fun getParentSpanId ()Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public abstract fun getSampled ()Ljava/lang/Boolean; + public abstract fun getSpanId ()Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public abstract fun getTraceId ()Lio/sentry/kotlin/multiplatform/protocol/SentryId; +} + +public final class io/sentry/kotlin/multiplatform/SpanStatus : java/lang/Enum { + public static final field ABORTED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field ALREADY_EXISTS Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field CANCELLED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field Companion Lio/sentry/kotlin/multiplatform/SpanStatus$Companion; + public static final field DATA_LOSS Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field DEADLINE_EXCEEDED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field FAILED_PRECONDITION Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field INTERNAL_ERROR Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field INVALID_ARGUMENT Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field NOT_FOUND Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field OK Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field OUT_OF_RANGE Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field PERMISSION_DENIED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field RESOURCE_EXHAUSTED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field UNAUTHENTICATED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field UNAVAILABLE Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field UNIMPLEMENTED Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field UNKNOWN Lio/sentry/kotlin/multiplatform/SpanStatus; + public static final field UNKNOWN_ERROR Lio/sentry/kotlin/multiplatform/SpanStatus; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/SpanStatus; + public static fun values ()[Lio/sentry/kotlin/multiplatform/SpanStatus; +} + +public final class io/sentry/kotlin/multiplatform/SpanStatus$Companion { + public final fun fromHttpStatusCode (I)Lio/sentry/kotlin/multiplatform/SpanStatus; + public final fun fromHttpStatusCode (Ljava/lang/Integer;Lio/sentry/kotlin/multiplatform/SpanStatus;)Lio/sentry/kotlin/multiplatform/SpanStatus; +} + +public abstract interface class io/sentry/kotlin/multiplatform/TransactionContext : io/sentry/kotlin/multiplatform/SpanContext { + public abstract fun getName ()Ljava/lang/String; + public abstract fun getParentSampled ()Ljava/lang/Boolean; + public abstract fun getTransactionNameSource ()Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; +} + +public final class io/sentry/kotlin/multiplatform/TransactionContextKt { + public static final fun TransactionContext (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/TransactionContext; + public static final fun TransactionContext (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/TransactionContext; + public static final fun TransactionContext (Ljava/lang/String;Ljava/lang/String;Z)Lio/sentry/kotlin/multiplatform/TransactionContext; } public final class io/sentry/kotlin/multiplatform/protocol/Breadcrumb { @@ -324,6 +415,34 @@ public final class io/sentry/kotlin/multiplatform/protocol/SentryId$Companion { public final fun getEMPTY_ID ()Lio/sentry/kotlin/multiplatform/protocol/SentryId; } +public final class io/sentry/kotlin/multiplatform/protocol/SpanId { + public static final field Companion Lio/sentry/kotlin/multiplatform/protocol/SpanId$Companion; + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public static synthetic fun copy$default (Lio/sentry/kotlin/multiplatform/protocol/SpanId;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/kotlin/multiplatform/protocol/SpanId; + public fun equals (Ljava/lang/Object;)Z + public final fun getSpanIdString ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/sentry/kotlin/multiplatform/protocol/SpanId$Companion { + public final fun getEMPTY_ID ()Lio/sentry/kotlin/multiplatform/protocol/SpanId; +} + +public final class io/sentry/kotlin/multiplatform/protocol/TransactionNameSource : java/lang/Enum { + public static final field COMPONENT Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static final field CUSTOM Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static final field ROUTE Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static final field TASK Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static final field URL Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static final field VIEW Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; + public static fun values ()[Lio/sentry/kotlin/multiplatform/protocol/TransactionNameSource; +} + public final class io/sentry/kotlin/multiplatform/protocol/User { public fun ()V public fun (Lio/sentry/kotlin/multiplatform/protocol/User;)V diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/CocoaScopeProvider.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/ScopeAdapter.apple.kt similarity index 98% rename from sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/CocoaScopeProvider.kt rename to sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/ScopeAdapter.apple.kt index f7906086..f5762445 100644 --- a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/CocoaScopeProvider.kt +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/ScopeAdapter.apple.kt @@ -10,8 +10,7 @@ import io.sentry.kotlin.multiplatform.protocol.Breadcrumb import io.sentry.kotlin.multiplatform.protocol.User import Scope.Sentry.SentryScope as PrivateCocoaScope -internal class CocoaScopeProvider(private val scope: CocoaScope) : Scope { - +internal class ScopeAdapter(private val scope: CocoaScope) : Scope { /* This bridge exposes private Cocoa SDK API to fetch internal properties such as user, level, etc. We need this in order to return properties because the Cocoa SDK doesn't implement getters. diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt index 3a7cd87d..f113ddcf 100644 --- a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.apple.kt @@ -1,6 +1,7 @@ package io.sentry.kotlin.multiplatform import cocoapods.Sentry.SentrySDK +import io.sentry.kotlin.multiplatform.converters.toCocoa import io.sentry.kotlin.multiplatform.extensions.toCocoaBreadcrumb import io.sentry.kotlin.multiplatform.extensions.toCocoaUser import io.sentry.kotlin.multiplatform.extensions.toCocoaUserFeedback @@ -17,7 +18,6 @@ public actual abstract class Context internal expect fun initSentry(configuration: OptionsConfiguration) internal actual object SentryBridge { - actual fun init(context: Context, configuration: OptionsConfiguration) { initSentry(configuration) } @@ -65,16 +65,55 @@ internal actual object SentryBridge { SentrySDK.setUser(user?.toCocoaUser()) } + actual fun startTransaction(name: String, operation: String): Span { + val cocoaSpan = SentrySDK.startTransactionWithName(name, operation) + return SpanAdapter(cocoaSpan) + } + + actual fun startTransaction(name: String, operation: String, bindToScope: Boolean): Span { + val cocoaSpan = SentrySDK.startTransactionWithName(name, operation, bindToScope) + return SpanAdapter(cocoaSpan) + } + + actual fun startTransaction( + transactionContext: TransactionContext, + customSamplingContext: CustomSamplingContext + ): Span { + val cocoaSpan = SentrySDK.startTransactionWithContext( + transactionContext.toCocoa(), + customSamplingContext?.toCocoa() ?: mapOf() + ) + return SpanAdapter(cocoaSpan) + } + + actual fun startTransaction( + transactionContext: TransactionContext, + customSamplingContext: CustomSamplingContext, + bindToScope: Boolean + ): Span { + val cocoaSpan = SentrySDK.startTransactionWithContext( + transactionContext.toCocoa(), + bindToScope, + customSamplingContext?.toCocoa() ?: mapOf() + ) + return SpanAdapter(cocoaSpan) + } + + actual fun getSpan(): Span? { + val cocoaSpan = SentrySDK.span + return cocoaSpan?.let { SpanAdapter(it) } + } + actual fun close() { SentrySDK.close() } private fun configureScopeCallback(scopeCallback: ScopeCallback): (CocoaScope?) -> Unit { return { cocoaScope -> - val cocoaScopeProvider = cocoaScope?.let { - CocoaScopeProvider(it) + val cocoaScopeAdapter = cocoaScope?.let { + ScopeAdapter(it) } - cocoaScopeProvider?.let { + cocoaScopeAdapter?.let { scopeCallback.invoke(it) } } diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SpanAdapter.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SpanAdapter.apple.kt new file mode 100644 index 00000000..f7542fa7 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/SpanAdapter.apple.kt @@ -0,0 +1,70 @@ +package io.sentry.kotlin.multiplatform + +import cocoapods.Sentry.SentrySpanProtocol +import io.sentry.kotlin.multiplatform.converters.toCocoa +import io.sentry.kotlin.multiplatform.converters.toKmp +import io.sentry.kotlin.multiplatform.protocol.SpanId + +internal class SpanAdapter(private val cocoaSpan: SentrySpanProtocol) : Span { + override fun startChild(operation: String): Span { + val cocoaSpan = cocoaSpan.startChildWithOperation(operation) + return SpanAdapter(cocoaSpan) + } + + override fun startChild(operation: String, description: String?): Span { + val cocoaSpan = + cocoaSpan.startChildWithOperation(operation = operation, description = description) + return SpanAdapter(cocoaSpan) + } + + override fun finish() { + cocoaSpan.finish() + } + + override fun finish(status: SpanStatus) { + cocoaSpan.finishWithStatus(status.toCocoa()) + } + + override var operation: String + get() = cocoaSpan.operation + set(value) { + cocoaSpan.operation = value + } + + override var description: String? + get() = cocoaSpan.spanDescription + set(value) { + cocoaSpan.setSpanDescription(value) + } + + override var status: SpanStatus? + get() = cocoaSpan.status.toKmp() + set(value) { + value?.let { + cocoaSpan.status = it.toCocoa() + } + } + + override val spanId: SpanId = SpanId(cocoaSpan.spanId.toString()) + + override val parentSpanId: SpanId? = cocoaSpan.parentSpanId?.let { SpanId(it.toString()) } + + override fun setTag(key: String, value: String) { + cocoaSpan.setTagValue(value = value, forKey = key) + } + + override fun getTag(key: String): String? { + return cocoaSpan.tags[key] as String? + } + + override val isFinished: Boolean + get() = cocoaSpan.isFinished + + override fun setData(key: String, value: Any) { + cocoaSpan.setDataValue(value = value, forKey = key) + } + + override fun getData(key: String): Any? { + return cocoaSpan.data[key] + } +} diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/TransactionContextProvider.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/TransactionContextProvider.apple.kt new file mode 100644 index 00000000..0949dd42 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/TransactionContextProvider.apple.kt @@ -0,0 +1,22 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.converters.toBoolean +import io.sentry.kotlin.multiplatform.converters.toKmpTransactionNameSource +import io.sentry.kotlin.multiplatform.protocol.SentryId +import io.sentry.kotlin.multiplatform.protocol.SpanId +import io.sentry.kotlin.multiplatform.protocol.TransactionNameSource + +internal class TransactionContextProvider(cocoaTransactionContext: CocoaTransactionContext) : + TransactionContext { + override val name: String = cocoaTransactionContext.name + override val transactionNameSource: TransactionNameSource = + cocoaTransactionContext.nameSource.toKmpTransactionNameSource() + override val sampled: Boolean? = cocoaTransactionContext.sampled().toBoolean() + override val parentSampled: Boolean? = cocoaTransactionContext.parentSampled().toBoolean() + override val operation: String = cocoaTransactionContext.operation + override val traceId: SentryId = SentryId(cocoaTransactionContext.traceId.toString()) + override val spanId: SpanId = SpanId(cocoaTransactionContext.spanId.toString()) + override val parentSpanId: SpanId? = + cocoaTransactionContext.parentSpanId?.let { SpanId(it.toString()) } + override val description: String? = cocoaTransactionContext.spanDescription +} diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/TypeAliases.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/TypeAliases.apple.kt index a85825f9..2da4295b 100644 --- a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/TypeAliases.apple.kt +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/TypeAliases.apple.kt @@ -8,7 +8,11 @@ import cocoapods.Sentry.SentryId import cocoapods.Sentry.SentryLevel import cocoapods.Sentry.SentryMessage import cocoapods.Sentry.SentryOptions +import cocoapods.Sentry.SentrySampleDecision import cocoapods.Sentry.SentryScope +import cocoapods.Sentry.SentrySpanId +import cocoapods.Sentry.SentrySpanStatus +import cocoapods.Sentry.SentryTransactionContext import cocoapods.Sentry.SentryUser import cocoapods.Sentry.SentryUserFeedback @@ -23,3 +27,8 @@ internal typealias CocoaUserFeedback = SentryUserFeedback internal typealias CocoaSentryEvent = SentryEvent internal typealias CocoaMessage = SentryMessage internal typealias CocoaSentryException = SentryException +internal typealias CocoaSpanStatus = SentrySpanStatus +internal typealias CocoaSpanId = SentrySpanId +internal typealias CocoaTransactionContext = SentryTransactionContext +internal typealias CocoaSampleDecision = SentrySampleDecision +internal typealias CocoaCustomSamplingContext = Map diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/CustomSamplingContextConverters.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/CustomSamplingContextConverters.apple.kt new file mode 100644 index 00000000..d2edaabc --- /dev/null +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/CustomSamplingContextConverters.apple.kt @@ -0,0 +1,8 @@ +package io.sentry.kotlin.multiplatform.converters + +import io.sentry.kotlin.multiplatform.CocoaCustomSamplingContext +import io.sentry.kotlin.multiplatform.CustomSamplingContext + +internal fun CustomSamplingContext.toCocoa(): CocoaCustomSamplingContext { + return this as CocoaCustomSamplingContext +} diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/SentrySampleDecisionConverters.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/SentrySampleDecisionConverters.apple.kt new file mode 100644 index 00000000..a2e9b16e --- /dev/null +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/SentrySampleDecisionConverters.apple.kt @@ -0,0 +1,17 @@ +package io.sentry.kotlin.multiplatform.converters + +import cocoapods.Sentry.SentrySampleDecision + +internal fun SentrySampleDecision.toBoolean(): Boolean? = when (this) { + SentrySampleDecision.kSentrySampleDecisionUndecided -> false + SentrySampleDecision.kSentrySampleDecisionYes -> true + else -> { + null + } +} + +internal fun Boolean?.toSampleDecision(): SentrySampleDecision = when (this) { + null -> SentrySampleDecision.kSentrySampleDecisionUndecided + false -> SentrySampleDecision.kSentrySampleDecisionNo + true -> SentrySampleDecision.kSentrySampleDecisionYes +} diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/SpanStatusConverters.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/SpanStatusConverters.apple.kt new file mode 100644 index 00000000..cd6e5059 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/SpanStatusConverters.apple.kt @@ -0,0 +1,48 @@ +package io.sentry.kotlin.multiplatform.converters + +import io.sentry.kotlin.multiplatform.CocoaSpanStatus +import io.sentry.kotlin.multiplatform.SpanStatus + +internal fun SpanStatus.toCocoa(): CocoaSpanStatus { + return when (this) { + SpanStatus.ABORTED -> CocoaSpanStatus.kSentrySpanStatusAborted + SpanStatus.ALREADY_EXISTS -> CocoaSpanStatus.kSentrySpanStatusAlreadyExists + SpanStatus.CANCELLED -> CocoaSpanStatus.kSentrySpanStatusCancelled + SpanStatus.DATA_LOSS -> CocoaSpanStatus.kSentrySpanStatusDataLoss + SpanStatus.DEADLINE_EXCEEDED -> CocoaSpanStatus.kSentrySpanStatusDeadlineExceeded + SpanStatus.FAILED_PRECONDITION -> CocoaSpanStatus.kSentrySpanStatusFailedPrecondition + SpanStatus.INVALID_ARGUMENT -> CocoaSpanStatus.kSentrySpanStatusInvalidArgument + SpanStatus.NOT_FOUND -> CocoaSpanStatus.kSentrySpanStatusNotFound + SpanStatus.OK -> CocoaSpanStatus.kSentrySpanStatusOk + SpanStatus.OUT_OF_RANGE -> CocoaSpanStatus.kSentrySpanStatusOutOfRange + SpanStatus.PERMISSION_DENIED -> CocoaSpanStatus.kSentrySpanStatusPermissionDenied + SpanStatus.RESOURCE_EXHAUSTED -> CocoaSpanStatus.kSentrySpanStatusResourceExhausted + SpanStatus.UNAUTHENTICATED -> CocoaSpanStatus.kSentrySpanStatusUnauthenticated + SpanStatus.UNAVAILABLE -> CocoaSpanStatus.kSentrySpanStatusUnavailable + SpanStatus.UNIMPLEMENTED -> CocoaSpanStatus.kSentrySpanStatusUnimplemented + else -> { + CocoaSpanStatus.kSentrySpanStatusUnknownError + } + } +} + +internal fun CocoaSpanStatus.toKmp(): SpanStatus { + return when (this) { + CocoaSpanStatus.kSentrySpanStatusAborted -> SpanStatus.ABORTED + CocoaSpanStatus.kSentrySpanStatusAlreadyExists -> SpanStatus.ALREADY_EXISTS + CocoaSpanStatus.kSentrySpanStatusCancelled -> SpanStatus.CANCELLED + CocoaSpanStatus.kSentrySpanStatusDataLoss -> SpanStatus.DATA_LOSS + CocoaSpanStatus.kSentrySpanStatusDeadlineExceeded -> SpanStatus.DEADLINE_EXCEEDED + CocoaSpanStatus.kSentrySpanStatusFailedPrecondition -> SpanStatus.FAILED_PRECONDITION + CocoaSpanStatus.kSentrySpanStatusInvalidArgument -> SpanStatus.INVALID_ARGUMENT + CocoaSpanStatus.kSentrySpanStatusNotFound -> SpanStatus.NOT_FOUND + CocoaSpanStatus.kSentrySpanStatusOk -> SpanStatus.OK + CocoaSpanStatus.kSentrySpanStatusOutOfRange -> SpanStatus.OUT_OF_RANGE + CocoaSpanStatus.kSentrySpanStatusPermissionDenied -> SpanStatus.PERMISSION_DENIED + CocoaSpanStatus.kSentrySpanStatusResourceExhausted -> SpanStatus.RESOURCE_EXHAUSTED + CocoaSpanStatus.kSentrySpanStatusUnauthenticated -> SpanStatus.UNAUTHENTICATED + CocoaSpanStatus.kSentrySpanStatusUnavailable -> SpanStatus.UNAVAILABLE + CocoaSpanStatus.kSentrySpanStatusUnimplemented -> SpanStatus.UNIMPLEMENTED + else -> SpanStatus.UNKNOWN + } +} diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionContextConverters.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionContextConverters.apple.kt new file mode 100644 index 00000000..db702e3b --- /dev/null +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionContextConverters.apple.kt @@ -0,0 +1,19 @@ +package io.sentry.kotlin.multiplatform.converters + +import io.sentry.kotlin.multiplatform.CocoaSampleDecision +import io.sentry.kotlin.multiplatform.CocoaTransactionContext +import io.sentry.kotlin.multiplatform.TransactionContext + +/** + * Converts a [CocoaTransactionContext] to a [TransactionContext]. + * + * Mainly used by the SDK for starting transactions with a [TransactionContext]. + */ +internal fun TransactionContext.toCocoa(): CocoaTransactionContext { + val sampleDecision = sampled?.toSampleDecision() ?: CocoaSampleDecision.kSentrySampleDecisionUndecided + val kmpScope = this@toCocoa + + return CocoaTransactionContext(name, operation, sampleDecision).apply { + parentSampled = kmpScope.parentSampled.toSampleDecision() + } +} diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionNameSourceConverters.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionNameSourceConverters.apple.kt new file mode 100644 index 00000000..2ae0c54c --- /dev/null +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionNameSourceConverters.apple.kt @@ -0,0 +1,31 @@ +package io.sentry.kotlin.multiplatform.converters + +import io.sentry.kotlin.multiplatform.protocol.TransactionNameSource +import kotlinx.cinterop.UnsafeNumber +import platform.darwin.NSInteger + +/* +Definition of TransactionNameSource in the Cocoa SDK + +typedef NS_ENUM(NSInteger, SentryTransactionNameSource) { + kSentryTransactionNameSourceCustom = 0, + kSentryTransactionNameSourceUrl, + kSentryTransactionNameSourceRoute, + kSentryTransactionNameSourceView, + kSentryTransactionNameSourceComponent, + kSentryTransactionNameSourceTask +}; + */ +@OptIn(UnsafeNumber::class) +internal fun NSInteger.toKmpTransactionNameSource(): TransactionNameSource { + val transactionNameSource = when (this.toInt()) { + 0 -> TransactionNameSource.CUSTOM + 1 -> TransactionNameSource.URL + 2 -> TransactionNameSource.ROUTE + 3 -> TransactionNameSource.VIEW + 4 -> TransactionNameSource.COMPONENT + 5 -> TransactionNameSource.TASK + else -> TransactionNameSource.CUSTOM + } + return transactionNameSource +} diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.apple.kt index 34063c1a..4c672602 100644 --- a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.apple.kt +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.apple.kt @@ -5,8 +5,11 @@ import cocoapods.Sentry.SentryHttpStatusCodeRange import io.sentry.kotlin.multiplatform.BuildKonfig import io.sentry.kotlin.multiplatform.CocoaSentryEvent import io.sentry.kotlin.multiplatform.CocoaSentryOptions +import io.sentry.kotlin.multiplatform.CustomSamplingContext +import io.sentry.kotlin.multiplatform.SamplingContext import io.sentry.kotlin.multiplatform.SentryEvent import io.sentry.kotlin.multiplatform.SentryOptions +import io.sentry.kotlin.multiplatform.TransactionContextProvider import io.sentry.kotlin.multiplatform.nsexception.dropKotlinCrashEvent import kotlinx.cinterop.convert import platform.Foundation.NSNumber @@ -24,6 +27,7 @@ internal fun CocoaSentryOptions.applyCocoaBaseOptions(options: SentryOptions) { dsn = options.dsn attachStacktrace = options.attachStackTrace dist = options.dist + tracesSampleRate = options.tracesSampleRate?.let { NSNumber(it) } options.environment?.let { environment = it } @@ -82,6 +86,23 @@ internal fun CocoaSentryOptions.applyCocoaBaseOptions(options: SentryOptions) { } } + tracesSampler = { cocoaSamplingContext -> + cocoaSamplingContext?.let { context -> + val customSamplingContext: CustomSamplingContext = + context.customSamplingContext?.mapKeys { entry -> + entry.key.toString() + } + val cocoaTransactionContext = + TransactionContextProvider(context.transactionContext) + val samplingContext = SamplingContext(cocoaTransactionContext, customSamplingContext) + // returns null if KMP tracesSampler is null + val sampleRate = options.tracesSampler?.invoke(samplingContext) + sampleRate?.let { unwrappedSampleRate -> + NSNumber(unwrappedSampleRate) + } + } + } + enableCaptureFailedRequests = options.enableCaptureFailedRequests failedRequestTargets = options.failedRequestTargets failedRequestStatusCodes = options.failedRequestStatusCodes.map { diff --git a/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/protocol/SpanId.apple.kt b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/protocol/SpanId.apple.kt new file mode 100644 index 00000000..5603159a --- /dev/null +++ b/sentry-kotlin-multiplatform/src/appleMain/kotlin/io/sentry/kotlin/multiplatform/protocol/SpanId.apple.kt @@ -0,0 +1,24 @@ +package io.sentry.kotlin.multiplatform.protocol + +import io.sentry.kotlin.multiplatform.CocoaSpanId + +public actual data class SpanId actual constructor(val spanIdString: String) { + + public actual companion object { + public actual val EMPTY_ID: SpanId = SpanId("") + } + + private var cocoaSpanId: CocoaSpanId? = null + + init { + cocoaSpanId = if (spanIdString.isEmpty()) { + CocoaSpanId.empty + } else { + CocoaSpanId(spanIdString) + } + } + + actual override fun toString(): String { + return cocoaSpanId.toString() + } +} diff --git a/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryScopeTest.kt b/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryScopeTest.kt index bb9aac47..922d125b 100644 --- a/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryScopeTest.kt +++ b/sentry-kotlin-multiplatform/src/appleTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryScopeTest.kt @@ -5,6 +5,6 @@ import cocoapods.Sentry.SentryScope as CocoaScope actual abstract class BaseSentryScopeTest { actual fun initializeScope(): Scope { val cocoaScope = CocoaScope() - return CocoaScopeProvider(cocoaScope) + return ScopeAdapter(cocoaScope) } } diff --git a/sentry-kotlin-multiplatform/src/commonAppleTest/kotlin/io/sentry/kotlin/multiplatform/converters/CustomSamplingContextConvertersTest.kt b/sentry-kotlin-multiplatform/src/commonAppleTest/kotlin/io/sentry/kotlin/multiplatform/converters/CustomSamplingContextConvertersTest.kt new file mode 100644 index 00000000..54dacf4b --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonAppleTest/kotlin/io/sentry/kotlin/multiplatform/converters/CustomSamplingContextConvertersTest.kt @@ -0,0 +1,21 @@ +package io.sentry.kotlin.multiplatform.converters + +import io.sentry.kotlin.multiplatform.CocoaCustomSamplingContext +import io.sentry.kotlin.multiplatform.CustomSamplingContext +import io.sentry.kotlin.multiplatform.fakes.createFakeCustomSamplingContext +import kotlin.test.Test +import kotlin.test.assertTrue + +class CustomSamplingContextConvertersTest { + @Test + fun `GIVEN customSamplingContext WHEN toCocoa called THEN value is CocoaCustomSamplingContext`() { + // GIVEN + val customSamplingContext: CustomSamplingContext = createFakeCustomSamplingContext() + + // WHEN + val cocoaCustomSamplingContext = customSamplingContext.toCocoa() + + // THEN + assertTrue(cocoaCustomSamplingContext is CocoaCustomSamplingContext) + } +} diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/JvmScopeProvider.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/ScopeAdapter.jvm.kt similarity index 97% rename from sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/JvmScopeProvider.kt rename to sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/ScopeAdapter.jvm.kt index 3e47181a..28f79bf6 100644 --- a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/JvmScopeProvider.kt +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/ScopeAdapter.jvm.kt @@ -8,8 +8,7 @@ import io.sentry.kotlin.multiplatform.extensions.toKmpUser import io.sentry.kotlin.multiplatform.protocol.Breadcrumb import io.sentry.kotlin.multiplatform.protocol.User -internal class JvmScopeProvider(private val scope: JvmIScope) : Scope { - +internal class ScopeAdapter(private val scope: JvmIScope) : Scope { override var level: SentryLevel? set(value) { scope.level = value?.toJvmSentryLevel() diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt index 51b1e662..8acab1ae 100644 --- a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.jvm.kt @@ -1,6 +1,9 @@ package io.sentry.kotlin.multiplatform +import io.sentry.CustomSamplingContext import io.sentry.Sentry +import io.sentry.TransactionOptions +import io.sentry.kotlin.multiplatform.converters.toJvm import io.sentry.kotlin.multiplatform.extensions.toJvmBreadcrumb import io.sentry.kotlin.multiplatform.extensions.toJvmUser import io.sentry.kotlin.multiplatform.extensions.toJvmUserFeedback @@ -8,11 +11,11 @@ import io.sentry.kotlin.multiplatform.protocol.Breadcrumb import io.sentry.kotlin.multiplatform.protocol.SentryId import io.sentry.kotlin.multiplatform.protocol.User import io.sentry.kotlin.multiplatform.protocol.UserFeedback +import io.sentry.kotlin.multiplatform.CustomSamplingContext as KmpCustomSamplingContext internal expect fun initSentry(configuration: OptionsConfiguration) internal actual object SentryBridge { - actual fun init(context: Context, configuration: OptionsConfiguration) { initSentry(configuration) } @@ -57,13 +60,66 @@ internal actual object SentryBridge { Sentry.setUser(user?.toJvmUser()) } + actual fun startTransaction(name: String, operation: String): Span { + val jvmTransaction = Sentry.startTransaction(name, operation) + return SpanAdapter(jvmTransaction) + } + + actual fun startTransaction(name: String, operation: String, bindToScope: Boolean): Span { + val jvmTransaction = Sentry.startTransaction( + name, + operation, + TransactionOptions().apply { this.isBindToScope = bindToScope } + ) + return SpanAdapter(jvmTransaction) + } + + actual fun startTransaction( + transactionContext: TransactionContext, + customSamplingContext: KmpCustomSamplingContext + ): Span { + val jvmCustomSamplingContext = customSamplingContext?.toJvm() ?: CustomSamplingContext() + val jvmTransactionContext = transactionContext.toJvm() + val jvmTransaction = + Sentry.startTransaction( + jvmTransactionContext, + TransactionOptions().apply { + this.customSamplingContext = jvmCustomSamplingContext + } + ) + return SpanAdapter(jvmTransaction) + } + + actual fun startTransaction( + transactionContext: TransactionContext, + customSamplingContext: KmpCustomSamplingContext, + bindToScope: Boolean + ): Span { + val jvmCustomSamplingContext = customSamplingContext?.toJvm() + val jvmTransactionContext = transactionContext.toJvm() + val jvmTransaction = + Sentry.startTransaction( + jvmTransactionContext, + TransactionOptions().apply { + this.isBindToScope = bindToScope + this.customSamplingContext = jvmCustomSamplingContext + } + ) + return SpanAdapter(jvmTransaction) + } + + actual fun getSpan(): Span? { + val jvmSpan = Sentry.getSpan() + return jvmSpan?.let { SpanAdapter(it) } + } + actual fun close() { Sentry.close() } private fun configureScopeCallback(scopeCallback: ScopeCallback): (JvmIScope) -> Unit { return { - val jvmScopeProvider = JvmScopeProvider(it) + val jvmScopeProvider = ScopeAdapter(it) scopeCallback.invoke(jvmScopeProvider) } } diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SpanAdapter.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SpanAdapter.jvm.kt new file mode 100644 index 00000000..d858b887 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/SpanAdapter.jvm.kt @@ -0,0 +1,66 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.ISpan +import io.sentry.kotlin.multiplatform.converters.toJvm +import io.sentry.kotlin.multiplatform.converters.toKmp +import io.sentry.kotlin.multiplatform.protocol.SpanId + +internal class SpanAdapter(private val jvmSpan: ISpan) : Span { + override fun startChild(operation: String): Span { + val jvmSpan = jvmSpan.startChild(operation) + return SpanAdapter(jvmSpan) + } + + override fun startChild(operation: String, description: String?): Span { + val jvmSpan = jvmSpan.startChild(operation, description) + return SpanAdapter(jvmSpan) + } + + override fun finish() { + jvmSpan.finish() + } + + override fun finish(status: SpanStatus) { + jvmSpan.finish(status.toJvm()) + } + + override var operation: String + get() = jvmSpan.operation + set(value) { + jvmSpan.operation = value + } + override var description: String? + get() = jvmSpan.description + set(value) { + jvmSpan.description = value + } + override var status: SpanStatus? + get() = jvmSpan.status?.toKmp() + set(value) { + jvmSpan.status = value?.toJvm() + } + + override val spanId: SpanId = SpanId(jvmSpan.spanContext.spanId.toString()) + + override val parentSpanId: SpanId? = + jvmSpan.spanContext.parentSpanId?.let { SpanId(it.toString()) } + + override fun setTag(key: String, value: String) { + jvmSpan.setTag(key, value) + } + + override fun getTag(key: String): String? { + return jvmSpan.getTag(key) + } + + override val isFinished: Boolean + get() = jvmSpan.isFinished + + override fun setData(key: String, value: Any) { + jvmSpan.setData(key, value) + } + + override fun getData(key: String): Any? { + return jvmSpan.getData(key) + } +} diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/TransactionContextProvider.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/TransactionContextProvider.jvm.kt new file mode 100644 index 00000000..363e9c9a --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/TransactionContextProvider.jvm.kt @@ -0,0 +1,21 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.converters.toKmp +import io.sentry.kotlin.multiplatform.protocol.SentryId +import io.sentry.kotlin.multiplatform.protocol.SpanId +import io.sentry.kotlin.multiplatform.protocol.TransactionNameSource + +internal class TransactionContextProvider(jvmTransactionContext: JvmTransactionContext) : + TransactionContext { + override val name: String = jvmTransactionContext.name + override val transactionNameSource: TransactionNameSource = + jvmTransactionContext.transactionNameSource.toKmp() + override val sampled: Boolean? = jvmTransactionContext.sampled + override val parentSampled: Boolean? = jvmTransactionContext.parentSampled + override val operation: String = jvmTransactionContext.operation + override val traceId: SentryId = SentryId(jvmTransactionContext.traceId.toString()) + override val spanId: SpanId = SpanId(jvmTransactionContext.spanId.toString()) + override val parentSpanId: SpanId? = + jvmTransactionContext.parentSpanId?.let { SpanId(it.toString()) } + override val description: String? = jvmTransactionContext.description +} diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/TypeAliases.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/TypeAliases.jvm.kt index 14923844..cc623eca 100644 --- a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/TypeAliases.jvm.kt +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/TypeAliases.jvm.kt @@ -2,16 +2,21 @@ package io.sentry.kotlin.multiplatform import io.sentry.Attachment import io.sentry.Breadcrumb +import io.sentry.CustomSamplingContext import io.sentry.IScope import io.sentry.Scope import io.sentry.SentryEvent import io.sentry.SentryLevel import io.sentry.SentryOptions +import io.sentry.SpanId +import io.sentry.SpanStatus +import io.sentry.TransactionContext import io.sentry.UserFeedback import io.sentry.protocol.Contexts import io.sentry.protocol.Message import io.sentry.protocol.SentryException import io.sentry.protocol.SentryId +import io.sentry.protocol.TransactionNameSource import io.sentry.protocol.User internal typealias JvmSentryLevel = SentryLevel @@ -27,3 +32,8 @@ internal typealias JvmSentryEvent = SentryEvent internal typealias JvmMessage = Message internal typealias JvmSentryException = SentryException internal typealias JvmContexts = Contexts +internal typealias JvmSpanStatus = SpanStatus +internal typealias JvmSpanId = SpanId +internal typealias JvmTransactionContext = TransactionContext +internal typealias JvmTransactionNameSource = TransactionNameSource +internal typealias JvmCustomSamplingContext = CustomSamplingContext diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/CustomSamplingContextConverters.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/CustomSamplingContextConverters.jvm.kt new file mode 100644 index 00000000..5090b90d --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/CustomSamplingContextConverters.jvm.kt @@ -0,0 +1,12 @@ +package io.sentry.kotlin.multiplatform.converters + +import io.sentry.kotlin.multiplatform.CustomSamplingContext +import io.sentry.kotlin.multiplatform.JvmCustomSamplingContext + +internal fun CustomSamplingContext.toJvm(): JvmCustomSamplingContext { + val jvmCustomSamplingContext = JvmCustomSamplingContext() + this?.forEach { (key, value) -> + jvmCustomSamplingContext[key] = value + } + return jvmCustomSamplingContext +} diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/SpanStatusConverters.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/SpanStatusConverters.jvm.kt new file mode 100644 index 00000000..d81c982f --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/SpanStatusConverters.jvm.kt @@ -0,0 +1,50 @@ +package io.sentry.kotlin.multiplatform.converters + +import io.sentry.kotlin.multiplatform.JvmSpanStatus +import io.sentry.kotlin.multiplatform.SpanStatus + +internal fun SpanStatus.toJvm(): JvmSpanStatus { + return when (this) { + SpanStatus.ABORTED -> JvmSpanStatus.ABORTED + SpanStatus.ALREADY_EXISTS -> JvmSpanStatus.ALREADY_EXISTS + SpanStatus.CANCELLED -> JvmSpanStatus.CANCELLED + SpanStatus.DATA_LOSS -> JvmSpanStatus.DATA_LOSS + SpanStatus.DEADLINE_EXCEEDED -> JvmSpanStatus.DEADLINE_EXCEEDED + SpanStatus.FAILED_PRECONDITION -> JvmSpanStatus.FAILED_PRECONDITION + SpanStatus.INVALID_ARGUMENT -> JvmSpanStatus.INVALID_ARGUMENT + SpanStatus.NOT_FOUND -> JvmSpanStatus.NOT_FOUND + SpanStatus.OK -> JvmSpanStatus.OK + SpanStatus.OUT_OF_RANGE -> JvmSpanStatus.OUT_OF_RANGE + SpanStatus.PERMISSION_DENIED -> JvmSpanStatus.PERMISSION_DENIED + SpanStatus.RESOURCE_EXHAUSTED -> JvmSpanStatus.RESOURCE_EXHAUSTED + SpanStatus.UNAUTHENTICATED -> JvmSpanStatus.UNAUTHENTICATED + SpanStatus.UNAVAILABLE -> JvmSpanStatus.UNAVAILABLE + SpanStatus.UNIMPLEMENTED -> JvmSpanStatus.UNIMPLEMENTED + else -> { + JvmSpanStatus.UNKNOWN + } + } +} + +internal fun JvmSpanStatus.toKmp(): SpanStatus { + return when (this) { + JvmSpanStatus.ABORTED -> SpanStatus.ABORTED + JvmSpanStatus.ALREADY_EXISTS -> SpanStatus.ALREADY_EXISTS + JvmSpanStatus.CANCELLED -> SpanStatus.CANCELLED + JvmSpanStatus.DATA_LOSS -> SpanStatus.DATA_LOSS + JvmSpanStatus.DEADLINE_EXCEEDED -> SpanStatus.DEADLINE_EXCEEDED + JvmSpanStatus.FAILED_PRECONDITION -> SpanStatus.FAILED_PRECONDITION + JvmSpanStatus.INVALID_ARGUMENT -> SpanStatus.INVALID_ARGUMENT + JvmSpanStatus.NOT_FOUND -> SpanStatus.NOT_FOUND + JvmSpanStatus.OK -> SpanStatus.OK + JvmSpanStatus.OUT_OF_RANGE -> SpanStatus.OUT_OF_RANGE + JvmSpanStatus.PERMISSION_DENIED -> SpanStatus.PERMISSION_DENIED + JvmSpanStatus.RESOURCE_EXHAUSTED -> SpanStatus.RESOURCE_EXHAUSTED + JvmSpanStatus.UNAUTHENTICATED -> SpanStatus.UNAUTHENTICATED + JvmSpanStatus.UNAVAILABLE -> SpanStatus.UNAVAILABLE + JvmSpanStatus.UNIMPLEMENTED -> SpanStatus.UNIMPLEMENTED + else -> { + SpanStatus.UNKNOWN + } + } +} diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionContextConverters.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionContextConverters.jvm.kt new file mode 100644 index 00000000..af9e1e60 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionContextConverters.jvm.kt @@ -0,0 +1,20 @@ +package io.sentry.kotlin.multiplatform.converters + +import io.sentry.TracesSamplingDecision +import io.sentry.kotlin.multiplatform.JvmTransactionContext +import io.sentry.kotlin.multiplatform.TransactionContext + +/** + * Converts a [JvmTransactionContext] to a [TransactionContext]. + * + * Mainly used by the SDK for starting transactions with a [TransactionContext]. + */ +internal fun TransactionContext.toJvm(): JvmTransactionContext { + val sampleDecision = sampled?.let { TracesSamplingDecision(it) } + val kmpScope = this@toJvm + return JvmTransactionContext(name, operation, sampleDecision).apply { + parentSampled = kmpScope.parentSampled + description = kmpScope.description + name = kmpScope.name + } +} diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionNameSourceConverters.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionNameSourceConverters.jvm.kt new file mode 100644 index 00000000..66f81431 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/converters/TransactionNameSourceConverters.jvm.kt @@ -0,0 +1,13 @@ +package io.sentry.kotlin.multiplatform.converters + +import io.sentry.kotlin.multiplatform.JvmTransactionNameSource +import io.sentry.kotlin.multiplatform.protocol.TransactionNameSource + +internal fun JvmTransactionNameSource.toKmp(): TransactionNameSource = when (this) { + JvmTransactionNameSource.COMPONENT -> TransactionNameSource.COMPONENT + JvmTransactionNameSource.ROUTE -> TransactionNameSource.ROUTE + JvmTransactionNameSource.URL -> TransactionNameSource.URL + JvmTransactionNameSource.VIEW -> TransactionNameSource.VIEW + JvmTransactionNameSource.TASK -> TransactionNameSource.TASK + JvmTransactionNameSource.CUSTOM -> TransactionNameSource.CUSTOM +} diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.jvm.kt index 3821083b..aad2fb5b 100644 --- a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.jvm.kt +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.jvm.kt @@ -2,8 +2,10 @@ package io.sentry.kotlin.multiplatform.extensions import io.sentry.kotlin.multiplatform.BuildKonfig import io.sentry.kotlin.multiplatform.JvmSentryOptions +import io.sentry.kotlin.multiplatform.SamplingContext import io.sentry.kotlin.multiplatform.SentryEvent import io.sentry.kotlin.multiplatform.SentryOptions +import io.sentry.kotlin.multiplatform.TransactionContextProvider internal fun SentryOptions.toJvmSentryOptionsCallback(): (JvmSentryOptions) -> Unit = { it.applyJvmBaseOptions(this) @@ -33,6 +35,7 @@ internal fun JvmSentryOptions.applyJvmBaseOptions(options: SentryOptions) { isAttachThreads = options.attachThreads isAttachStacktrace = options.attachStackTrace dist = options.dist + tracesSampleRate = options.tracesSampleRate environment = options.environment release = options.release isDebug = options.debug @@ -49,7 +52,7 @@ internal fun JvmSentryOptions.applyJvmBaseOptions(options: SentryOptions) { options.beforeBreadcrumb?.invoke(jvmBreadcrumb.toKmpBreadcrumb())?.toJvmBreadcrumb() } } - setBeforeSend { jvmSentryEvent, hint -> + setBeforeSend { jvmSentryEvent, _ -> if (options.beforeSend == null) { jvmSentryEvent } else { @@ -58,4 +61,11 @@ internal fun JvmSentryOptions.applyJvmBaseOptions(options: SentryOptions) { } } } + setTracesSampler { jvmSamplingContext -> + val jvmTransactionContext = TransactionContextProvider(jvmSamplingContext.transactionContext) + val samplingContext = SamplingContext(jvmTransactionContext, jvmSamplingContext.customSamplingContext?.data) + // returns null if KMP tracesSampler is null + val sampleRate = options.tracesSampler?.invoke(samplingContext) + sampleRate + } } diff --git a/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/protocol/SpanId.jvm.kt b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/protocol/SpanId.jvm.kt new file mode 100644 index 00000000..14c6dda7 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonJvmMain/kotlin/io/sentry/kotlin/multiplatform/protocol/SpanId.jvm.kt @@ -0,0 +1,24 @@ +package io.sentry.kotlin.multiplatform.protocol + +import io.sentry.kotlin.multiplatform.JvmSpanId + +public actual data class SpanId actual constructor(val spanIdString: String) { + + public actual companion object { + public actual val EMPTY_ID: SpanId = SpanId("") + } + + private var jvmSpanId: JvmSpanId? = null + + init { + jvmSpanId = if (spanIdString.isEmpty()) { + JvmSpanId.EMPTY_ID + } else { + JvmSpanId(spanIdString) + } + } + + actual override fun toString(): String { + return jvmSpanId.toString() + } +} diff --git a/sentry-kotlin-multiplatform/src/commonJvmTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryScopeTest.kt b/sentry-kotlin-multiplatform/src/commonJvmTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryScopeTest.kt index 37431deb..5a347f08 100644 --- a/sentry-kotlin-multiplatform/src/commonJvmTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryScopeTest.kt +++ b/sentry-kotlin-multiplatform/src/commonJvmTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryScopeTest.kt @@ -5,6 +5,6 @@ import io.sentry.SentryOptions actual abstract class BaseSentryScopeTest { actual fun initializeScope(): Scope { val jvmScope = JvmScope(SentryOptions()) - return JvmScopeProvider(jvmScope) + return ScopeAdapter(jvmScope) } } diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SamplingContext.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SamplingContext.kt new file mode 100644 index 00000000..f4eb46e0 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SamplingContext.kt @@ -0,0 +1,15 @@ +package io.sentry.kotlin.multiplatform + +public typealias CustomSamplingContext = Map? + +/** + * Context used by [io.sentry.kotlin.multiplatform.SentryOptions.tracesSampler] to determine if transaction + * is going to be sampled. + */ +public data class SamplingContext( + /** The transaction context. */ + public val transactionContext: TransactionContext, + + /** Arbitrary data used to determine if transaction is going to be sampled. */ + public val customSamplingContext: CustomSamplingContext +) diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt index 32c6a074..b23a3065 100644 --- a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryBridge.kt @@ -27,5 +27,22 @@ internal expect object SentryBridge { fun setUser(user: User?) + fun startTransaction(name: String, operation: String): Span + + fun startTransaction(name: String, operation: String, bindToScope: Boolean): Span + + fun startTransaction( + transactionContext: TransactionContext, + customSamplingContext: CustomSamplingContext + ): Span + + fun startTransaction( + transactionContext: TransactionContext, + customSamplingContext: CustomSamplingContext, + bindToScope: Boolean + ): Span + + fun getSpan(): Span? + fun close() } diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt index 56f6f39e..ef117430 100644 --- a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryKMP.kt @@ -117,6 +117,72 @@ public object Sentry { SentryBridge.setUser(user) } + /** + * Starts a new transaction and returns a new `Span` representing it. The `Span` can be + * used to record additional information about the transaction or add child spans. + * + * @param name The name of the transaction. + * @param operation The operation of the transaction, typically the name of the function or endpoint. + * @return A new `Span` representing the transaction. + */ + public fun startTransaction(name: String, operation: String): Span { + return SentryBridge.startTransaction(name, operation) + } + + /** + * Starts a new transaction and returns a new `Span` representing it. The `Span` can be + * used to record additional information about the transaction or add child spans. + * + * @param name The name of the transaction. + * @param operation The operation of the transaction, typically the name of the function or endpoint. + * @param bindToScope Whether to bind the transaction to the current scope. + * @return A new `Span` representing the transaction. + */ + public fun startTransaction(name: String, operation: String, bindToScope: Boolean): Span { + return SentryBridge.startTransaction(name, operation, bindToScope) + } + + /** + * Starts a new transaction and returns a new `Span` representing it. The `Span` can be + * used to record additional information about the transaction or add child spans. + * + * @param transactionContext The transaction context. + * @param customSamplingContext The custom sampling context. + * @return A new `Span` representing the transaction. + */ + public fun startTransaction( + transactionContext: TransactionContext, + customSamplingContext: CustomSamplingContext + ): Span { + return SentryBridge.startTransaction(transactionContext, customSamplingContext) + } + + /** + * Starts a new transaction and returns a new `Span` representing it. The `Span` can be + * used to record additional information about the transaction or add child spans. + * + * @param transactionContext The transaction context. + * @param customSamplingContext The custom sampling context. + * @param bindToScope Whether to bind the transaction to the current scope. + * @return A new `Span` representing the transaction. + */ + public fun startTransaction( + transactionContext: TransactionContext, + customSamplingContext: CustomSamplingContext, + bindToScope: Boolean + ): Span { + return SentryBridge.startTransaction(transactionContext, customSamplingContext, bindToScope) + } + + /** + * Apple: returns the active root transaction + * + * JVM: returns the active transaction or the latest active child span + */ + public fun getSpan(): Span? { + return SentryBridge.getSpan() + } + /** * Throws a RuntimeException, useful for testing. */ diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryOptions.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryOptions.kt index b8cbb8eb..ad7da7d4 100644 --- a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryOptions.kt +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SentryOptions.kt @@ -44,6 +44,9 @@ public open class SentryOptions { /** Sets the distribution. Think about it together with release and environment */ public var dist: String? = null + /** This function is called by TracesSampler to determine if transaction is sampled - meant to be sent to Sentry. */ + public var tracesSampler: ((SamplingContext) -> Double?)? = null + /** Whether to enable or disable automatic session tracking. */ public var enableAutoSessionTracking: Boolean = true diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/Span.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/Span.kt new file mode 100644 index 00000000..6b47357e --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/Span.kt @@ -0,0 +1,85 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.protocol.SpanId + +/** Represents a performance monitoring Span. */ +public interface Span { + /** + * Starts a child Span. + * + * @param operation - new span operation name + * @return a new transaction span + */ + public fun startChild(operation: String): Span + + /** + * Starts a child Span. + * + * @param operation - new span operation name + * @param description - new span description name + * @return a new transaction span + */ + public fun startChild(operation: String, description: String?): Span + + /** Sets span timestamp marking this span as finished. */ + public fun finish() + + /** + * Sets span timestamp marking this span as finished. + * + * @param status - the status + */ + public fun finish(status: SpanStatus) + + /** The span operation. */ + public var operation: String + + /** The span description. */ + public var description: String? + + /** The span status. */ + public var status: SpanStatus? + + /** The span spanId. */ + public val spanId: SpanId + + /** The span parentSpanId. */ + public val parentSpanId: SpanId? + + /** + * Sets the tag on span. + * + * @param key the tag key + * @param value the tag value + */ + public fun setTag(key: String, value: String) + + /** + * Returns the tag value from span. + * + * @return the tag value + */ + public fun getTag(key: String): String? + + /** + * Returns if span has finished. + * + * @return if span has finished. + */ + public val isFinished: Boolean + + /** + * Sets extra data on span. + * + * @param key the data key + * @param value the data value + */ + public fun setData(key: String, value: Any) + + /** + * Returns extra data from span. + * + * @return the data + */ + public fun getData(key: String): Any? +} diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SpanContext.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SpanContext.kt new file mode 100644 index 00000000..e21e2dce --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SpanContext.kt @@ -0,0 +1,28 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.protocol.SentryId +import io.sentry.kotlin.multiplatform.protocol.SpanId + +public typealias SamplingDecision = Boolean? + +/** An interface representing the context of a span. */ +public interface SpanContext { + /** The name of the operation associated with the span.*/ + public val operation: String + + /** Determines which trace the span belongs to.*/ + public val traceId: SentryId + + /** The unique identifier of the span.*/ + public val spanId: SpanId + + /** The unique identifier of the span's parent, if any.*/ + public val parentSpanId: SpanId? + + /** A longer description of the span's operation, which uniquely identifies the span but is + * consistent across instances of the span.*/ + public val description: String? + + /** Indicates if the span is sampled.*/ + public val sampled: SamplingDecision +} diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SpanStatus.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SpanStatus.kt new file mode 100644 index 00000000..dbd548f1 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/SpanStatus.kt @@ -0,0 +1,111 @@ +package io.sentry.kotlin.multiplatform + +/** The status of a [Span]. */ +public enum class SpanStatus { + /** Not an error, returned on success. */ + OK(200, 299), + + /** The operation was cancelled, typically by the caller. */ + CANCELLED(499), + + /** + * Some invariants expected by the underlying system have been broken. This code is reserved for + * serious errors. + */ + INTERNAL_ERROR(500), + + /** An unknown error raised by APIs that don't return enough error information. */ + UNKNOWN(500), + + /** An unknown error raised by APIs that don't return enough error information. */ + UNKNOWN_ERROR(500), + + /** The client specified an invalid argument. */ + INVALID_ARGUMENT(400), + + /** The deadline expired before the operation could succeed. */ + DEADLINE_EXCEEDED(504), + + /** Content was not found or request was denied for an entire class of users. */ + NOT_FOUND(404), + + /** The entity attempted to be created already exists */ + ALREADY_EXISTS(409), + + /** The caller doesn't have permission to execute the specified operation. */ + PERMISSION_DENIED(403), + + /** The resource has been exhausted e.g. per-user quota exhausted, file system out of space. */ + RESOURCE_EXHAUSTED(429), + + /** The client shouldn't retry until the system state has been explicitly handled. */ + FAILED_PRECONDITION(400), + + /** The operation was aborted. */ + ABORTED(409), + + /** The operation was attempted past the valid range e.g. seeking past the end of a file. */ + OUT_OF_RANGE(400), + + /** The operation is not implemented or is not supported/enabled for this operation. */ + UNIMPLEMENTED(501), + + /** The service is currently available e.g. as a transient condition. */ + UNAVAILABLE(503), + + /** Unrecoverable data loss or corruption. */ + DATA_LOSS(500), + + /** The requester doesn't have valid authentication credentials for the operation. */ + UNAUTHENTICATED(401); + + private val minHttpStatusCode: Int + private val maxHttpStatusCode: Int + + constructor(httpStatusCode: Int) { + minHttpStatusCode = httpStatusCode + maxHttpStatusCode = httpStatusCode + } + + constructor(minHttpStatusCode: Int, maxHttpStatusCode: Int) { + this.minHttpStatusCode = minHttpStatusCode + this.maxHttpStatusCode = maxHttpStatusCode + } + + private fun matches(httpStatusCode: Int): Boolean { + return httpStatusCode in minHttpStatusCode..maxHttpStatusCode + } + + public companion object { + /** + * Creates [SpanStatus] from HTTP status code. + * + * @param httpStatusCode the http status code + * @return span status equivalent of http status code or null if not found + */ + public fun fromHttpStatusCode(httpStatusCode: Int): SpanStatus? { + for (status in SpanStatus.values()) { + if (status.matches(httpStatusCode)) { + return status + } + } + return null + } + + /** + * Creates [SpanStatus] from HTTP status code. + * + * @param httpStatusCode the http status code + * @param defaultStatus the default SpanStatus + * @return span status equivalent of http status code or defaultStatus if not found + */ + public fun fromHttpStatusCode( + httpStatusCode: Int?, + defaultStatus: SpanStatus + ): SpanStatus { + val spanStatus = + if (httpStatusCode != null) fromHttpStatusCode(httpStatusCode) else defaultStatus + return spanStatus ?: defaultStatus + } + } +} diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/TransactionContext.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/TransactionContext.kt new file mode 100644 index 00000000..7c36a70b --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/TransactionContext.kt @@ -0,0 +1,45 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.protocol.SentryId +import io.sentry.kotlin.multiplatform.protocol.SpanId +import io.sentry.kotlin.multiplatform.protocol.TransactionNameSource + +/** The Transaction Context defines the metadata for a Performance Monitoring Transaction. */ +public interface TransactionContext : SpanContext { + /** The name of the transaction. */ + public val name: String + + /** The source of the transaction name. */ + public val transactionNameSource: TransactionNameSource + + /** Indicates if the parent transaction is sampled. */ + public val parentSampled: Boolean? +} + +/** The default Transaction Context implementation. */ +internal class TransactionContextImpl(override val operation: String) : TransactionContext { + override var name: String = "" + override var sampled: Boolean? = null + override var parentSampled: Boolean? = null + override var traceId: SentryId = SentryId.EMPTY_ID + override var spanId: SpanId = SpanId.EMPTY_ID + override var parentSpanId: SpanId? = null + override var description: String? = null + override var transactionNameSource: TransactionNameSource = TransactionNameSource.CUSTOM + + constructor(operation: String, name: String) : this(operation) { + this.name = name + } + + constructor(operation: String, name: String, sampled: Boolean?) : this(operation, name) { + this.sampled = sampled + } +} + +public fun TransactionContext(operation: String): TransactionContext = TransactionContextImpl(operation) + +public fun TransactionContext(name: String, operation: String): TransactionContext = + TransactionContextImpl(name = name, operation = operation) + +public fun TransactionContext(name: String, operation: String, sampled: Boolean): TransactionContext = + TransactionContextImpl(name = name, operation = operation, sampled = sampled) diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/protocol/SpanId.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/protocol/SpanId.kt new file mode 100644 index 00000000..cd295d4c --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/protocol/SpanId.kt @@ -0,0 +1,10 @@ +package io.sentry.kotlin.multiplatform.protocol + +/** The unique ID of a [io.sentry.kotlin.multiplatform.Span]. */ +public expect class SpanId(spanIdString: String) { + public companion object { + /** Returns a new empty SpanId */ + public val EMPTY_ID: SpanId + } + override fun toString(): String +} diff --git a/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/protocol/TransactionNameSource.kt b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/protocol/TransactionNameSource.kt new file mode 100644 index 00000000..d66d01e4 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonMain/kotlin/io/sentry/kotlin/multiplatform/protocol/TransactionNameSource.kt @@ -0,0 +1,79 @@ +package io.sentry.kotlin.multiplatform.protocol + +/** The source of the transaction name. */ +public enum class TransactionNameSource { + /** + * User-defined name + * + * + * Examples: + * + * + * * my_transaction + * + */ + CUSTOM, + + /** + * Raw URL, potentially containing identifiers. + * + * + * Examples: + * + * + * * /auth/login/john123/ + * * GET /auth/login/john123/ + * + */ + URL, + + /** + * Parametrized URL / route + * + * + * Examples: + * + * + * * /auth/login/:userId/ + * * GET /auth/login/{user}/ + * + */ + ROUTE, + + /** + * Name of the view handling the request. + * + * + * Examples: + * + * + * * UserListView + * + */ + VIEW, + + /** + * Named after a software component, such as a function or class name. + * + * + * Examples: + * + * + * * AuthLogin.login + * * LoginActivity.login_button + * + */ + COMPONENT, + + /** + * Name of a background task + * + * + * Examples: + * + * + * * sentry.tasks.do_something + * + */ + TASK; +} diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt index a23652f5..650a474f 100644 --- a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/BaseSentryTest.kt @@ -1,5 +1,6 @@ package io.sentry.kotlin.multiplatform +/** Base class for tests where Sentry.init is required. */ expect abstract class BaseSentryTest() { val platform: String val authToken: String? diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SentryTransactionTest.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SentryTransactionTest.kt new file mode 100644 index 00000000..c6f542cf --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SentryTransactionTest.kt @@ -0,0 +1,319 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.fakes.createFakeTransactionContext +import io.sentry.kotlin.multiplatform.utils.fakeDsn +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class SentryTransactionTest : BaseSentryTest() { + class Fixture { + val name = "test name" + val operation = "test description" + + internal fun getSut( + name: String = this@Fixture.name, + operation: String = this@Fixture.operation, + bindToScope: Boolean + ): Span { + return Sentry.startTransaction(name, operation, bindToScope) + } + + internal fun getSut( + name: String = this@Fixture.name, + operation: String = this@Fixture.operation + ): Span { + return Sentry.startTransaction(name, operation) + } + + internal fun getSutWithTransactionContext( + transactionContext: TransactionContext, + customSamplingContext: CustomSamplingContext + ): Span { + return Sentry.startTransaction(transactionContext, customSamplingContext) + } + } + + private lateinit var fixture: Fixture + + @BeforeTest + fun setup() { + fixture = Fixture() + } + + @Test + fun `GIVEN Sentry init AND tracesSampler set WHEN transaction finishes THEN receive correct name`() { + // GIVEN + var actualName = "" + Sentry.init { + it.dsn = fakeDsn + it.tracesSampler = { context -> + print("hello") + actualName = context.transactionContext.name + null + } + } + + // WHEN + val transaction = fixture.getSut(fixture.name, fixture.operation) + transaction.finish() + + // THEN + assertEquals(fixture.name, actualName) + } + + @Test + fun `GIVEN Sentry init AND tracesSampler set WHEN transaction started and finishes THEN receive correct spanId`() { + // GIVEN + var actualSpanId = "" + Sentry.init { + it.dsn = fakeDsn + it.tracesSampler = { context -> + actualSpanId = context.transactionContext.spanId.toString() + null + } + } + + // WHEN + val transaction = fixture.getSut() + transaction.finish() + + // THEN + assertEquals(transaction.spanId.toString(), actualSpanId) + } + + @Test + fun `GIVEN Sentry init AND parent + child spans WHEN both started and finished THEN child parentSpanId equals spanId of parent`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + + // WHEN + val transaction = fixture.getSut() + val child = transaction.startChild("child") + child.finish() + transaction.finish() + + // THEN + assertEquals(child.parentSpanId?.toString(), transaction.spanId.toString()) + } + + @Test + fun `GIVEN Sentry init AND parent + child spans with operation WHEN both started and finished THEN child parentSpanId equals spanId of parent`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + + // WHEN + val transaction = fixture.getSut() + val child = transaction.startChild("child", "description") + child.finish() + transaction.finish() + + // THEN + assertEquals(child.parentSpanId?.toString(), transaction.spanId.toString()) + } + + @Test + fun `GIVEN Sentry init WHEN transaction with bindToScope enabled started AND getSpan called THEN should return the correct transaction`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + + // WHEN + val transaction = + fixture.getSut(bindToScope = true) + val activeTransactionSpanId = Sentry.getSpan()?.spanId + + // THEN + assertEquals( + activeTransactionSpanId, + transaction.spanId, + "activeTransactionSpanId should be equal to transaction.spanId" + ) + transaction.finish() + } + + @Test + fun `GIVEN Sentry init WHEN transaction with bindToScope disabled started AND getSpan called THEN getSpan should be null`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + + // WHEN + val transaction = fixture.getSut() + val activeTransactionSpanId = Sentry.getSpan()?.spanId + + // THEN + assertNull(activeTransactionSpanId) + transaction.finish() + } + + @Test + fun `GIVEN Sentry init WHEN transaction with bindToScope enabled starts and finishes THEN calling getSpan after should be null`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + + // WHEN + val transaction = fixture.getSut(bindToScope = true) + transaction.finish() + + // THEN + assertNull(Sentry.getSpan()?.spanId) + } + + @Test + fun `GIVEN Sentry init WHEN transaction finishes with status OK THEN span status should be OK`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + + // WHEN + val transaction = fixture.getSut() + transaction.finish(SpanStatus.OK) + + // THEN + assertEquals(SpanStatus.OK, transaction.status) + } + + @Test + fun `GIVEN Sentry init WHEN overriding status on transaction THEN status returns correct value`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + + // WHEN + val transaction = fixture.getSut() + transaction.status = SpanStatus.ABORTED + + // THEN + assertEquals(SpanStatus.ABORTED, transaction.status) + } + + @Test + fun `GIVEN Sentry init WHEN overriding operation on transaction THEN operation returns correct value`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + val expectedOperation = "custom operation" + + // WHEN + val transaction = fixture.getSut() + transaction.operation = expectedOperation + + // THEN + assertEquals(expectedOperation, transaction.operation) + } + + @Test + fun `GIVEN Sentry init WHEN setData is used on transaction THEN getData will return correct value`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + val expectedKey = "key" + val expectedValue = "value" + + // WHEN + val transaction = fixture.getSut() + transaction.setData(expectedKey, expectedValue) + + // THEN + assertEquals(expectedValue, transaction.getData(expectedKey)) + } + + @Test + fun `GIVEN Sentry init WHEN setTag is used on transaction THEN getTag will return correct value`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + val expectedKey = "key" + val expectedValue = "value" + + // WHEN + val transaction = fixture.getSut() + transaction.setTag(expectedKey, expectedValue) + + // THEN + assertEquals(expectedValue, transaction.getTag(expectedKey)) + } + + @Test + fun `GIVEN Sentry init WHEN transaction starts with operation THEN span should return correct operation`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + val expectedOperation = "custom operation" + + // WHEN + val transaction = fixture.getSut(operation = expectedOperation) + + // THEN + assertEquals(expectedOperation, transaction.operation) + } + + @Test + fun `GIVEN Sentry init WHEN transaction is given description THEN span should return correct description`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + val expectedDescription = "custom description" + + // WHEN + val transaction = fixture.getSut() + transaction.description = expectedDescription + + // THEN + assertEquals(expectedDescription, transaction.description) + } + + @Test + fun `GIVEN Sentry init WHEN transaction calls finish THEN span should return isFinish true`() { + // GIVEN + Sentry.init { + it.dsn = fakeDsn + } + + // WHEN + val transaction = fixture.getSut() + transaction.finish() + + // THEN + assertEquals(true, transaction.isFinished) + } + + @Test + fun `GIVEN Sentry init WHEN startTransaction is given transactionContext with sampled not null THEN tracesSampler is ignored`() { + // GIVEN + var expectedTransactionContext: TransactionContext? = null + Sentry.init { + it.dsn = fakeDsn + it.tracesSampler = { _ -> + expectedTransactionContext = createFakeTransactionContext(operation = "different") + null + } + } + + // WHEN + fixture.getSutWithTransactionContext( + transactionContext = createFakeTransactionContext(sampled = true), + customSamplingContext = null + ) + + // THEN + assertNull(expectedTransactionContext) + } +} diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SpanStatusTest.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SpanStatusTest.kt new file mode 100644 index 00000000..1f2b6b3d --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/SpanStatusTest.kt @@ -0,0 +1,81 @@ +package io.sentry.kotlin.multiplatform + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class SpanStatusTest { + @Test + fun `GIVEN http status code in a matching range WHEN converting to SpanStatus THEN returns corresponding SpanStatus`() { + // GIVEN + val httpStatusCode = 202 + + // WHEN + val convertedStatus = SpanStatus.fromHttpStatusCode(httpStatusCode) + + // THEN + assertEquals(SpanStatus.OK, convertedStatus) + } + + @Test + fun `GIVEN specific http status code WHEN converting to SpanStatus THEN returns corresponding SpanStatus`() { + // GIVEN + val httpStatusCode = 504 + + // WHEN + val convertedStatus = SpanStatus.fromHttpStatusCode(httpStatusCode) + + // THEN + assertEquals(SpanStatus.DEADLINE_EXCEEDED, convertedStatus) + } + + @Test + fun `GIVEN http status code matching multiple statuses WHEN converting to SpanStatus THEN returns the first matching SpanStatus`() { + // GIVEN + val httpStatusCode = 500 + + // WHEN + val convertedStatus = SpanStatus.fromHttpStatusCode(httpStatusCode) + + // THEN + assertEquals(SpanStatus.INTERNAL_ERROR, convertedStatus) + } + + @Test + fun `GIVEN http status code with no matching SpanStatus WHEN converting to SpanStatus THEN returns null`() { + // GIVEN + val httpStatusCode = 302 + + // WHEN + val convertedStatus = SpanStatus.fromHttpStatusCode(httpStatusCode) + + // THEN + assertNull(convertedStatus) + } + + @Test + fun `GIVEN http status code with no matching SpanStatus AND default value provided WHEN converting to SpanStatus THEN returns the default value`() { + // GIVEN + val httpStatusCode = 302 + val defaultValue = SpanStatus.UNKNOWN_ERROR + + // WHEN + val convertedStatus = SpanStatus.fromHttpStatusCode(httpStatusCode, defaultValue) + + // THEN + assertEquals(defaultValue, convertedStatus) + } + + @Test + fun `GIVEN null http status code AND default value provided WHEN converting to SpanStatus THEN returns the default value`() { + // GIVEN + val httpStatusCode: Int? = null + val defaultValue = SpanStatus.UNKNOWN_ERROR + + // WHEN + val convertedStatus = SpanStatus.fromHttpStatusCode(httpStatusCode, defaultValue) + + // THEN + assertEquals(defaultValue, convertedStatus) + } +} diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/TracesSamplerTest.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/TracesSamplerTest.kt new file mode 100644 index 00000000..f592c020 --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/TracesSamplerTest.kt @@ -0,0 +1,261 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.fakes.createFakeCustomSamplingContext +import io.sentry.kotlin.multiplatform.fakes.createFakeTransactionContext +import io.sentry.kotlin.multiplatform.protocol.SentryId +import io.sentry.kotlin.multiplatform.protocol.SpanId +import io.sentry.kotlin.multiplatform.utils.fakeSentryIdString +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class TracesSamplerTest { + class Fixture { + val options = SentryOptions() + + private fun getSamplingContext( + transactionContext: TransactionContext, + customSamplingContext: CustomSamplingContext + ): SamplingContext { + return SamplingContext(transactionContext, customSamplingContext) + } + + internal fun getSut( + transactionContext: TransactionContext = createFakeTransactionContext(), + customSamplingContext: CustomSamplingContext = createFakeCustomSamplingContext(), + sampler: (SamplingContext) -> Double? + ): SamplingContext { + this.options.tracesSampler = sampler + return getSamplingContext(transactionContext, customSamplingContext) + } + } + + /** Returns the sample rate or null if not sampled. */ + private fun SamplingContext.getSampleRate(): Double? { + return fixture.options.tracesSampler?.invoke(this) + } + + private lateinit var fixture: Fixture + + @BeforeTest + fun setup() { + fixture = Fixture() + } + + @Test + fun `GIVEN null sampleRate WHEN tracesSampler callback set to sampleRate THEN return null sample rate`() { + // GIVEN + val sampleRate = null + + // WHEN + val sut = fixture.getSut { sampleRate } + + // THEM + assertEquals(sampleRate, sut.getSampleRate()) + } + + @Test + fun `GIVEN sampleRate WHEN traceSampler callback set to sampleRate THEN return correct sample rate`() { + // GIVEN + val sampleRate = 0.5 + + // WHEN + val sut = fixture.getSut { sampleRate } + + // THEN + assertEquals(sampleRate, sut.getSampleRate()) + } + + @Test + fun `GIVEN different sampleRates WHEN traceSampler callback set to sampleRates THEN can return different sample rates`() { + // GIVEN + val sampleRate1 = 0.5 + val sampleRate2 = 0.1 + + // WHEN + val sut = fixture.getSut { + if (it.transactionContext.name == "random") { + sampleRate1 + } else { + sampleRate2 + } + } + + // THEN + assertEquals(sampleRate2, sut.getSampleRate()) + } + + @Test + fun `GIVEN null sampleRate WHEN traceSampler callback set to sampleRate THEN return null`() { + // GIVEN + val sampleRate = null + + // WHEN + val sut = fixture.getSut { sampleRate } + + // THEN + assertNull(sut.getSampleRate()) + } + + @Test + fun `GIVEN transactionContext name WHEN tracerSampler set AND sampled THEN returns correct TransactionContext name`() { + // GIVEN + val expectedName = "testName" + val transactionContext = createFakeTransactionContext(name = expectedName) + + // WHEN + var actualName = "" + val sut = fixture.getSut(transactionContext = transactionContext) { + actualName = it.transactionContext.name + null + } + sut.getSampleRate() + + // THEN + assertEquals(expectedName, actualName) + } + + @Test + fun `GIVEN transactionContext spanId WHEN tracerSampler set AND sampled THEN returns correct TransactionContext spanId`() { + // GIVEN + val expectedSpanId = SpanId(fakeSentryIdString) + val transactionContext = createFakeTransactionContext(spanId = expectedSpanId) + + // WHEN + var actualSpanId: SpanId? = null + val sut = fixture.getSut(transactionContext = transactionContext) { + actualSpanId = it.transactionContext.spanId + null + } + sut.getSampleRate() + + // THEN + assertEquals(expectedSpanId, actualSpanId) + } + + @Test + fun `GIVEN transactionContext parentSpanId WHEN tracerSampler set AND sampled THEN returns correct TransactionContext parentSpanId`() { + // GIVEN + val expectedParentSpanId = SpanId("123") + val transactionContext = createFakeTransactionContext(parentSpanId = expectedParentSpanId) + + // WHEN + var actualParentSpanId: SpanId? = null + val sut = fixture.getSut(transactionContext = transactionContext) { + actualParentSpanId = it.transactionContext.parentSpanId + null + } + sut.getSampleRate() + + // THEN + assertEquals(expectedParentSpanId, actualParentSpanId) + } + + @Test + fun `GIVEN transactionContext description WHEN tracerSampler set AND sampled THEN returns correct TransactionContext description`() { + // GIVEN + val expectedDescription = "testDescription" + val transactionContext = createFakeTransactionContext(description = expectedDescription) + + // WHEN + var actualDescription: String? = null + val sut = fixture.getSut(transactionContext = transactionContext) { + actualDescription = it.transactionContext.description + null + } + sut.getSampleRate() + + // THEN + assertEquals(expectedDescription, actualDescription) + } + + @Test + fun `GIVEN transactionContext sampled WHEN tracerSampler set AND sampled THEN returns correct TransactionContext sampled`() { + // GIVEN + val expectedSampled = false + val transactionContext = createFakeTransactionContext(sampled = expectedSampled) + + // WHEN + var actualSampled: Boolean? = null + val sut = fixture.getSut(transactionContext = transactionContext) { + actualSampled = it.transactionContext.sampled + null + } + sut.getSampleRate() + + // THEN + assertEquals(expectedSampled, actualSampled) + } + + @Test + fun `GIVEN transactionContext parentSampled WHEN tracerSampler set AND sampled THEN returns correct TransactionContext parentSampled`() { + // GIVEN + val expectedParentSampled = false + val transactionContext = createFakeTransactionContext(parentSampled = expectedParentSampled) + + // WHEN + var actualParentSampled: Boolean? = null + val sut = fixture.getSut(transactionContext = transactionContext) { + actualParentSampled = it.transactionContext.parentSampled + null + } + sut.getSampleRate() + + // THEN + assertEquals(expectedParentSampled, actualParentSampled) + } + + @Test + fun `GIVEN transactionContext operation WHEN tracerSampler set AND sampled THEN returns correct TransactionContext operation`() { + // GIVEN + val expectedOperation = "testOperation" + val transactionContext = createFakeTransactionContext(operation = expectedOperation) + + // WHEN + var actualOperation: String? = null + val sut = fixture.getSut(transactionContext = transactionContext) { + actualOperation = it.transactionContext.operation + null + } + sut.getSampleRate() + + // THEN + assertEquals(expectedOperation, actualOperation) + } + + @Test + fun `GIVEN transactionContext traceId WHEN tracerSampler set AND sampled THEN returns correct TransactionContext traceId`() { + // GIVEN + val expectedTraceId = SentryId(fakeSentryIdString) + val transactionContext = createFakeTransactionContext(traceId = expectedTraceId) + + // WHEN + var actualTraceId: SentryId? = null + val sut = fixture.getSut(transactionContext = transactionContext) { + actualTraceId = it.transactionContext.traceId + null + } + sut.getSampleRate() + + // THEN + assertEquals(expectedTraceId, actualTraceId) + } + + @Test + fun `GIVEN customSamplingContext WHEN tracerSampler set AND sampled THEN returns correct customSamplingContext`() { + // GIVEN + val expectedCustomSamplingContext = mapOf("user_id" to 12345) + + // WHEN + var actualCustomSamplingContext: CustomSamplingContext = null + val sut = fixture.getSut(customSamplingContext = expectedCustomSamplingContext) { + actualCustomSamplingContext = it.customSamplingContext + null + } + sut.getSampleRate() + + // THEN + assertEquals(expectedCustomSamplingContext, actualCustomSamplingContext) + } +} diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/TransactionContextTest.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/TransactionContextTest.kt new file mode 100644 index 00000000..76bf12ba --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/TransactionContextTest.kt @@ -0,0 +1,186 @@ +package io.sentry.kotlin.multiplatform + +import io.sentry.kotlin.multiplatform.protocol.SentryId +import io.sentry.kotlin.multiplatform.protocol.SpanId +import io.sentry.kotlin.multiplatform.protocol.TransactionNameSource +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class TransactionContextTest { + class Fixture { + val name = "test name" + val operation = "test operation" + val sampled = false + + fun getSut( + name: String = this.name, + operation: String = this.operation, + sampled: Boolean + ): TransactionContext { + return TransactionContext(operation = operation, name = name, sampled = sampled) + } + + fun getSut( + operation: String + ): TransactionContext { + return TransactionContext(operation = operation) + } + + fun getSut( + name: String = this.name, + operation: String = this.operation + ): TransactionContext { + return TransactionContext(operation = operation, name = name) + } + } + + private lateinit var fixture: Fixture + + @BeforeTest + fun setup() { + fixture = Fixture() + } + + @Test + fun `GIVEN name WHEN transactionContext created THEN receive correct name`() { + // GIVEN + val expectedName = "other name" + + // WHEN + val transactionContext = fixture.getSut(name = expectedName) + val actualName = transactionContext.name + + // THEN + assertEquals(expectedName, actualName) + } + + @Test + fun `GIVEN operation WHEN transactionContext created THEN receive correct operation`() { + // GIVEN + val expectedOperation = "other operation" + + // WHEN + val transactionContext = fixture.getSut( + operation = expectedOperation + ) + val actualOperation = transactionContext.operation + + // THEN + assertEquals(expectedOperation, actualOperation) + } + + @Test + fun `GIVEN sampled WHEN transactionContext created THEN receive correct sampled`() { + // GIVEN + val expectedSampled = true + + // WHEN + val transactionContext = fixture.getSut( + sampled = expectedSampled + ) + val actualSampled = transactionContext.sampled + + // THEN + assertEquals(expectedSampled, actualSampled) + } + + @Test + fun `WHEN transactionContext created THEN receive default parentSampled`() { + // WHEN + val transactionContext = fixture.getSut() + val actualParentSampled = transactionContext.parentSampled + + // THEN + assertNull(actualParentSampled) + } + + @Test + fun `WHEN transactionContext created THEN receive default traceId`() { + // WHEN + val transactionContext = fixture.getSut() + val actualTraceId = transactionContext.traceId + + // THEN + assertEquals(SentryId.EMPTY_ID, actualTraceId) + } + + @Test + fun `WHEN transactionContext created THEN receive default spanId`() { + // WHEN + val transactionContext = fixture.getSut() + val actualSpanId = transactionContext.spanId + + // THEN + assertEquals(SpanId.EMPTY_ID, actualSpanId) + } + + @Test + fun `WHEN transactionContext created THEN receive default parentSpanId`() { + // WHEN + val transactionContext = fixture.getSut() + val actualParentSpanId = transactionContext.parentSpanId + + // THEN + assertEquals(null, actualParentSpanId) + } + + @Test + fun `WHEN transactionContext created THEN receive default description`() { + // WHEN + val transactionContext = fixture.getSut() + val actualDescription = transactionContext.description + + // THEN + assertEquals(null, actualDescription) + } + + @Test + fun `WHEN transactionContext created THEN receive default transactionNameSource`() { + // WHEN + val transactionContext = fixture.getSut() + val actualTransactionNameSource = transactionContext.transactionNameSource + + // THEN + assertEquals(TransactionNameSource.CUSTOM, actualTransactionNameSource) + } + + @Test + fun `WHEN transactionContext with name and operation THEN sampling decision and parent sampling is not set`() { + // WHEN + val transactionContext = fixture.getSut() + + // THEN + assertNull(transactionContext.sampled) + assertNull(transactionContext.parentSampled) + } + + @Test + fun `GIVEN operation WHEN transactionContext created only with operation THEN receive correct operation`() { + // GIVEN + val expectedOperation = "other operation" + + // WHEN + val transactionContext = fixture.getSut( + operation = expectedOperation + ) + val actualOperation = transactionContext.operation + + // THEN + assertEquals(expectedOperation, actualOperation) + } + + @Test + fun `GIVEN TransactionContext instance WHEN checking the type THEN should be TransactionContextImpl`() { + // GIVEN + val transactionContext = fixture.getSut() + + // WHEN + val actual = transactionContext is TransactionContextImpl + + // THEN + assertTrue(actual) + } +} diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/UserFeedbackTest.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/UserFeedbackTest.kt index c6b09de3..e1ce35ad 100644 --- a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/UserFeedbackTest.kt +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/UserFeedbackTest.kt @@ -6,7 +6,6 @@ import kotlin.test.Test import kotlin.test.assertEquals class UserFeedbackTest { - private val sentryIdString = "dcebada57d794590a6da3d1977eed58a" @Test diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/fakes/FakeSamplingContext.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/fakes/FakeSamplingContext.kt new file mode 100644 index 00000000..274aa9af --- /dev/null +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/fakes/FakeSamplingContext.kt @@ -0,0 +1,47 @@ +package io.sentry.kotlin.multiplatform.fakes + +import io.sentry.kotlin.multiplatform.CustomSamplingContext +import io.sentry.kotlin.multiplatform.TransactionContext +import io.sentry.kotlin.multiplatform.protocol.SentryId +import io.sentry.kotlin.multiplatform.protocol.SpanId +import io.sentry.kotlin.multiplatform.protocol.TransactionNameSource + +class FakeTransactionContext( + override val operation: String, + override val traceId: SentryId, + override val spanId: SpanId, + override val parentSpanId: SpanId?, + override val description: String?, + override val sampled: Boolean?, + override val name: String, + override val transactionNameSource: TransactionNameSource, + override val parentSampled: Boolean? +) : TransactionContext + +fun createFakeTransactionContext( + operation: String = "test", + traceId: SentryId = SentryId.EMPTY_ID, + spanId: SpanId = SpanId.EMPTY_ID, + parentSpanId: SpanId? = SpanId("123"), + description: String? = "test description", + sampled: Boolean? = null, + name: String = "test", + transactionNameSource: TransactionNameSource = TransactionNameSource.TASK, + parentSampled: Boolean? = null +): TransactionContext { + return FakeTransactionContext( + operation = operation, + traceId = traceId, + spanId = spanId, + parentSpanId = parentSpanId, + description = description, + sampled = sampled, + name = name, + transactionNameSource = transactionNameSource, + parentSampled = parentSampled + ) +} + +fun createFakeCustomSamplingContext(): CustomSamplingContext { + return mapOf("user_id" to 12345, "search_results" to "search results") +} diff --git a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/utils/Constants.kt b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/utils/Constants.kt index bbb38aa5..670eb5c5 100644 --- a/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/utils/Constants.kt +++ b/sentry-kotlin-multiplatform/src/commonTest/kotlin/io/sentry/kotlin/multiplatform/utils/Constants.kt @@ -5,3 +5,4 @@ const val projectSlug = "sentry-kotlin-multiplatform" const val fakeDsn = "https://abc@def.ingest.sentry.io/1234567" const val realDsn = "https://83f281ded2844eda83a8a413b080dbb9@o447951.ingest.sentry.io/5903800" +const val fakeSentryIdString = "626261be1dac4fac90e469032d828390" diff --git a/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt index fddd8726..41e99190 100644 --- a/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt +++ b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt @@ -15,9 +15,12 @@ object LoginImpl { * */ fun login(username: String? = null) { + val transaction = Sentry.startTransaction("Authentication", "login", bindToScope = true) try { validateUsername(username) } catch (exception: InvalidUsernameException) { + val activeSpan = Sentry.getSpan() + activeSpan?.startChild("child demo span", "child demo span description") val sentryId = Sentry.captureException(exception) { val breadcrumb = Breadcrumb.debug("this is a test breadcrumb") breadcrumb.setData("touch event", "on login") @@ -37,8 +40,11 @@ object LoginImpl { comments = "I had an error during login on ${Platform().platform}" } Sentry.captureUserFeedback(userFeedback) + activeSpan?.finish() } catch (exception: IllegalArgumentException) { throw exception + } finally { + transaction.finish() } } diff --git a/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt index ffdd4af6..e41b9533 100644 --- a/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt +++ b/sentry-samples/kmp-app-cocoapods/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt @@ -50,5 +50,6 @@ private fun optionsConfiguration(): OptionsConfiguration { event } } + it.tracesSampleRate = 1.0 } } diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/AuthenticationViewModel.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/AuthenticationViewModel.kt index b0d3f153..d6c76df7 100644 --- a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/AuthenticationViewModel.kt +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/models/AuthenticationViewModel.kt @@ -10,9 +10,12 @@ class AuthenticationViewModel : ViewModel() { fun login(withError: Boolean): Boolean { return if (withError) { + val transaction = Sentry.startTransaction("Authentication", "login", bindToScope = true) try { throw LoginException("Error logging in") } catch (exception: Exception) { + val activeSpan = Sentry.getSpan() + activeSpan?.startChild("child demo span", "child demo span description") Sentry.captureException(exception) { val breadcrumb = Breadcrumb.error("Error during login").apply { setData("touch event", "on login") @@ -22,7 +25,10 @@ class AuthenticationViewModel : ViewModel() { it.setTag("login", "failed") it.level = SentryLevel.ERROR } + activeSpan?.finish() false + } finally { + transaction.finish() } } else { true diff --git a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt index 60f1f6e1..60ca0a57 100644 --- a/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt +++ b/sentry-samples/kmp-app-mvvm-di/shared/src/commonMain/kotlin/sentry.kmp.demo/sentry/SentrySetup.kt @@ -22,6 +22,7 @@ private val optionsConfiguration: OptionsConfiguration = { breadcrumb.message = "Add message before every breadcrumb" breadcrumb } + it.tracesSampleRate = 1.0 } /** diff --git a/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt b/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt index fddd8726..41e99190 100644 --- a/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt +++ b/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/LoginImpl.kt @@ -15,9 +15,12 @@ object LoginImpl { * */ fun login(username: String? = null) { + val transaction = Sentry.startTransaction("Authentication", "login", bindToScope = true) try { validateUsername(username) } catch (exception: InvalidUsernameException) { + val activeSpan = Sentry.getSpan() + activeSpan?.startChild("child demo span", "child demo span description") val sentryId = Sentry.captureException(exception) { val breadcrumb = Breadcrumb.debug("this is a test breadcrumb") breadcrumb.setData("touch event", "on login") @@ -37,8 +40,11 @@ object LoginImpl { comments = "I had an error during login on ${Platform().platform}" } Sentry.captureUserFeedback(userFeedback) + activeSpan?.finish() } catch (exception: IllegalArgumentException) { throw exception + } finally { + transaction.finish() } } diff --git a/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt b/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt index be7e6e27..7516e93a 100644 --- a/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt +++ b/sentry-samples/kmp-app-spm/shared/src/commonMain/kotlin/sample.kmp.app/SentrySetup.kt @@ -49,5 +49,6 @@ private fun optionsConfiguration(): OptionsConfiguration { event } } + it.tracesSampleRate = 1.0 } }