Skip to content

Commit 227662a

Browse files
authored
Merge pull request #2366 from bugsnag/lemnik/http-onErrorCallback
HTTP OnErrorCallback
2 parents 83f6662 + 05f384e commit 227662a

6 files changed

Lines changed: 120 additions & 11 deletions

File tree

bugsnag-android-http-api/api/bugsnag-android-http-api.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ public abstract interface class com/bugsnag/android/http/HttpInstrumentedRequest
2020
}
2121

2222
public abstract interface class com/bugsnag/android/http/HttpInstrumentedResponse {
23+
public abstract fun getErrorCallback ()Lcom/bugsnag/android/OnErrorCallback;
2324
public abstract fun getReportedResponseBody ()Ljava/lang/String;
2425
public abstract fun getRequest ()Ljava/lang/Object;
2526
public abstract fun getResponse ()Ljava/lang/Object;
2627
public abstract fun isBreadcrumbReported ()Z
2728
public abstract fun isErrorReported ()Z
2829
public abstract fun setBreadcrumbReported (Z)V
30+
public abstract fun setErrorCallback (Lcom/bugsnag/android/OnErrorCallback;)V
2931
public abstract fun setErrorReported (Z)V
3032
public abstract fun setReportedResponseBody (Ljava/lang/String;)V
3133
}

bugsnag-android-http-api/src/main/java/com/bugsnag/android/http/HttpInstrumentedResponse.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.bugsnag.android.http;
22

3+
import com.bugsnag.android.OnErrorCallback;
4+
35
import androidx.annotation.NonNull;
46
import androidx.annotation.Nullable;
57

@@ -78,4 +80,21 @@ public interface HttpInstrumentedResponse<R, S> {
7880
* @param responseBody the response body to report
7981
*/
8082
void setReportedResponseBody(@Nullable String responseBody);
83+
84+
/**
85+
* Set an {@code OnErrorCallback} that can customise {@link com.bugsnag.android.Event Events}
86+
* created as a consequence to this response (when {@link #isErrorReported()} is true). Setting
87+
* this to {@code null} will remove any existing error callback.
88+
*
89+
* @param onErrorCallback the error callback to customise HTTP events
90+
*/
91+
void setErrorCallback(@Nullable OnErrorCallback onErrorCallback);
92+
93+
/**
94+
* Return the error callback if one has been set.
95+
*
96+
* @return the error callback for this response
97+
*/
98+
@Nullable
99+
OnErrorCallback getErrorCallback();
81100
}

bugsnag-plugin-android-okhttp/src/main/java/com/bugsnag/android/okhttp/BugsnagOkHttpInterceptor.kt

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.bugsnag.android.okhttp
22

3+
import android.net.Uri
34
import android.os.SystemClock
45
import android.util.Base64
56
import com.bugsnag.android.BreadcrumbType
67
import com.bugsnag.android.Client
78
import com.bugsnag.android.ErrorCaptureOptions
89
import com.bugsnag.android.ErrorOptions
910
import com.bugsnag.android.Logger
11+
import com.bugsnag.android.OnErrorCallback
1012
import com.bugsnag.android.http.HttpInstrumentedRequest
1113
import com.bugsnag.android.http.HttpInstrumentedResponse
1214
import com.bugsnag.android.http.HttpRequestCallback
@@ -128,15 +130,29 @@ internal class BugsnagOkHttpInterceptor(
128130
val okHttpRequest = resp.request
129131
val okHttpResponse = resp.response
130132

131-
val domain = okHttpRequest.url.host
132-
133133
event.errors.clear()
134-
event.addError("HTTPError", "${okHttpResponse?.code}: ${okHttpRequest.url}")
135-
event.context = "${okHttpRequest.method} $domain"
134+
event.addError("HTTPError", "${okHttpResponse?.code}: ${req.reportedUrl}")
135+
event.context = "${okHttpRequest.method} ${extractDomain(req)}"
136136
event.setHttpInfo(req, resp)
137-
true
137+
138+
return@notify resp.errorCallback?.onError(event) != false
139+
}
140+
}
141+
}
142+
143+
private fun extractDomain(req: OkHttpInstrumentedRequest): String {
144+
val reportedUrl = req.reportedUrl
145+
if (reportedUrl != null) {
146+
try {
147+
val host = Uri.parse(reportedUrl).host
148+
if (host != null) {
149+
return host
150+
}
151+
} catch (_: Exception) {
138152
}
139153
}
154+
155+
return req.request.url.host
140156
}
141157

142158
private fun collateMetadata(
@@ -265,6 +281,8 @@ private class OkHttpInstrumentedResponse(
265281

266282
private var isErrorReported = response != null && errorCodes[response.code]
267283

284+
private var errorCallback: OnErrorCallback? = null
285+
268286
override fun getRequest(): Request = request
269287
override fun getResponse(): Response? = response
270288

@@ -299,6 +317,14 @@ private class OkHttpInstrumentedResponse(
299317
isResponseBodySet = true
300318
}
301319

320+
override fun setErrorCallback(onErrorCallback: OnErrorCallback?) {
321+
this.errorCallback = onErrorCallback
322+
}
323+
324+
override fun getErrorCallback(): OnErrorCallback? {
325+
return errorCallback
326+
}
327+
302328
private fun extractResponseBody(): String? {
303329
if (maxResponseBodyCapture <= 0) {
304330
return null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.bugsnag.android.mazerunner.scenarios
2+
3+
import android.content.Context
4+
import com.bugsnag.android.Configuration
5+
6+
class OkHttpInstrumentationCallbackScenario(
7+
config: Configuration,
8+
context: Context,
9+
eventMetadata: String?
10+
) : OkHttpInstrumentationScenario(config, context, eventMetadata) {
11+
override val instrumentation
12+
get() = super.instrumentation
13+
.addRequestCallback { request ->
14+
request.reportedRequestBody = "testing request body"
15+
request.reportedUrl = "http://testingUrl.bugsnag.com"
16+
}
17+
.addResponseCallback { response ->
18+
response.reportedResponseBody = "testing response body"
19+
}
20+
}

features/fixtures/mazerunner/jvm-scenarios/src/main/java/com/bugsnag/android/mazerunner/scenarios/OkHttpInstrumentationScenario.kt

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.bugsnag.android.mazerunner.scenarios
33
import android.content.Context
44
import android.net.Uri
55
import com.bugsnag.android.Configuration
6+
import com.bugsnag.android.OnErrorCallback
67
import com.bugsnag.android.mazerunner.log
78
import com.bugsnag.android.okhttp.BugsnagOkHttp
89
import okhttp3.MediaType.Companion.toMediaType
@@ -16,16 +17,24 @@ import kotlin.concurrent.thread
1617
private const val MAX_CAPTURE_BYTES = 32L
1718
private val JSON = "application/json".toMediaType()
1819

19-
class OkHttpInstrumentationScenario(
20+
open class OkHttpInstrumentationScenario(
2021
config: Configuration,
2122
context: Context,
2223
eventMetadata: String?
2324
) : Scenario(config, context, eventMetadata) {
24-
private val instrumentation = BugsnagOkHttp()
25-
.maxRequestBodyCapture(MAX_CAPTURE_BYTES)
26-
.maxResponseBodyCapture(MAX_CAPTURE_BYTES)
27-
.logBreadcrumbs()
28-
.addHttpErrorCodes(400, 599)
25+
26+
protected open val instrumentation
27+
get() = BugsnagOkHttp()
28+
.maxRequestBodyCapture(MAX_CAPTURE_BYTES)
29+
.maxResponseBodyCapture(MAX_CAPTURE_BYTES)
30+
.logBreadcrumbs()
31+
.addHttpErrorCodes(400, 599)
32+
.addResponseCallback { response ->
33+
response.errorCallback = OnErrorCallback { event ->
34+
event.addMetadata("OkHttpInstrumentationScenario", "onErrorCallback", true)
35+
true
36+
}
37+
}
2938

3039
private val httpClient = OkHttpClient.Builder()
3140
.addInterceptor(instrumentation.createInterceptor())

features/full_tests/okhttp_instrumentation.feature

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,36 @@ Feature: Capturing network breadcrumbs
5050
And the event "response.bodyLength" is greater than 1
5151
And the event "response.body" is not null
5252

53+
# Validate the event metadata
54+
And the event "metaData.OkHttpInstrumentationScenario.onErrorCallback" is true
55+
56+
Scenario: HTTP Error reporting can be modified by callbacks
57+
When I configure the app to run in the "POST 400" state
58+
And I run "OkHttpInstrumentationCallbackScenario"
59+
And I wait to receive a reflection
60+
Then I wait to receive an error
61+
And the error payload field "events" is an array with 1 elements
62+
And the exception "errorClass" equals "HTTPError"
63+
And the exception "message" equals "400: http://testingUrl.bugsnag.com"
64+
And the event "context" equals "POST testingUrl.bugsnag.com"
65+
66+
# Validate request fields
67+
And the event "request.httpMethod" equals "POST"
68+
And the event "request.httpVersion" is not null
69+
And the event "request.bodyLength" is greater than 64
70+
And the error payload field "events.0.request.body" equals "testing request body"
71+
And the error payload field "events.0.request.url" equals "http://testingUrl.bugsnag.com"
72+
And the error payload field "events.0.request.headers.Authorization" equals "[REDACTED]"
73+
And the error payload field "events.0.request.params.password" equals "[REDACTED]"
74+
75+
# Validate response fields
76+
And the event "response.statusCode" equals 400
77+
And the event "response.bodyLength" is greater than 1
78+
And the event "response.body" equals "testing response body"
79+
80+
# Validate the event metadata
81+
And the event "metaData.OkHttpInstrumentationScenario.onErrorCallback" is true
82+
5383
Scenario: Failed GET requests send error reports when configured
5484
When I configure the app to run in the "GET 500" state
5585
And I run "OkHttpInstrumentationScenario"
@@ -72,6 +102,9 @@ Feature: Capturing network breadcrumbs
72102
And the event "response.bodyLength" is greater than 1
73103
And the event "response.body" is not null
74104

105+
# Validate the event metadata
106+
And the event "metaData.OkHttpInstrumentationScenario.onErrorCallback" is true
107+
75108
Scenario: Successful requests do not emit errors
76109
When I configure the app to run in the "POST 200" state
77110
And I run "OkHttpInstrumentationScenario"

0 commit comments

Comments
 (0)