Skip to content

Commit 75de6f1

Browse files
authored
Add CoroutineExceptionHandler and try/catch blocks over critical area… (#146)
* Add CoroutineExceptionHandler and try/catch blocks over critical areas. Also moved AndroidLogger setup to earlier time to handle early crashes. * Move try/catch to timeline.add() instead of build.
1 parent 19cc5e9 commit 75de6f1

File tree

5 files changed

+38
-7
lines changed

5 files changed

+38
-7
lines changed

android/src/main/java/com/segment/analytics/kotlin/android/AndroidAnalytics.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public fun Analytics(
5050
context: Context,
5151
configs: Configuration.() -> Unit
5252
): Analytics {
53+
Analytics.setLogger(AndroidLogger())
5354
require(writeKey.isNotBlank()) { "writeKey cannot be blank " }
5455
val conf = Configuration(
5556
writeKey = writeKey,
@@ -66,7 +67,6 @@ public fun Analytics(
6667
private fun Analytics.startup() {
6768
add(AndroidContextPlugin())
6869
add(AndroidLifecyclePlugin())
69-
Analytics.setLogger(AndroidLogger())
7070
}
7171

7272
/**

core/src/main/java/com/segment/analytics/kotlin/core/Analytics.kt

+7-4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import com.segment.analytics.kotlin.core.platform.plugins.ContextPlugin
88
import com.segment.analytics.kotlin.core.platform.plugins.SegmentDestination
99
import com.segment.analytics.kotlin.core.platform.plugins.StartupQueue
1010
import com.segment.analytics.kotlin.core.platform.plugins.UserInfoPlugin
11-
import com.segment.analytics.kotlin.core.platform.plugins.logger.ConsoleLogger
12-
import com.segment.analytics.kotlin.core.platform.plugins.logger.Logger
13-
import com.segment.analytics.kotlin.core.platform.plugins.logger.log
11+
import com.segment.analytics.kotlin.core.platform.plugins.logger.*
1412
import kotlinx.coroutines.*
1513
import kotlinx.serialization.DeserializationStrategy
1614
import kotlinx.serialization.SerializationStrategy
@@ -87,7 +85,12 @@ open class Analytics protected constructor(
8785
constructor(configuration: Configuration) : this(configuration,
8886
object : CoroutineConfiguration {
8987
override val store = Store()
90-
override val analyticsScope = CoroutineScope(SupervisorJob())
88+
val exceptionHandler = CoroutineExceptionHandler { _, t ->
89+
Analytics.segmentLog(
90+
"Caught Exception in Analytics Scope: ${t}"
91+
)
92+
}
93+
override val analyticsScope = CoroutineScope(SupervisorJob() + exceptionHandler)
9194
override val analyticsDispatcher : CloseableCoroutineDispatcher =
9295
Executors.newCachedThreadPool().asCoroutineDispatcher()
9396
override val networkIODispatcher : CloseableCoroutineDispatcher =

core/src/main/java/com/segment/analytics/kotlin/core/platform/Mediator.kt

+5-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ internal class Mediator(internal var plugins: CopyOnWriteArrayList<Plugin> = Cop
5151

5252
fun applyClosure(closure: (Plugin) -> Unit) {
5353
plugins.forEach {
54-
closure(it)
54+
try {
55+
closure(it)
56+
} catch (t: Throwable) {
57+
Analytics.segmentLog("Caught Exception applying closure to plugin: $it: $t")
58+
}
5559
}
5660
}
5761

core/src/main/java/com/segment/analytics/kotlin/core/platform/Timeline.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.segment.analytics.kotlin.core.platform
33
import com.segment.analytics.kotlin.core.Analytics
44
import com.segment.analytics.kotlin.core.BaseEvent
55
import com.segment.analytics.kotlin.core.System
6+
import com.segment.analytics.kotlin.core.platform.plugins.logger.segmentLog
67
import kotlinx.coroutines.launch
78
import java.util.concurrent.CopyOnWriteArrayList
89
import kotlin.reflect.KClass
@@ -60,7 +61,11 @@ internal class Timeline {
6061

6162
// Register a new plugin
6263
fun add(plugin: Plugin) {
63-
plugin.setup(analytics)
64+
try {
65+
plugin.setup(analytics)
66+
} catch (t: Throwable) {
67+
Analytics.segmentLog("Caught Exception while setting up plugin $plugin: $t")
68+
}
6469
plugins[plugin.type]?.add(plugin)
6570
with(analytics) {
6671
analyticsScope.launch(analyticsDispatcher) {

core/src/test/kotlin/com/segment/analytics/kotlin/core/PluginTests.kt

+19
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import kotlinx.serialization.json.put
1212
import org.junit.jupiter.api.Assertions
1313
import org.junit.jupiter.api.Test
1414
import org.junit.jupiter.api.TestInstance
15+
import org.junit.jupiter.api.assertDoesNotThrow
1516
import java.util.*
1617

1718
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -59,4 +60,22 @@ class PluginTests {
5960
Assertions.assertEquals(trackEvent, result)
6061
verify(exactly = 1) { plugin.execute(trackEvent) }
6162
}
63+
64+
@Test
65+
fun `throwing exception in setup() is handled in timeline`() {
66+
val plugin = object : Plugin {
67+
override val type: Plugin.Type = Plugin.Type.Before
68+
override lateinit var analytics: Analytics
69+
override fun setup(analytics: Analytics) {
70+
super.setup(analytics)
71+
throw Exception("Boom!")
72+
}
73+
}
74+
val spy = spyk(plugin)
75+
assertDoesNotThrow {
76+
timeline.add(spy)
77+
}
78+
verify(exactly = 1) { spy.setup(mockAnalytics) }
79+
Assertions.assertTrue(spy.analytics === mockAnalytics)
80+
}
6281
}

0 commit comments

Comments
 (0)