Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental session replay options to common code #275

Merged
merged 6 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

### Features

- Add experimental session replay options to common code ([#275](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/275))
```kotlin
Sentry.init { options ->
// Adjust these values for production
options.experimental.sessionReplay.onErrorSampleRate = 1.0
options.experimental.sessionReplay.sessionSampleRate = 1.0
}
```
- Add `Sentry.isEnabled()` API to common code ([#273](https://github.com/getsentry/sentry-kotlin-multiplatform/pull/273))

### Dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ public class io/sentry/kotlin/multiplatform/SentryOptions {
public final fun getEnableAutoSessionTracking ()Z
public final fun getEnableCaptureFailedRequests ()Z
public final fun getEnvironment ()Ljava/lang/String;
public final fun getExperimental ()Lio/sentry/kotlin/multiplatform/SentryOptions$ExperimentalOptions;
public final fun getFailedRequestStatusCodes ()Ljava/util/List;
public final fun getFailedRequestTargets ()Ljava/util/List;
public final fun getMaxAttachmentSize ()J
Expand Down Expand Up @@ -212,6 +213,49 @@ public class io/sentry/kotlin/multiplatform/SentryOptions {
public final fun setTracesSampleRate (Ljava/lang/Double;)V
}

public final class io/sentry/kotlin/multiplatform/SentryOptions$ExperimentalOptions {
public fun <init> ()V
public final fun getSessionReplay ()Lio/sentry/kotlin/multiplatform/SentryReplayOptions;
public final fun setSessionReplay (Lio/sentry/kotlin/multiplatform/SentryReplayOptions;)V
}

public final class io/sentry/kotlin/multiplatform/SentryReplayOptions {
public fun <init> ()V
public fun <init> (Ljava/lang/Double;Ljava/lang/Double;ZZLio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;)V
public synthetic fun <init> (Ljava/lang/Double;Ljava/lang/Double;ZZLio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/Double;
public final fun component2 ()Ljava/lang/Double;
public final fun component3 ()Z
public final fun component4 ()Z
public final fun component5 ()Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public final fun copy (Ljava/lang/Double;Ljava/lang/Double;ZZLio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;)Lio/sentry/kotlin/multiplatform/SentryReplayOptions;
public static synthetic fun copy$default (Lio/sentry/kotlin/multiplatform/SentryReplayOptions;Ljava/lang/Double;Ljava/lang/Double;ZZLio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;ILjava/lang/Object;)Lio/sentry/kotlin/multiplatform/SentryReplayOptions;
public fun equals (Ljava/lang/Object;)Z
public final fun getOnErrorSampleRate ()Ljava/lang/Double;
public final fun getQuality ()Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public final fun getRedactAllImages ()Z
public final fun getRedactAllText ()Z
public final fun getSessionSampleRate ()Ljava/lang/Double;
public fun hashCode ()I
public final fun setOnErrorSampleRate (Ljava/lang/Double;)V
public final fun setQuality (Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;)V
public final fun setRedactAllImages (Z)V
public final fun setRedactAllText (Z)V
public final fun setSessionSampleRate (Ljava/lang/Double;)V
public fun toString ()Ljava/lang/String;
}

public final class io/sentry/kotlin/multiplatform/SentryReplayOptions$Quality : java/lang/Enum {
public static final field HIGH Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public static final field LOW Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public static final field MEDIUM Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public final fun getBitRate ()I
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public final fun getSizeScale ()F
public static fun valueOf (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public static fun values ()[Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
}

public final class io/sentry/kotlin/multiplatform/protocol/Breadcrumb {
public static final field Companion Lio/sentry/kotlin/multiplatform/protocol/Breadcrumb$Companion;
public fun <init> ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ public class io/sentry/kotlin/multiplatform/SentryOptions {
public final fun getEnableAutoSessionTracking ()Z
public final fun getEnableCaptureFailedRequests ()Z
public final fun getEnvironment ()Ljava/lang/String;
public final fun getExperimental ()Lio/sentry/kotlin/multiplatform/SentryOptions$ExperimentalOptions;
public final fun getFailedRequestStatusCodes ()Ljava/util/List;
public final fun getFailedRequestTargets ()Ljava/util/List;
public final fun getMaxAttachmentSize ()J
Expand Down Expand Up @@ -209,6 +210,49 @@ public class io/sentry/kotlin/multiplatform/SentryOptions {
public final fun setTracesSampleRate (Ljava/lang/Double;)V
}

public final class io/sentry/kotlin/multiplatform/SentryOptions$ExperimentalOptions {
public fun <init> ()V
public final fun getSessionReplay ()Lio/sentry/kotlin/multiplatform/SentryReplayOptions;
public final fun setSessionReplay (Lio/sentry/kotlin/multiplatform/SentryReplayOptions;)V
}

public final class io/sentry/kotlin/multiplatform/SentryReplayOptions {
public fun <init> ()V
public fun <init> (Ljava/lang/Double;Ljava/lang/Double;ZZLio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;)V
public synthetic fun <init> (Ljava/lang/Double;Ljava/lang/Double;ZZLio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/Double;
public final fun component2 ()Ljava/lang/Double;
public final fun component3 ()Z
public final fun component4 ()Z
public final fun component5 ()Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public final fun copy (Ljava/lang/Double;Ljava/lang/Double;ZZLio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;)Lio/sentry/kotlin/multiplatform/SentryReplayOptions;
public static synthetic fun copy$default (Lio/sentry/kotlin/multiplatform/SentryReplayOptions;Ljava/lang/Double;Ljava/lang/Double;ZZLio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;ILjava/lang/Object;)Lio/sentry/kotlin/multiplatform/SentryReplayOptions;
public fun equals (Ljava/lang/Object;)Z
public final fun getOnErrorSampleRate ()Ljava/lang/Double;
public final fun getQuality ()Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public final fun getRedactAllImages ()Z
public final fun getRedactAllText ()Z
public final fun getSessionSampleRate ()Ljava/lang/Double;
public fun hashCode ()I
public final fun setOnErrorSampleRate (Ljava/lang/Double;)V
public final fun setQuality (Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;)V
public final fun setRedactAllImages (Z)V
public final fun setRedactAllText (Z)V
public final fun setSessionSampleRate (Ljava/lang/Double;)V
public fun toString ()Ljava/lang/String;
}

public final class io/sentry/kotlin/multiplatform/SentryReplayOptions$Quality : java/lang/Enum {
public static final field HIGH Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public static final field LOW Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public static final field MEDIUM Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public final fun getBitRate ()I
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public final fun getSizeScale ()F
public static fun valueOf (Ljava/lang/String;)Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
public static fun values ()[Lio/sentry/kotlin/multiplatform/SentryReplayOptions$Quality;
}

public final class io/sentry/kotlin/multiplatform/protocol/Breadcrumb {
public static final field Companion Lio/sentry/kotlin/multiplatform/protocol/Breadcrumb$Companion;
public fun <init> ()V
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,47 @@
package io.sentry.kotlin.multiplatform.extensions

import io.sentry.android.core.SentryAndroidOptions
import io.sentry.kotlin.multiplatform.JvmSentryReplayQuality
import io.sentry.kotlin.multiplatform.SentryOptions
import io.sentry.kotlin.multiplatform.SentryReplayOptions
import kotlin.collections.forEach as kForEach

internal fun SentryOptions.toAndroidSentryOptionsCallback(): (SentryAndroidOptions) -> Unit = {
internal fun SentryOptions.toAndroidSentryOptionsCallback(): (SentryAndroidOptions) -> Unit = { androidOptions ->
val kmpOptions = this

// Apply base options available to all JVM targets
it.applyJvmBaseOptions(this)
androidOptions.applyJvmBaseOptions(kmpOptions)

// Apply Android specific options
it.isAttachScreenshot = this.attachScreenshot
it.isAttachViewHierarchy = this.attachViewHierarchy
it.isAnrEnabled = this.isAnrEnabled
it.anrTimeoutIntervalMillis = this.anrTimeoutIntervalMillis
androidOptions.isAttachScreenshot = kmpOptions.attachScreenshot
androidOptions.isAttachViewHierarchy = kmpOptions.attachViewHierarchy
androidOptions.isAnrEnabled = kmpOptions.isAnrEnabled
androidOptions.anrTimeoutIntervalMillis = kmpOptions.anrTimeoutIntervalMillis

// Replay options
androidOptions.experimental.sessionReplay.redactAllText =
kmpOptions.experimental.sessionReplay.redactAllText
androidOptions.experimental.sessionReplay.redactAllImages =
kmpOptions.experimental.sessionReplay.redactAllImages
androidOptions.experimental.sessionReplay.sessionSampleRate =
kmpOptions.experimental.sessionReplay.sessionSampleRate
androidOptions.experimental.sessionReplay.errorSampleRate =
kmpOptions.experimental.sessionReplay.onErrorSampleRate
androidOptions.experimental.sessionReplay.quality =
kmpOptions.experimental.sessionReplay.quality.toAndroidSentryQuality()

// kForEach solves an issue with linter where it thinks forEach is the Java version
// see here: https://stackoverflow.com/questions/44751469/kotlin-extension-functions-suddenly-require-api-level-24/68897591#68897591
this.sdk?.packages?.kForEach { sdkPackage ->
it.sdkVersion?.addPackage(sdkPackage.name, sdkPackage.version)
androidOptions.sdkVersion?.addPackage(sdkPackage.name, sdkPackage.version)
}
}

internal fun SentryReplayOptions.Quality.toAndroidSentryQuality(): JvmSentryReplayQuality {
val kmpQuality = this
return when (kmpQuality) {
SentryReplayOptions.Quality.LOW -> JvmSentryReplayQuality.LOW
SentryReplayOptions.Quality.MEDIUM -> JvmSentryReplayQuality.MEDIUM
SentryReplayOptions.Quality.HIGH -> JvmSentryReplayQuality.HIGH

Check warning on line 45 in sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.android.kt

View check run for this annotation

Codecov / codecov/patch

sentry-kotlin-multiplatform/src/androidMain/kotlin/io/sentry/kotlin/multiplatform/extensions/SentryOptionsExtensions.android.kt#L45

Added line #L45 was not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import io.sentry.android.core.SentryAndroidOptions
import io.sentry.kotlin.multiplatform.extensions.toAndroidSentryOptionsCallback
import io.sentry.kotlin.multiplatform.utils.fakeDsn
import kotlin.test.assertEquals
import io.sentry.SentryReplayOptions as NativeSentryReplayOptions

actual interface PlatformOptions : CommonPlatformOptions {
val isAnrEnabled: Boolean
val anrTimeoutIntervalMillis: Long
val attachScreenshot: Boolean
val attachViewHierarchy: Boolean
val sessionReplay: NativeSentryReplayOptions
}

class SentryAndroidOptionsWrapper(private val androidOptions: SentryAndroidOptions) :
Expand Down Expand Up @@ -62,6 +64,9 @@ class SentryAndroidOptionsWrapper(private val androidOptions: SentryAndroidOptio
override val attachViewHierarchy: Boolean
get() = androidOptions.isAttachViewHierarchy

override val sessionReplay: NativeSentryReplayOptions
get() = androidOptions.experimental.sessionReplay

override fun applyFromOptions(options: SentryOptions) {
options.toAndroidSentryOptionsCallback().invoke(androidOptions)
}
Expand All @@ -75,6 +80,11 @@ actual fun PlatformOptions.assertPlatformSpecificOptions(options: SentryOptions)
assertEquals(attachViewHierarchy, options.attachViewHierarchy)
assertEquals(isAnrEnabled, options.isAnrEnabled)
assertEquals(anrTimeoutIntervalMillis, options.anrTimeoutIntervalMillis)
assertEquals(sessionReplay.redactAllText, options.experimental.sessionReplay.redactAllText)
assertEquals(sessionReplay.redactAllImages, options.experimental.sessionReplay.redactAllImages)
assertEquals(sessionReplay.errorSampleRate, options.experimental.sessionReplay.onErrorSampleRate)
assertEquals(sessionReplay.sessionSampleRate, options.experimental.sessionReplay.sessionSampleRate)
assertEquals(sessionReplay.quality.name, options.experimental.sessionReplay.quality.name)
}

actual fun createSentryPlatformOptionsConfiguration(): PlatformOptionsConfiguration = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.sentry.Scope
import io.sentry.SentryEvent
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.SentryReplayOptions
import io.sentry.UserFeedback
import io.sentry.protocol.Contexts
import io.sentry.protocol.Message
Expand All @@ -27,3 +28,4 @@ internal typealias JvmSentryEvent = SentryEvent
internal typealias JvmMessage = Message
internal typealias JvmSentryException = SentryException
internal typealias JvmContexts = Contexts
internal typealias JvmSentryReplayQuality = SentryReplayOptions.SentryReplayQuality
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,27 @@ public open class SentryOptions {
* [Android ANR](https://docs.sentry.io/platforms/android/configuration/app-not-respond/)
*/
public var anrTimeoutIntervalMillis: Long = DEFAULT_ANR_TIMEOUT_INTERVAL_MILLIS

/**
* Experimental options for new features, these options are going to be promoted to SentryOptions
* before GA.
*
* Beware that experimental options can change at any time.
*/
public var experimental: ExperimentalOptions = ExperimentalOptions()
private set

/**
* Experimental options for new features, these options are going to be promoted to SentryOptions
* before GA.
*
* Beware that experimental options can change at any time.
*/
public class ExperimentalOptions {
/**
* Experimental feature. The options for session replay.
* Currently available for **Android** and **iOS**.
*/
public var sessionReplay: SentryReplayOptions = SentryReplayOptions()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.sentry.kotlin.multiplatform

/**
* Configuration options for session replay.
*/
public data class SentryReplayOptions(
/**
* Indicates the percentage in which the replay for the session will be created.
* Specifying 0 means never, 1.0 means always. The value needs to be >= 0.0 and <= 1.0.
*
* The default is null (disabled).
*/
public var sessionSampleRate: Double? = null,

/**
* Indicates the percentage in which a 30 seconds replay will be sent with error events.
* Specifying 0 means never, 1.0 means always. The value needs to be >= 0.0 and <= 1.0.
*
* The default is null (disabled).
*/
public var onErrorSampleRate: Double? = null,

/**
* Redact all text content. Draws a rectangle of text bounds with text color on top.
*
* The default is true.
*/
public var redactAllText: Boolean = true,

/**
* Redact all image content. Draws a rectangle of image bounds with image's dominant color on top.
*
* The default is true.
*/
public var redactAllImages: Boolean = true,

/**
* Defines the quality of the session replay. The higher the quality, the more accurate the replay
* will be, but also more data to transfer and more CPU load, defaults to MEDIUM.
*/
public var quality: Quality = Quality.MEDIUM
) {
/**
* Quality of the session replay.
*/
@Suppress("MagicNumber")
public enum class Quality(
/** The scale related to the window size (in dp) at which the replay will be created. */
public val sizeScale: Float,
/**
* Defines the quality of the session replay. Higher bit rates have better replay quality, but
* also affect the final payload size to transfer, defaults to 40kbps.
*/
public val bitRate: Int
) {
/** Video Scale: 80% Bit Rate: 50.000 */
LOW(0.8f, 50000),

/** Video Scale: 100% Bit Rate: 75.000 */
MEDIUM(1.0f, 75000),

/** Video Scale: 100% Bit Rate: 100.000 */
HIGH(1.0f, 100000)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ class SentryOptionsTest : BaseSentryTest() {
assertEquals(2000L, options.appHangTimeoutIntervalMillis)
assertTrue(options.isAnrEnabled)
assertEquals(5000L, options.anrTimeoutIntervalMillis)
assertNull(options.experimental.sessionReplay.onErrorSampleRate)
assertNull(options.experimental.sessionReplay.sessionSampleRate)
assertTrue(options.experimental.sessionReplay.redactAllText)
assertTrue(options.experimental.sessionReplay.redactAllImages)
assertEquals(SentryReplayOptions.Quality.MEDIUM, options.experimental.sessionReplay.quality)
}

@Test
Expand All @@ -149,6 +154,11 @@ class SentryOptionsTest : BaseSentryTest() {
appHangTimeoutIntervalMillis = 1000L
isAnrEnabled = false
anrTimeoutIntervalMillis = 1000L
experimental.sessionReplay.onErrorSampleRate = 0.5
experimental.sessionReplay.sessionSampleRate = 0.5
experimental.sessionReplay.redactAllText = false
experimental.sessionReplay.redactAllImages = false
experimental.sessionReplay.quality = SentryReplayOptions.Quality.LOW
}

val platformOptions = createPlatformOptions()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,37 @@
package io.sentry.kotlin.multiplatform.extensions

import cocoapods.Sentry.SentryReplayOptions
import io.sentry.kotlin.multiplatform.CocoaSentryOptions
import io.sentry.kotlin.multiplatform.SentryOptions

internal fun SentryOptions.toIosOptionsConfiguration(): (CocoaSentryOptions?) -> Unit = {
// Apply base options available to all Cocoa/Apple targets
it?.applyCocoaBaseOptions(this)
internal fun SentryOptions.toIosOptionsConfiguration(): (CocoaSentryOptions?) -> Unit = { options ->
options?.let { cocoaOptions ->
val kmpOptions = this@toIosOptionsConfiguration

// Apply iOS specific options
it?.attachScreenshot = this.attachScreenshot
it?.attachViewHierarchy = this.attachViewHierarchy
// Apply base options available to all Cocoa/Apple targets
cocoaOptions.applyCocoaBaseOptions(kmpOptions)

// Apply iOS specific options
cocoaOptions.attachScreenshot = this.attachScreenshot
cocoaOptions.attachViewHierarchy = this.attachViewHierarchy

// Replay options
val replayOptions = SentryReplayOptions(
dictionary = mapOf(
// Setting the onErrorSampleRate like this, using setOnErrorSampleRate
// crashes on compose multiplatform for some unknown reason
"errorSampleRate" to kmpOptions.experimental.sessionReplay.onErrorSampleRate?.toFloat()
)
).apply {
setRedactAllText(kmpOptions.experimental.sessionReplay.redactAllText)
setRedactAllImages(kmpOptions.experimental.sessionReplay.redactAllImages)
kmpOptions.experimental.sessionReplay.sessionSampleRate?.let { setSessionSampleRate(it.toFloat()) }
setQuality(kmpOptions.experimental.sessionReplay.quality.ordinal.toLong())
}
cocoaOptions.experimental().setSessionReplay(replayOptions)
} ?: run {
// Log a warning if options is null
// TODO: Replace with actual logging when a logger is available
println("Warning: CocoaSentryOptions is null, skipping iOS configuration")
}
}
Loading
Loading