diff --git a/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/CrashLogging.kt b/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/CrashLogging.kt index d7b09c19..66a14f4a 100644 --- a/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/CrashLogging.kt +++ b/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/CrashLogging.kt @@ -1,7 +1,6 @@ package com.automattic.android.tracks.crashlogging interface CrashLogging { - /** * Records a breadcrumb during the app lifecycle but doesn't report an event. This basically * adds more context for the next reports created and sent by [sendReport] or an unhandled @@ -40,4 +39,9 @@ interface CrashLogging { tags: Map = emptyMap(), message: String? = null ) + + fun sendJavaScriptReport( + jsException: JsException, + callback: JsExceptionCallback + ) } diff --git a/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/JsException.kt b/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/JsException.kt new file mode 100644 index 00000000..6c9fc2dc --- /dev/null +++ b/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/JsException.kt @@ -0,0 +1,22 @@ +package com.automattic.android.tracks.crashlogging + +data class JsException( + val type: String, + val message: String, + var stackTrace: List, + val context: Map, + val tags: Map, + val isHandled: Boolean, + val handledBy: String +) + +data class JsExceptionStackTraceElement( + val fileName: String?, + val lineNumber: Int?, + val colNumber: Int?, + val function: String +) + +interface JsExceptionCallback { + fun onReportSent(sent: Boolean) +} diff --git a/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/internal/SentryCrashLogging.kt b/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/internal/SentryCrashLogging.kt index d30870de..17ee8781 100644 --- a/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/internal/SentryCrashLogging.kt +++ b/AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/internal/SentryCrashLogging.kt @@ -5,15 +5,22 @@ import com.automattic.android.tracks.crashlogging.CrashLogging import com.automattic.android.tracks.crashlogging.CrashLoggingDataProvider import com.automattic.android.tracks.crashlogging.CrashLoggingUser import com.automattic.android.tracks.crashlogging.ExtraKnownKey +import com.automattic.android.tracks.crashlogging.JsException +import com.automattic.android.tracks.crashlogging.JsExceptionCallback import com.automattic.android.tracks.crashlogging.PerformanceMonitoringConfig.Disabled import com.automattic.android.tracks.crashlogging.PerformanceMonitoringConfig.Enabled import com.automattic.android.tracks.crashlogging.eventLevel import io.sentry.Breadcrumb +import io.sentry.Sentry import io.sentry.SentryEvent import io.sentry.SentryLevel import io.sentry.SentryOptions import io.sentry.android.fragment.FragmentLifecycleIntegration +import io.sentry.protocol.Mechanism import io.sentry.protocol.Message +import io.sentry.protocol.SentryException +import io.sentry.protocol.SentryStackFrame +import io.sentry.protocol.SentryStackTrace import io.sentry.protocol.User import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -24,7 +31,6 @@ internal class SentryCrashLogging constructor( private val sentryWrapper: SentryErrorTrackerWrapper, applicationScope: CoroutineScope ) : CrashLogging { - init { sentryWrapper.initialize(application) { options -> @@ -134,6 +140,46 @@ internal class SentryCrashLogging constructor( sentryWrapper.captureEvent(event) } + override fun sendJavaScriptReport( + jsException: JsException, + callback: JsExceptionCallback + ) { + val frames = jsException.stackTrace.map { + SentryStackFrame().apply { + this.filename = it.fileName + this.function = it.function + this.lineno = it.lineNumber + this.colno = it.colNumber + this.isInApp = true + } + }.toMutableList() + + val sentryException = SentryException().apply { + this.type = jsException.type + this.value = jsException.message + this.module = "javascript" + this.stacktrace = SentryStackTrace().apply { this.frames = frames } + this.mechanism = Mechanism().apply { + this.isHandled = jsException.isHandled + this.type = jsException.handledBy + } + } + + val event = SentryEvent().apply { + this.message = Message().apply { this.message = message } + this.level = SentryLevel.FATAL + this.platform = "javascript" + this.appendTags(jsException.tags) + this.exceptions = mutableListOf(sentryException) + } + + Sentry.configureScope { scope -> + scope.setContexts("react_native_context", jsException.context) + } + sentryWrapper.captureEvent(event) + callback.onReportSent(true) + } + private fun SentryEvent.appendTags(tags: Map) { for ((key, value) in tags) { this.setTag(key, value) diff --git a/sampletracksapp/src/main/java/com/example/sampletracksapp/MainActivity.kt b/sampletracksapp/src/main/java/com/example/sampletracksapp/MainActivity.kt index 6fb3bd16..512d4ea2 100644 --- a/sampletracksapp/src/main/java/com/example/sampletracksapp/MainActivity.kt +++ b/sampletracksapp/src/main/java/com/example/sampletracksapp/MainActivity.kt @@ -1,6 +1,7 @@ package com.example.sampletracksapp import android.os.Bundle +import android.util.Log import androidx.appcompat.app.AppCompatActivity import com.automattic.android.tracks.crashlogging.CrashLoggingDataProvider import com.automattic.android.tracks.crashlogging.CrashLoggingOkHttpInterceptorProvider @@ -8,6 +9,9 @@ import com.automattic.android.tracks.crashlogging.CrashLoggingProvider import com.automattic.android.tracks.crashlogging.CrashLoggingUser import com.automattic.android.tracks.crashlogging.EventLevel import com.automattic.android.tracks.crashlogging.ExtraKnownKey +import com.automattic.android.tracks.crashlogging.JsException +import com.automattic.android.tracks.crashlogging.JsExceptionCallback +import com.automattic.android.tracks.crashlogging.JsExceptionStackTraceElement import com.automattic.android.tracks.crashlogging.PerformanceMonitoringConfig import com.automattic.android.tracks.crashlogging.RequestFormatter import com.automattic.android.tracks.crashlogging.performance.PerformanceMonitoringRepositoryProvider @@ -26,7 +30,6 @@ import java.io.IOException import java.util.Locale class MainActivity : AppCompatActivity() { - val transactionRepository: PerformanceTransactionRepository = PerformanceMonitoringRepositoryProvider.createInstance() @@ -89,6 +92,31 @@ class MainActivity : AppCompatActivity() { crashLogging.sendReport(exception = Exception("Exception from Tracks test app")) } + sendReportWithJavaScriptException.setOnClickListener { + val callback = object : JsExceptionCallback { + override fun onReportSent(sent: Boolean) { + Log.d("JsExceptionCallback", "onReportSent: $sent") + } + } + val jsException = JsException( + type = "Error", + message = "JavaScript exception from Tracks test app", + stackTrace = listOf( + JsExceptionStackTraceElement( + fileName = "file.js", + lineNumber = 1, + colNumber = 1, + function = "function" + ) + ), + context = mapOf("context" to "value"), + tags = mapOf("tag" to "SomeTag"), + isHandled = true, + handledBy = "SomeHandler" + ) + crashLogging.sendJavaScriptReport(jsException, callback) + } + recordBreadcrumbWithMessage.setOnClickListener { crashLogging.recordEvent( message = "Custom breadcrumb", diff --git a/sampletracksapp/src/main/res/layout/activity_main.xml b/sampletracksapp/src/main/res/layout/activity_main.xml index d537954d..77c396a0 100644 --- a/sampletracksapp/src/main/res/layout/activity_main.xml +++ b/sampletracksapp/src/main/res/layout/activity_main.xml @@ -20,6 +20,12 @@ android:layout_height="wrap_content" android:text="Send report with exception" /> +