Skip to content

Commit 1d3b156

Browse files
committed
fix(logging): filter client-error gRPC status codes from Bugsnag reporting
StatusException (thrown by coroutine-based gRPC stubs) was not being checked alongside StatusRuntimeException, silently breaking the existing UNAVAILABLE/CANCELLED filters. Additionally, client-error codes like INVALID_ARGUMENT and NOT_FOUND were always reported as bugs despite being expected validation failures. Unify the ignored status code set in ErrorUtils and reference it from the Bugsnag callback to avoid duplication. Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 64232b1 commit 1d3b156

2 files changed

Lines changed: 33 additions & 10 deletions

File tree

apps/flipcash/app/src/main/kotlin/com/flipcash/app/internal/debug/FlipcashBugsnagErrorCallback.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
package com.flipcash.app.internal.debug
22

33
import com.bugsnag.android.OnErrorCallback
4+
import com.getcode.utils.ErrorUtils
45
import io.grpc.StatusException
56

67
internal val FlipcashErrorCallback = OnErrorCallback onError@{ event ->
78
val error = event.originalError ?: return@onError true
89
val cause = error.cause ?: error
910

10-
// Discard gRPC deadline exceeded — these are expected transient timeouts
11-
if (cause is StatusException && cause.status.code == io.grpc.Status.Code.DEADLINE_EXCEEDED) {
11+
// Discard gRPC client-error / validation status codes — these are not bugs
12+
if (cause is StatusException && cause.status.code in ErrorUtils.ignoredGrpcStatusCodes) {
1213
return@onError false
1314
}
1415

libs/logging/src/main/kotlin/com/getcode/utils/ErrorUtils.kt

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.database.SQLException
44
import com.getcode.libs.logging.BuildConfig
55
import com.getcode.manager.TopBarManager
66
import io.grpc.Status
7+
import io.grpc.StatusException
78
import io.grpc.StatusRuntimeException
89
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException
910
import io.reactivex.rxjava3.exceptions.UndeliverableException
@@ -42,13 +43,7 @@ object ErrorUtils {
4243
throwable.cause ?: throwable
4344
else throwable
4445

45-
if (throwableCause is StatusRuntimeException) {
46-
when (throwableCause.status.code) {
47-
Status.Code.UNAVAILABLE -> return
48-
Status.Code.CANCELLED -> return
49-
else -> { /* fall through */ }
50-
}
51-
}
46+
if (isIgnoredGrpcStatus(throwableCause)) return
5247

5348
Timber.e(throwable)
5449

@@ -82,9 +77,36 @@ object ErrorUtils {
8277
throwable is UnknownHostException ||
8378
throwable.cause is UnknownHostException
8479

80+
val ignoredGrpcStatusCodes = setOf(
81+
// Transport/transient
82+
Status.Code.UNAVAILABLE,
83+
Status.Code.CANCELLED,
84+
Status.Code.DEADLINE_EXCEEDED,
85+
// Client-error / validation (not bugs)
86+
Status.Code.INVALID_ARGUMENT,
87+
Status.Code.NOT_FOUND,
88+
Status.Code.ALREADY_EXISTS,
89+
Status.Code.PERMISSION_DENIED,
90+
Status.Code.UNAUTHENTICATED,
91+
Status.Code.FAILED_PRECONDITION,
92+
Status.Code.OUT_OF_RANGE,
93+
Status.Code.RESOURCE_EXHAUSTED,
94+
)
95+
96+
private fun isIgnoredGrpcStatus(throwable: Throwable): Boolean {
97+
val code = when (throwable) {
98+
is StatusRuntimeException -> throwable.status.code
99+
is StatusException -> throwable.status.code
100+
else -> return false
101+
}
102+
return code in ignoredGrpcStatusCodes
103+
}
104+
85105
private fun isRuntimeError(throwable: Throwable): Boolean =
86106
throwable is StatusRuntimeException ||
87-
throwable.cause is StatusRuntimeException
107+
throwable is StatusException ||
108+
throwable.cause is StatusRuntimeException ||
109+
throwable.cause is StatusException
88110

89111
private fun isSuppressibleError(throwable: Throwable): Boolean =
90112
throwable is SQLException || throwable is SuppressibleException || throwable is TimeoutCancellationException

0 commit comments

Comments
 (0)