Skip to content

Commit 7f6f941

Browse files
authored
Merge pull request #202 from Automattic/add/js-exception-logging
Add support to send JS exceptions to Sentry
2 parents 6879b56 + 9313f55 commit 7f6f941

5 files changed

Lines changed: 109 additions & 3 deletions

File tree

AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/CrashLogging.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.automattic.android.tracks.crashlogging
22

33
interface CrashLogging {
4-
54
/**
65
* Records a breadcrumb during the app lifecycle but doesn't report an event. This basically
76
* adds more context for the next reports created and sent by [sendReport] or an unhandled
@@ -40,4 +39,9 @@ interface CrashLogging {
4039
tags: Map<String, String> = emptyMap(),
4140
message: String? = null
4241
)
42+
43+
fun sendJavaScriptReport(
44+
jsException: JsException,
45+
callback: JsExceptionCallback
46+
)
4347
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.automattic.android.tracks.crashlogging
2+
3+
data class JsException(
4+
val type: String,
5+
val message: String,
6+
var stackTrace: List<JsExceptionStackTraceElement>,
7+
val context: Map<String, Any>,
8+
val tags: Map<String, String>,
9+
val isHandled: Boolean,
10+
val handledBy: String
11+
)
12+
13+
data class JsExceptionStackTraceElement(
14+
val fileName: String?,
15+
val lineNumber: Int?,
16+
val colNumber: Int?,
17+
val function: String
18+
)
19+
20+
interface JsExceptionCallback {
21+
fun onReportSent(sent: Boolean)
22+
}

AutomatticTracks/src/main/java/com/automattic/android/tracks/crashlogging/internal/SentryCrashLogging.kt

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,22 @@ import com.automattic.android.tracks.crashlogging.CrashLogging
55
import com.automattic.android.tracks.crashlogging.CrashLoggingDataProvider
66
import com.automattic.android.tracks.crashlogging.CrashLoggingUser
77
import com.automattic.android.tracks.crashlogging.ExtraKnownKey
8+
import com.automattic.android.tracks.crashlogging.JsException
9+
import com.automattic.android.tracks.crashlogging.JsExceptionCallback
810
import com.automattic.android.tracks.crashlogging.PerformanceMonitoringConfig.Disabled
911
import com.automattic.android.tracks.crashlogging.PerformanceMonitoringConfig.Enabled
1012
import com.automattic.android.tracks.crashlogging.eventLevel
1113
import io.sentry.Breadcrumb
14+
import io.sentry.Sentry
1215
import io.sentry.SentryEvent
1316
import io.sentry.SentryLevel
1417
import io.sentry.SentryOptions
1518
import io.sentry.android.fragment.FragmentLifecycleIntegration
19+
import io.sentry.protocol.Mechanism
1620
import io.sentry.protocol.Message
21+
import io.sentry.protocol.SentryException
22+
import io.sentry.protocol.SentryStackFrame
23+
import io.sentry.protocol.SentryStackTrace
1724
import io.sentry.protocol.User
1825
import kotlinx.coroutines.CoroutineScope
1926
import kotlinx.coroutines.launch
@@ -24,7 +31,6 @@ internal class SentryCrashLogging constructor(
2431
private val sentryWrapper: SentryErrorTrackerWrapper,
2532
applicationScope: CoroutineScope
2633
) : CrashLogging {
27-
2834
init {
2935
sentryWrapper.initialize(application) { options ->
3036

@@ -134,6 +140,46 @@ internal class SentryCrashLogging constructor(
134140
sentryWrapper.captureEvent(event)
135141
}
136142

143+
override fun sendJavaScriptReport(
144+
jsException: JsException,
145+
callback: JsExceptionCallback
146+
) {
147+
val frames = jsException.stackTrace.map {
148+
SentryStackFrame().apply {
149+
this.filename = it.fileName
150+
this.function = it.function
151+
this.lineno = it.lineNumber
152+
this.colno = it.colNumber
153+
this.isInApp = true
154+
}
155+
}.toMutableList()
156+
157+
val sentryException = SentryException().apply {
158+
this.type = jsException.type
159+
this.value = jsException.message
160+
this.module = "javascript"
161+
this.stacktrace = SentryStackTrace().apply { this.frames = frames }
162+
this.mechanism = Mechanism().apply {
163+
this.isHandled = jsException.isHandled
164+
this.type = jsException.handledBy
165+
}
166+
}
167+
168+
val event = SentryEvent().apply {
169+
this.message = Message().apply { this.message = message }
170+
this.level = SentryLevel.FATAL
171+
this.platform = "javascript"
172+
this.appendTags(jsException.tags)
173+
this.exceptions = mutableListOf(sentryException)
174+
}
175+
176+
Sentry.configureScope { scope ->
177+
scope.setContexts("react_native_context", jsException.context)
178+
}
179+
sentryWrapper.captureEvent(event)
180+
callback.onReportSent(true)
181+
}
182+
137183
private fun SentryEvent.appendTags(tags: Map<String, String>) {
138184
for ((key, value) in tags) {
139185
this.setTag(key, value)

sampletracksapp/src/main/java/com/example/sampletracksapp/MainActivity.kt

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.example.sampletracksapp
22

33
import android.os.Bundle
4+
import android.util.Log
45
import androidx.appcompat.app.AppCompatActivity
56
import com.automattic.android.tracks.crashlogging.CrashLoggingDataProvider
67
import com.automattic.android.tracks.crashlogging.CrashLoggingOkHttpInterceptorProvider
78
import com.automattic.android.tracks.crashlogging.CrashLoggingProvider
89
import com.automattic.android.tracks.crashlogging.CrashLoggingUser
910
import com.automattic.android.tracks.crashlogging.EventLevel
1011
import com.automattic.android.tracks.crashlogging.ExtraKnownKey
12+
import com.automattic.android.tracks.crashlogging.JsException
13+
import com.automattic.android.tracks.crashlogging.JsExceptionCallback
14+
import com.automattic.android.tracks.crashlogging.JsExceptionStackTraceElement
1115
import com.automattic.android.tracks.crashlogging.PerformanceMonitoringConfig
1216
import com.automattic.android.tracks.crashlogging.RequestFormatter
1317
import com.automattic.android.tracks.crashlogging.performance.PerformanceMonitoringRepositoryProvider
@@ -26,7 +30,6 @@ import java.io.IOException
2630
import java.util.Locale
2731

2832
class MainActivity : AppCompatActivity() {
29-
3033
val transactionRepository: PerformanceTransactionRepository =
3134
PerformanceMonitoringRepositoryProvider.createInstance()
3235

@@ -89,6 +92,31 @@ class MainActivity : AppCompatActivity() {
8992
crashLogging.sendReport(exception = Exception("Exception from Tracks test app"))
9093
}
9194

95+
sendReportWithJavaScriptException.setOnClickListener {
96+
val callback = object : JsExceptionCallback {
97+
override fun onReportSent(sent: Boolean) {
98+
Log.d("JsExceptionCallback", "onReportSent: $sent")
99+
}
100+
}
101+
val jsException = JsException(
102+
type = "Error",
103+
message = "JavaScript exception from Tracks test app",
104+
stackTrace = listOf(
105+
JsExceptionStackTraceElement(
106+
fileName = "file.js",
107+
lineNumber = 1,
108+
colNumber = 1,
109+
function = "function"
110+
)
111+
),
112+
context = mapOf("context" to "value"),
113+
tags = mapOf("tag" to "SomeTag"),
114+
isHandled = true,
115+
handledBy = "SomeHandler"
116+
)
117+
crashLogging.sendJavaScriptReport(jsException, callback)
118+
}
119+
92120
recordBreadcrumbWithMessage.setOnClickListener {
93121
crashLogging.recordEvent(
94122
message = "Custom breadcrumb",

sampletracksapp/src/main/res/layout/activity_main.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020
android:layout_height="wrap_content"
2121
android:text="Send report with exception" />
2222

23+
<Button
24+
android:id="@+id/sendReportWithJavaScriptException"
25+
android:layout_width="wrap_content"
26+
android:layout_height="wrap_content"
27+
android:text="Send report with JS exception" />
28+
2329
<View
2430
android:layout_width="match_parent"
2531
android:layout_height="1dp"

0 commit comments

Comments
 (0)