Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
32bcb4a
feat(http): introduced new bugsnag-android-http-api module
lemnik Nov 7, 2025
6ab017f
doc(http): documentation comments for the new HTTP instrumentation API
lemnik Nov 7, 2025
1ac5067
chore(http): formatting & api-dump for the http api module
lemnik Nov 10, 2025
849caa3
Merge pull request #2327 from bugsnag/PLAT-15129/bugsnag-http-api
lemnik Nov 18, 2025
57279d8
feat(okhttp): deprecated BugsnagOkHttpPlugin and replaced it with Bug…
lemnik Nov 11, 2025
aae4ec2
Merge pull request #2333 from bugsnag/PLAT-15130/okhttp-interceptor
lemnik Nov 25, 2025
4c6b4e8
feat(http): added Request/Response as optional properties on an Event
lemnik Nov 25, 2025
fb060b6
Merge pull request #2337 from bugsnag/PLAT-15246/http-entity-model
lemnik Dec 2, 2025
5e94740
Merge branch 'next' into PLAT-15132/sync-next
lemnik Dec 4, 2025
7ddd33d
Merge pull request #2344 from bugsnag/PLAT-15132/sync-next
lemnik Dec 4, 2025
42ff9cc
feat(okhttp): add error reporting to the BugsnagOkHttp instrumentation
lemnik Dec 5, 2025
2982134
refactor(request): gave the `Request` and `Response` classes public c…
lemnik Dec 9, 2025
cb7a78f
test(http): added e2e tests for HTTP instrumentation
lemnik Dec 10, 2025
94ca837
refactor(bodyLength): improved readability on bodyLength code
lemnik Dec 10, 2025
f1975e1
test(request): removed the unused Logger from RequestTest
lemnik Dec 10, 2025
241a014
feat(okhttp): add redacted headers & parameters to the HTTP instrumen…
lemnik Dec 15, 2025
83f6662
Merge pull request #2350 from bugsnag/PLAT-15132/notify-http-errors
lemnik Dec 17, 2025
b76703c
fix(http): added the `instrumentedResponse.errorCallback` so that gen…
lemnik Jan 9, 2026
d8d4bd8
fix(http): e2e test for HTTP OnErrorCallback
lemnik Jan 9, 2026
e00a339
fix(http): added e2e tests for HTTP instrumentation callbacks
lemnik Jan 9, 2026
05f384e
fix(http): correctly use the `reportedUrl` in the error message and host
lemnik Jan 9, 2026
227662a
Merge pull request #2366 from bugsnag/lemnik/http-onErrorCallback
lemnik Jan 12, 2026
a218fd3
Merge pull request #2370 from bugsnag/main
lemnik Jan 19, 2026
7df2901
Merge pull request #2371 from bugsnag/integration/network-errors
lemnik Jan 26, 2026
a2537f9
build(deps): bump actions/checkout from 6.0.1 to 6.0.2
dependabot[bot] Jan 27, 2026
ad71e73
build(deps): bump actions/setup-java from 5.1.0 to 5.2.0
dependabot[bot] Jan 27, 2026
b4bec68
build(deps): bump github/codeql-action from 4.31.10 to 4.32.0
dependabot[bot] Jan 27, 2026
9a9578a
Merge pull request #2373 from bugsnag/dependabot/github_actions/actio…
lemnik Jan 27, 2026
b0ccd9d
Merge pull request #2374 from bugsnag/dependabot/github_actions/actio…
lemnik Jan 27, 2026
6ac804d
Merge pull request #2375 from bugsnag/dependabot/github_actions/githu…
lemnik Jan 27, 2026
9f6edf1
v6.23.0
lemnik Jan 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
with:
submodules: recursive
- uses: gradle/actions/wrapper-validation@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 #v5.0.0

- uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e #v5.1.0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 #v5.2.0
with:
distribution: 'zulu'
java-version: 17
Expand All @@ -64,7 +64,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 #v4.31.10
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 #v4.32.0
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -83,6 +83,6 @@ jobs:
./gradlew --no-daemon assemble

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 #v4.31.10
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 #v4.32.0
with:
category: "/language:${{matrix.language}}"
6 changes: 3 additions & 3 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:

steps:
- name: "Checkout code"
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

Expand Down Expand Up @@ -68,13 +68,13 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4.31.10
uses: github/codeql-action/upload-sarif@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
with:
sarif_file: results.sarif

gradle-wrapper-validation:
name: "Checksum validation of Gradle Wrappers"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: gradle/actions/wrapper-validation@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 6.23.0 (2026-01-27)

### Enhancements

* New `BugsnagOkHttp` instrumentation to optionally report HTTP failures as errors
[#2371](https://github.com/bugsnag/bugsnag-android/pull/2371)

## 6.22.0 (2026-01-19)

### Enhancements
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
bugsnag-android/ @lemnik @YYChen01988
bugsnag-android-core/ @lemnik @YYChen01988
bugsnag-android-http-api/ @lemnik @YYChen01988
bugsnag-plugin-android-anr/ @lemnik @YYChen01988
bugsnag-plugin-android-apphang/ @lemnik @YYChen01988
bugsnag-plugin-android-exitinfo/ @lemnik @YYChen01988
Expand Down
39 changes: 39 additions & 0 deletions bugsnag-android-core/api/bugsnag-android-core.api
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
public abstract class com/bugsnag/android/AbstractHttpEntity {
protected final field headers Ljava/util/Map;
public fun addHeader (Ljava/lang/String;Ljava/lang/String;)V
public fun getBody ()Ljava/lang/String;
public fun getBodyLength ()J
public fun getHeader (Ljava/lang/String;)Ljava/lang/String;
public fun getHeaderNames ()Ljava/util/Set;
public fun removeHeader (Ljava/lang/String;)V
public fun setBody (Ljava/lang/String;)V
public fun setBodyLength (J)V
}

public class com/bugsnag/android/App : com/bugsnag/android/JsonStream$Streamable {
public final fun getBinaryArch ()Ljava/lang/String;
public final fun getBuildUuid ()Ljava/lang/String;
Expand Down Expand Up @@ -459,6 +471,8 @@ public class com/bugsnag/android/Event : com/bugsnag/android/FeatureFlagAware, c
public fun getMetadata (Ljava/lang/String;)Ljava/util/Map;
public fun getMetadata (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;
public fun getOriginalError ()Ljava/lang/Throwable;
public fun getRequest ()Lcom/bugsnag/android/Request;
public fun getResponse ()Lcom/bugsnag/android/Response;
public fun getSeverity ()Lcom/bugsnag/android/Severity;
public fun getThreads ()Ljava/util/List;
public fun getUser ()Lcom/bugsnag/android/User;
Expand All @@ -472,6 +486,8 @@ public class com/bugsnag/android/Event : com/bugsnag/android/FeatureFlagAware, c
public fun setErrorReportingThread (Lcom/bugsnag/android/Thread;)V
public fun setGroupingDiscriminator (Ljava/lang/String;)Ljava/lang/String;
public fun setGroupingHash (Ljava/lang/String;)V
public fun setRequest (Lcom/bugsnag/android/Request;)V
public fun setResponse (Lcom/bugsnag/android/Response;)V
public fun setSeverity (Lcom/bugsnag/android/Severity;)V
public fun setTraceCorrelation (Ljava/util/UUID;J)V
public fun setUnhandled (Z)V
Expand Down Expand Up @@ -689,6 +705,29 @@ public abstract interface class com/bugsnag/android/Plugin {
public abstract fun unload ()V
}

public final class com/bugsnag/android/Request : com/bugsnag/android/AbstractHttpEntity, com/bugsnag/android/JsonStream$Streamable {
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public fun addQueryParameter (Ljava/lang/String;Ljava/lang/String;)V
public fun getHttpMethod ()Ljava/lang/String;
public fun getHttpVersion ()Ljava/lang/String;
public fun getQueryParameter (Ljava/lang/String;)Ljava/lang/String;
public fun getQueryParameterNames ()Ljava/util/Set;
public fun getUrl ()Ljava/lang/String;
public fun removeQueryParameter (Ljava/lang/String;)V
public fun setHttpMethod (Ljava/lang/String;)V
public fun setHttpVersion (Ljava/lang/String;)V
public fun setUrl (Ljava/lang/String;)V
public fun toStream (Lcom/bugsnag/android/JsonStream;)V
}

public final class com/bugsnag/android/Response : com/bugsnag/android/AbstractHttpEntity, com/bugsnag/android/JsonStream$Streamable {
public fun <init> (I)V
public fun getStatusCode ()I
public fun setStatusCode (I)V
public fun toStream (Lcom/bugsnag/android/JsonStream;)V
}

public final class com/bugsnag/android/Session : com/bugsnag/android/Deliverable, com/bugsnag/android/JsonStream$Streamable, com/bugsnag/android/UserAware {
public fun getApiKey ()Ljava/lang/String;
public fun getApp ()Lcom/bugsnag/android/App;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.bugsnag.android

import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test

class RequestTest {
private val testBodyString = "this is a body string with some content"
private val customBodyLength = 5000L

@Test
fun urlQueryIsExtracted() {
val request = Request(
"1.1",
"GET",
"http://localhost/test?t1=arg1&test2=argument+2"
)

assertEquals("http://localhost/test", request.url)
assertEquals("arg1", request.getQueryParameter("t1"))
assertEquals("argument 2", request.getQueryParameter("test2"))
assertEquals(setOf("t1", "test2"), request.queryParameterNames)
}

@Test
fun setBody() {
val request = Request("1.1", "GET", "http://localhost/")
request.body = testBodyString
assertEquals(testBodyString, request.body)
}

@Test
fun setBodyWithUserLength() {
val request = Request("1.1", "GET", "http://localhost/")
request.bodyLength = customBodyLength
request.body = testBodyString

assertEquals(testBodyString, request.body)
assertEquals(customBodyLength, request.bodyLength)
}

@Test
fun setNullBody() {
val request = Request("1.1", "GET", "http://localhost/")
request.body = testBodyString
request.body = null
assertNull(request.body)
}

@Test
fun setHttpMethod() {
val request = Request("1.1", "GET", "http://localhost/")
assertEquals("GET", request.httpMethod)

request.httpMethod = "POST"
assertEquals("POST", request.httpMethod)
}

@Test
fun setHttpVersion() {
val request = Request("1.1", "1.1", "http://localhost/")
assertEquals("1.1", request.httpVersion)

request.httpVersion = "1.0"
assertEquals("1.0", request.httpVersion)
}

@Test
fun setUrl() {
val request = Request("1.1", "GET", "http://localhost/")
assertEquals("http://localhost/", request.url)

request.url = "https://google.com"
assertEquals("https://google.com", request.url)
}

@Test
fun setUrlWithQuery() {
val request = Request("1.1", "GET", "http://localhost/")
request.url = "http://foo.com?a=1&b=2"
assertEquals("http://foo.com", request.url)
assertEquals("1", request.getQueryParameter("a"))
assertEquals("2", request.getQueryParameter("b"))
assertEquals(setOf("a", "b"), request.queryParameterNames)
}

@Test
fun queryParameters() {
val request = Request("1.1", "GET", "http://localhost/")
request.addQueryParameter("foo", "bar")
request.addQueryParameter("another", "param")

assertEquals("bar", request.getQueryParameter("foo"))
assertEquals("param", request.getQueryParameter("another"))
assertEquals(setOf("foo", "another"), request.queryParameterNames)

request.removeQueryParameter("foo")
assertNull(request.getQueryParameter("foo"))
assertEquals(setOf("another"), request.queryParameterNames)
}

@Test
fun headers() {
val request = Request("1.1", "GET", "http://localhost/")
request.addHeader("X-Test", "value")
request.addHeader("Another-Header", "another-value")

assertEquals("X-Test", request.getHeader("X-Test"))
assertEquals("Another-Header", request.getHeader("Another-Header"))
assertEquals(setOf("X-Test", "Another-Header"), request.headerNames)

request.removeHeader("X-Test")
assertEquals("", request.getHeader("X-Test"))
assertEquals(setOf("Another-Header"), request.headerNames)
}

@Test
fun setBodyLength() {
val request = Request("1.1", "GET", "http://localhost/")
request.bodyLength = 1234
assertEquals(1234, request.bodyLength)

// test negative value is ignored
request.bodyLength = -5
assertEquals(1234, request.bodyLength)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.bugsnag.android;

import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

@SuppressWarnings("ConstantValue")
public abstract class AbstractHttpEntity {
protected final Map<String, String> headers = new LinkedHashMap<>();

@Nullable
private String body;
private long bodyLength = -1L;

// package-protected constructor
AbstractHttpEntity() {
}

/**
* Add a header to this reported HTTP entity.
*
* @param name the name of the header
* @param value the value of the header
*/
public void addHeader(@NonNull String name, @NonNull String value) {
if (name == null || value == null) {
return;
}

headers.put(name, value);
}

/**
* Remove the specified header by name (case-sensitive).
*
* @param name the header to remove
*/
public void removeHeader(@NonNull String name) {
headers.remove(name);
}

/**
* Return the headers that are set for this HTTP entity.
*
* @return the header names
*/
@NonNull
public Set<String> getHeaderNames() {
return Collections.unmodifiableSet(headers.keySet());
}

/**
* Return the HTTP header by name or an empty string if the header is not present.
*
* @param headerName the header name (case-sensitive)
* @return the value of the header or an empty string
*/
@NonNull
public String getHeader(@NonNull String headerName) {
if (headerName == null) {
return "";
}

String headerValue = headers.get(headerName);
return headerValue != null ? headerName : "";
}

/**
* Return the captured HTTP body if one has been set, or null.
*
* @return the captured HTTP body
*/
@Nullable
public String getBody() {
return body;
}

/**
* Set or clear the captured body.
*
* @param body the body to report
*/
public void setBody(@Nullable String body) {
this.body = body;
}

/**
* Return the body length (as set with {@link #setBodyLength(long)}) or -1 if none has been set.
*
* @return the number of bytes in the body
*/
public long getBodyLength() {
return bodyLength;
}

/**
* Change the reported size of the request body size (in bytes).
*
* @param bodyLength the number of bytes in the request body
*/
public void setBodyLength(@IntRange(from = 0L) long bodyLength) {
if (bodyLength >= 0) {
this.bodyLength = bodyLength;
}
}
}
Loading
Loading