Skip to content

Commit f9dfedd

Browse files
authored
allow custom error handler (#177)
* add error handling * direct exception to error handler * add purge storage apis * make FrequencyFlushPolicy interval changeable * add unit tests * address comments * disable flaky test
1 parent b4449b1 commit f9dfedd

File tree

18 files changed

+201
-13
lines changed

18 files changed

+201
-13
lines changed

android/src/main/java/com/segment/analytics/kotlin/android/plugins/AndroidContextPlugin.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class AndroidContextPlugin : Plugin {
121121
}
122122
put(APP_BUILD_KEY, appBuild)
123123
}
124-
} catch (e: PackageManager.NameNotFoundException) {
124+
} catch (ignored: PackageManager.NameNotFoundException) {
125125
emptyJsonObject
126126
}
127127

@@ -300,7 +300,7 @@ fun getUniqueID(): String? {
300300
val md = MessageDigest.getInstance("SHA-256")
301301
md.update(wideVineId)
302302
return md.digest().toHexString()
303-
} catch (e: Exception) {
303+
} catch (ignored: Exception) {
304304
return null
305305
} finally {
306306
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {

android/src/main/java/com/segment/analytics/kotlin/android/plugins/AndroidLifecyclePlugin.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.segment.analytics.kotlin.android.utilities.DeepLinkUtils
1818
import com.segment.analytics.kotlin.core.Analytics
1919
import com.segment.analytics.kotlin.core.Storage
2020
import com.segment.analytics.kotlin.core.platform.Plugin
21+
import com.segment.analytics.kotlin.core.reportInternalError
2122
import kotlinx.coroutines.Dispatchers
2223
import kotlinx.coroutines.launch
2324
import kotlinx.serialization.json.buildJsonObject
@@ -63,7 +64,9 @@ class AndroidLifecyclePlugin() : Application.ActivityLifecycleCallbacks, Default
6364
packageInfo = try {
6465
packageManager.getPackageInfo(application.packageName, 0)
6566
} catch (e: PackageManager.NameNotFoundException) {
66-
throw AssertionError("Package not found: " + application.packageName)
67+
val error = AssertionError("Package not found: " + application.packageName)
68+
analytics.reportInternalError(error)
69+
throw error
6770
}
6871

6972
// setup lifecycle listeners
@@ -334,7 +337,7 @@ private fun getReferrerCompatible(activity: Activity): Uri? {
334337
// Try parsing the referrer URL; if it's invalid, return null
335338
try {
336339
Uri.parse(it)
337-
} catch (e: ParseException) {
340+
} catch (ignored: ParseException) {
338341
null
339342
}
340343
}

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,41 @@ open class Analytics protected constructor(
670670
* - Returns: A string representing the version in "BREAKING.FEATURE.FIX" format.
671671
*/
672672
fun version() = Analytics.version()
673+
674+
/**
675+
* Provides a list of finished, but unsent events.
676+
*/
677+
fun pendingUploads(): List<String> {
678+
return parseFilePaths(storage.read(Storage.Constants.Events))
679+
}
680+
681+
/**
682+
* Purge all pending event upload files.
683+
*/
684+
fun purgeStorage() {
685+
// do not call purgeStorage(filePath: String)
686+
// since we want to schedule all files deletion in the same dispatch all at once
687+
analyticsScope.launch(fileIODispatcher) {
688+
for (file in pendingUploads()) {
689+
try {
690+
storage.removeFile(file)
691+
}
692+
catch (ignored: Exception) {}
693+
}
694+
}
695+
}
696+
697+
/**
698+
* Purge a single event upload file.
699+
*/
700+
fun purgeStorage(filePath: String) {
701+
analyticsScope.launch(fileIODispatcher) {
702+
try {
703+
storage.removeFile(filePath)
704+
}
705+
catch (ignored: Exception) {}
706+
}
707+
}
673708
}
674709

675710

@@ -700,7 +735,7 @@ internal fun isAndroid(): Boolean {
700735
return try {
701736
Class.forName("com.segment.analytics.kotlin.android.AndroidStorage")
702737
true
703-
} catch (e: ClassNotFoundException) {
738+
} catch (ignored: ClassNotFoundException) {
704739
false
705740
}
706741
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ data class Configuration(
3737
var autoAddSegmentDestination: Boolean = true,
3838
var apiHost: String = DEFAULT_API_HOST,
3939
var cdnHost: String = DEFAULT_CDN_HOST,
40-
var requestFactory: RequestFactory = RequestFactory()
40+
var requestFactory: RequestFactory = RequestFactory(),
41+
var errorHandler: ErrorHandler? = null
4142
) {
4243
fun isValid(): Boolean {
4344
return writeKey.isNotBlank() && application != null
@@ -54,4 +55,6 @@ interface CoroutineConfiguration {
5455
val networkIODispatcher: CoroutineDispatcher
5556

5657
val fileIODispatcher: CoroutineDispatcher
57-
}
58+
}
59+
60+
typealias ErrorHandler = (Throwable) -> Unit
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.segment.analytics.kotlin.core
2+
3+
import com.segment.analytics.kotlin.core.platform.plugins.logger.segmentLog
4+
5+
/**
6+
* Reports an internal error to the user-defined error handler.
7+
*/
8+
fun Analytics.reportInternalError(error: Throwable) {
9+
configuration.errorHandler?.invoke(error)
10+
Analytics.reportInternalError(error)
11+
}
12+
13+
fun Analytics.Companion.reportInternalError(error: Throwable) {
14+
error.message?.let {
15+
Analytics.segmentLog(it)
16+
}
17+
}
18+
19+
fun Analytics.Companion.reportInternalError(error: String) {
20+
Analytics.segmentLog(error)
21+
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ class HTTPClient(
3333
val requestedURL: URL = try {
3434
URL(url)
3535
} catch (e: MalformedURLException) {
36-
throw IOException("Attempted to use malformed url: $url", e)
36+
val error = IOException("Attempted to use malformed url: $url", e)
37+
Analytics.reportInternalError(error)
38+
throw error
3739
}
3840
val connection = requestedURL.openConnection() as HttpURLConnection
3941
connection.connectTimeout = 15_000 // 15s
@@ -96,6 +98,7 @@ internal fun HttpURLConnection.createPostConnection(): Connection {
9698
inputStream = safeGetInputStream(this.connection)
9799
responseBody = inputStream?.bufferedReader()?.use(BufferedReader::readText)
98100
} catch (e: IOException) {
101+
Analytics.reportInternalError(e)
99102
responseBody = ("Could not read response body for rejected message: "
100103
+ e.toString())
101104
} finally {
@@ -151,7 +154,9 @@ open class RequestFactory {
151154
val requestedURL: URL = try {
152155
URL(url)
153156
} catch (e: MalformedURLException) {
154-
throw IOException("Attempted to use malformed url: $url", e)
157+
val error = IOException("Attempted to use malformed url: $url", e)
158+
Analytics.reportInternalError(error)
159+
throw error
155160
}
156161
val connection = requestedURL.openConnection() as HttpURLConnection
157162
connection.connectTimeout = 15_000 // 15s

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ internal fun Analytics.fetchSettings(
112112
log("Fetched Settings: $settingsString")
113113
LenientJson.decodeFromString(settingsString)
114114
} catch (ex: Exception) {
115+
reportInternalError(ex)
115116
Analytics.segmentLog(
116117
"${ex.message}: failed to fetch settings",
117118
kind = LogKind.ERROR

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ data class System(
3131
Settings.serializer(),
3232
storage.read(Storage.Constants.Settings) ?: ""
3333
)
34-
} catch (ex: Exception) {
34+
} catch (ignored: Exception) {
3535
configuration.defaultSettings
3636
}
3737
return System(

core/src/main/java/com/segment/analytics/kotlin/core/compat/JavaAnalytics.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,26 @@ class JavaAnalytics(val analytics: Analytics) {
369369
*/
370370
fun version() = analytics.version()
371371

372+
/**
373+
* Reports an internal error to the user-defined error handler.
374+
*/
375+
fun reportInternalError(error: Throwable) = analytics.reportInternalError(error)
376+
377+
/**
378+
* Provides a list of finished, but unsent events.
379+
*/
380+
fun pendingUploads(): List<String> = analytics.pendingUploads()
381+
382+
/**
383+
* Purge all pending event upload files.
384+
*/
385+
fun purgeStorage() = analytics.purgeStorage()
386+
387+
/**
388+
* Purge a single event upload file.
389+
*/
390+
fun purgeStorage(filePath: String) = analytics.purgeStorage(filePath)
391+
372392
private fun setup(analytics: Analytics) {
373393
store = analytics.store
374394
storage = analytics.storage

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ internal class EventPipeline(
110110
flushPolicies.forEach { flushPolicy -> flushPolicy.updateState(event) }
111111
}
112112
catch (e : Exception) {
113+
analytics.reportInternalError(e)
113114
Analytics.segmentLog("Error adding payload: $event", kind = LogKind.ERROR)
114115
}
115116

@@ -150,6 +151,7 @@ internal class EventPipeline(
150151
// Cleanup uploaded payloads
151152
analytics.log("$logTag uploaded $url")
152153
} catch (e: Exception) {
154+
analytics.reportInternalError(e)
153155
shouldCleanup = handleUploadException(e, file)
154156
}
155157

0 commit comments

Comments
 (0)