Skip to content

Commit

Permalink
Merge branch 'main' into ref/event-processor-deduplication
Browse files Browse the repository at this point in the history
  • Loading branch information
buenaflor authored Jan 22, 2025
2 parents d3f1d15 + 914a9ec commit a41f5f4
Show file tree
Hide file tree
Showing 11 changed files with 520 additions and 304 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
### Enhancements

- Print a warning if the rate limit was reached ([#2595](https://github.com/getsentry/sentry-dart/pull/2595))
- Add replay masking config to tags and report SDKs versions ([#2592](https://github.com/getsentry/sentry-dart/pull/2592))

### Fixes

Expand Down
100 changes: 84 additions & 16 deletions flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
package io.sentry.flutter

import android.util.Log
import io.sentry.Hint
import io.sentry.SentryEvent
import io.sentry.SentryLevel
import io.sentry.SentryOptions
import io.sentry.SentryOptions.Proxy
import io.sentry.SentryReplayOptions
import io.sentry.android.core.BuildConfig
import io.sentry.android.core.SentryAndroidOptions
import io.sentry.protocol.SdkVersion
import io.sentry.rrweb.RRWebOptionsEvent
import java.net.Proxy.Type
import java.util.Locale

class SentryFlutter(
private val androidSdk: String,
private val nativeSdk: String,
) {
class SentryFlutter {
companion object {
internal const val FLUTTER_SDK = "sentry.dart.flutter"
internal const val ANDROID_SDK = "sentry.java.android.flutter"
internal const val NATIVE_SDK = "sentry.native.android.flutter"
}

var autoPerformanceTracingEnabled = false

fun updateOptions(
Expand Down Expand Up @@ -114,14 +121,29 @@ class SentryFlutter(

var sdkVersion = options.sdkVersion
if (sdkVersion == null) {
sdkVersion = SdkVersion(androidSdk, BuildConfig.VERSION_NAME)
sdkVersion = SdkVersion(ANDROID_SDK, BuildConfig.VERSION_NAME)
} else {
sdkVersion.name = androidSdk
sdkVersion.name = ANDROID_SDK
}

options.sdkVersion = sdkVersion
options.sentryClientName = "$androidSdk/${BuildConfig.VERSION_NAME}"
options.nativeSdkName = nativeSdk
options.sentryClientName = "$ANDROID_SDK/${BuildConfig.VERSION_NAME}"
options.nativeSdkName = NATIVE_SDK

data.getIfNotNull<Map<String, Any>>("sdk") { flutterSdk ->
flutterSdk.getIfNotNull<List<String>>("integrations") {
it.forEach { integration ->
sdkVersion.addIntegration(integration)
}
}
flutterSdk.getIfNotNull<List<Map<String, String>>>("packages") {
it.forEach { fPackage ->
sdkVersion.addPackage(fPackage["name"] as String, fPackage["version"] as String)
}
}
}

options.beforeSend = BeforeSendCallbackImpl()

data.getIfNotNull<Int>("connectionTimeoutMillis") {
options.connectionTimeoutMillis = it
Expand Down Expand Up @@ -154,30 +176,51 @@ class SentryFlutter(
}
}

data.getIfNotNull<Map<String, Any>>("replay") {
updateReplayOptions(options.sessionReplay, it)
data.getIfNotNull<Map<String, Any>>("replay") { replayArgs ->
updateReplayOptions(options, replayArgs)

data.getIfNotNull<Map<String, Any>>("sdk") {
options.sessionReplay.sdkVersion = SdkVersion(it["name"] as String, it["version"] as String)
}
}
}

fun updateReplayOptions(
options: SentryReplayOptions,
private fun updateReplayOptions(
options: SentryAndroidOptions,
data: Map<String, Any>,
) {
options.quality =
val replayOptions = options.sessionReplay
replayOptions.quality =
when (data["quality"] as? String) {
"low" -> SentryReplayOptions.SentryReplayQuality.LOW
"high" -> SentryReplayOptions.SentryReplayQuality.HIGH
else -> {
SentryReplayOptions.SentryReplayQuality.MEDIUM
}
}
options.sessionSampleRate = data["sessionSampleRate"] as? Double
options.onErrorSampleRate = data["onErrorSampleRate"] as? Double
replayOptions.sessionSampleRate = (data["sessionSampleRate"] as? Number)?.toDouble()
replayOptions.onErrorSampleRate = (data["onErrorSampleRate"] as? Number)?.toDouble()

// Disable native tracking of orientation change (causes replay restart)
// because we don't have the new size from Flutter yet. Instead, we'll
// trigger onConfigurationChanged() manually in setReplayConfig().
options.setTrackOrientationChange(false)
replayOptions.setTrackOrientationChange(false)

@Suppress("UNCHECKED_CAST")
val tags = (data["tags"] as? Map<String, Any>) ?: mapOf()
options.beforeSendReplay =
SentryOptions.BeforeSendReplayCallback { event, hint ->
hint.replayRecording?.payload?.firstOrNull { it is RRWebOptionsEvent }?.let { optionsEvent ->
val payload = (optionsEvent as RRWebOptionsEvent).optionsPayload

// Remove defaults set by the native SDK.
payload.filterKeys { it.contains("mask") }.forEach { (k, _) -> payload.remove(k) }

// Now, set the Flutter-specific values.
payload.putAll(tags)
}
event
}
}
}

Expand All @@ -191,3 +234,28 @@ private fun <T> Map<String, Any>.getIfNotNull(
callback(it)
}
}

private class BeforeSendCallbackImpl : SentryOptions.BeforeSendCallback {
override fun execute(
event: SentryEvent,
hint: Hint,
): SentryEvent {
event.sdk?.let {
when (it.name) {
SentryFlutter.FLUTTER_SDK -> setEventEnvironmentTag(event, "flutter", "dart")
SentryFlutter.ANDROID_SDK -> setEventEnvironmentTag(event, environment = "java")
SentryFlutter.NATIVE_SDK -> setEventEnvironmentTag(event, environment = "native")
}
}
return event
}

private fun setEventEnvironmentTag(
event: SentryEvent,
origin: String = "android",
environment: String,
) {
event.setTag("event.origin", origin)
event.setTag("event.environment", environment)
}
}
145 changes: 36 additions & 109 deletions flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutterPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.sentry.Breadcrumb
import io.sentry.DateUtils
import io.sentry.Hint
import io.sentry.HubAdapter
import io.sentry.Sentry
import io.sentry.SentryEvent
import io.sentry.SentryOptions
import io.sentry.android.core.ActivityFramesTracker
import io.sentry.android.core.InternalSentrySdk
import io.sentry.android.core.LoadClass
Expand All @@ -35,10 +32,8 @@ import io.sentry.android.core.performance.TimeSpan
import io.sentry.android.replay.ReplayIntegration
import io.sentry.android.replay.ScreenshotRecorderConfig
import io.sentry.protocol.DebugImage
import io.sentry.protocol.SdkVersion
import io.sentry.protocol.SentryId
import io.sentry.protocol.User
import io.sentry.rrweb.RRWebOptionsEvent
import io.sentry.transport.CurrentDateProvider
import java.io.File
import java.lang.ref.WeakReference
Expand Down Expand Up @@ -79,11 +74,7 @@ class SentryFlutterPlugin :
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sentry_flutter")
channel.setMethodCallHandler(this)

sentryFlutter =
SentryFlutter(
androidSdk = ANDROID_SDK,
nativeSdk = NATIVE_SDK,
)
sentryFlutter = SentryFlutter()
}

@Suppress("CyclomaticComplexMethod")
Expand Down Expand Up @@ -165,57 +156,45 @@ class SentryFlutterPlugin :
framesTracker = ActivityFramesTracker(LoadClass(), options)
}

options.beforeSend = BeforeSendCallbackImpl(options.sdkVersion)

// Replace the default ReplayIntegration with a Flutter-specific recorder.
options.integrations.removeAll { it is ReplayIntegration }
val cacheDirPath = options.cacheDirPath
val replayOptions = options.sessionReplay
val isReplayEnabled = replayOptions.isSessionReplayEnabled || replayOptions.isSessionReplayForErrorsEnabled
if (cacheDirPath != null && isReplayEnabled) {
replay =
ReplayIntegration(
context,
dateProvider = CurrentDateProvider.getInstance(),
recorderProvider = { SentryFlutterReplayRecorder(channel, replay) },
recorderConfigProvider = {
Log.i(
"Sentry",
"Replay configuration requested. Returning: %dx%d at %d FPS, %d BPS".format(
replayConfig.recordingWidth,
replayConfig.recordingHeight,
replayConfig.frameRate,
replayConfig.bitRate,
),
)
replayConfig
},
replayCacheProvider = null,
)
replay.breadcrumbConverter = SentryFlutterReplayBreadcrumbConverter()
options.addIntegration(replay)
options.setReplayController(replay)

options.beforeSendReplay =
SentryOptions.BeforeSendReplayCallback { event, hint ->
hint.replayRecording?.payload?.firstOrNull { it is RRWebOptionsEvent }?.let { optionsEvent ->
val payload = (optionsEvent as RRWebOptionsEvent).optionsPayload

// Remove defaults set by the native SDK.
payload.filterKeys { it.contains("mask") }.forEach { (k, _) -> payload.remove(k) }

// Now, set the Flutter-specific values.
// TODO do this in a followup PR
}
event
}
} else {
options.setReplayController(null)
}
setupReplay(options)
}
result.success("")
}

private fun setupReplay(options: SentryAndroidOptions) {
// Replace the default ReplayIntegration with a Flutter-specific recorder.
options.integrations.removeAll { it is ReplayIntegration }
val cacheDirPath = options.cacheDirPath
val replayOptions = options.sessionReplay
val isReplayEnabled = replayOptions.isSessionReplayEnabled || replayOptions.isSessionReplayForErrorsEnabled
if (cacheDirPath != null && isReplayEnabled) {
replay =
ReplayIntegration(
context,
dateProvider = CurrentDateProvider.getInstance(),
recorderProvider = { SentryFlutterReplayRecorder(channel, replay) },
recorderConfigProvider = {
Log.i(
"Sentry",
"Replay configuration requested. Returning: %dx%d at %d FPS, %d BPS".format(
replayConfig.recordingWidth,
replayConfig.recordingHeight,
replayConfig.frameRate,
replayConfig.bitRate,
),
)
replayConfig
},
replayCacheProvider = null,
)
replay.breadcrumbConverter = SentryFlutterReplayBreadcrumbConverter()
options.addIntegration(replay)
options.setReplayController(replay)
} else {
options.setReplayController(null)
}
}

private fun fetchNativeAppStart(result: Result) {
if (!sentryFlutter.autoPerformanceTracingEnabled) {
result.success(null)
Expand Down Expand Up @@ -537,61 +516,9 @@ class SentryFlutterPlugin :
result.success("")
}

private class BeforeSendCallbackImpl(
private val sdkVersion: SdkVersion?,
) : SentryOptions.BeforeSendCallback {
override fun execute(
event: SentryEvent,
hint: Hint,
): SentryEvent {
setEventOriginTag(event)
addPackages(event, sdkVersion)
return event
}
}

companion object {
private const val FLUTTER_SDK = "sentry.dart.flutter"
private const val ANDROID_SDK = "sentry.java.android.flutter"
private const val NATIVE_SDK = "sentry.native.android.flutter"
private const val NATIVE_CRASH_WAIT_TIME = 500L

private fun setEventOriginTag(event: SentryEvent) {
event.sdk?.let {
when (it.name) {
FLUTTER_SDK -> setEventEnvironmentTag(event, "flutter", "dart")
ANDROID_SDK -> setEventEnvironmentTag(event, environment = "java")
NATIVE_SDK -> setEventEnvironmentTag(event, environment = "native")
else -> return
}
}
}

private fun setEventEnvironmentTag(
event: SentryEvent,
origin: String = "android",
environment: String,
) {
event.setTag("event.origin", origin)
event.setTag("event.environment", environment)
}

private fun addPackages(
event: SentryEvent,
sdk: SdkVersion?,
) {
event.sdk?.let {
if (it.name == FLUTTER_SDK) {
sdk?.packageSet?.forEach { sentryPackage ->
it.addPackage(sentryPackage.name, sentryPackage.version)
}
sdk?.integrationSet?.forEach { integration ->
it.addIntegration(integration)
}
}
}
}

private fun crash() {
val exception = RuntimeException("FlutterSentry Native Integration: Sample RuntimeException")
val mainThread = Looper.getMainLooper().thread
Expand Down
Loading

0 comments on commit a41f5f4

Please sign in to comment.