Skip to content

Commit d02dd2f

Browse files
authored
[Feat] [SDK-399] Okhttp interceptor (#367)
* feat(okhttp): add telemetry interceptor * build(okhttp): update dependencies * chore(okhttp): add readme * chore(okhttp): fix lint * fix(okhttp): isolate NetworkTelemetryRecorder failures in interceptor * build(okhttp): remove hardcoded version to inherit from root * build(okhttp): remove redundant plugins and repositories blocks * fix(okhttp): strip query params from recorded URLs by default to prevent sensitive data leakage * fix(okhttp): strip query params from recorded URLs by default to prevent sensitive data leakage * fix(okhttp): lint error, decrease line length * fix(okhttp): attribute sanitizer exceptions to urlSanitizer in logs * fix(okhttp): strip credentials and fragment from URLs in default sanitizer * fix(okhttp): replace JUL logger with SLF4J to match SDK conventions * build(okhttp): remove redundant mockito-core declaration * docs(okhttp): add rollbar-java to installation snippet * fix(okhttp): lint line length * style(okhttp): make test class public and use 2-space indentation * style: add missing colon prefix to rollbar-okhttp in settings.gradle.kts * docs(okhttp): update sanitizer docs to list all stripped URL components * fix(okhttp): replace java.util.function.Function with custom UrlSanitizer interface for API 21 compatibility * fix(okhttp): remove incorrect group override so module publishes as com.rollbar:rollbar-okhttp
1 parent 62eccb8 commit d02dd2f

7 files changed

Lines changed: 488 additions & 0 deletions

File tree

rollbar-okhttp/README.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Rollbar OkHttp Integration
2+
3+
This module provides an [OkHttp Interceptor](https://square.github.io/okhttp/features/interceptors/) that automatically captures network telemetry for the Rollbar Java SDK.
4+
5+
It records:
6+
7+
- **Network telemetry events** for HTTP responses with status code `>= 400` (client and server errors).
8+
- **Error events** for connection failures, timeouts, and other I/O exceptions.
9+
10+
## Installation
11+
12+
### Gradle (Kotlin DSL)
13+
14+
```kotlin
15+
dependencies {
16+
implementation("com.rollbar:rollbar-java:<version>")
17+
implementation("com.rollbar:rollbar-okhttp:<version>")
18+
implementation("com.squareup.okhttp3:okhttp:<okhttp-version>")
19+
}
20+
```
21+
22+
### Gradle (Groovy)
23+
24+
```groovy
25+
dependencies {
26+
implementation 'com.rollbar:rollbar-java:<version>'
27+
implementation 'com.rollbar:rollbar-okhttp:<version>'
28+
implementation 'com.squareup.okhttp3:okhttp:<okhttp-version>'
29+
}
30+
```
31+
32+
## Usage
33+
34+
### 1. Implement `NetworkTelemetryRecorder`
35+
36+
```java
37+
NetworkTelemetryRecorder recorder = new NetworkTelemetryRecorder() {
38+
@Override
39+
public void recordNetworkEvent(Level level, String method, String url, String statusCode) {
40+
// url has userinfo, query parameters, and fragment stripped by default
41+
// (see Security section below)
42+
rollbar.recordNetworkEventFor(level, method, url, statusCode);
43+
}
44+
45+
@Override
46+
public void recordErrorEvent(Exception exception) {
47+
rollbar.log(exception);
48+
}
49+
};
50+
```
51+
52+
### 2. Add the interceptor to your OkHttpClient
53+
54+
```java
55+
OkHttpClient client = new OkHttpClient.Builder()
56+
.addInterceptor(new RollbarOkHttpInterceptor(recorder))
57+
.build();
58+
```
59+
60+
### 3. Make requests as usual
61+
62+
```java
63+
Request request = new Request.Builder()
64+
.url("https://api.example.com/data")
65+
.build();
66+
67+
Response response = client.newCall(request).execute();
68+
```
69+
70+
The interceptor will automatically record telemetry events to Rollbar without interfering with the request/response flow.
71+
72+
## Behavior
73+
74+
| Scenario | Action |
75+
|-----------------------------------|---------------------------------------------------------|
76+
| Recorder is `null` | No telemetry or log is recorded |
77+
| Response status `< 400` | No telemetry recorded, response returned normally |
78+
| Response status `>= 400` | Records a network telemetry event with `Level.CRITICAL` |
79+
| Connection failure / timeout | Records an error event, then rethrows the `IOException` |
80+
81+
## Security
82+
83+
URLs can carry sensitive data in several components. To prevent accidental leakage to Rollbar, the interceptor **strips userinfo (basic-auth credentials), query parameters, and the fragment by default** before passing the URL to `NetworkTelemetryRecorder`.
84+
85+
For example, a request to `https://user:secret@api.example.com/charge?token=sk_live_secret#section` will be recorded as `https://api.example.com/charge`.
86+
87+
If your URLs do not contain sensitive query parameters and you need them for debugging, you can opt in to the full URL by supplying a custom sanitizer:
88+
89+
```java
90+
OkHttpClient client = new OkHttpClient.Builder()
91+
.addInterceptor(new RollbarOkHttpInterceptor(recorder, HttpUrl::toString))
92+
.build();
93+
```
94+
95+
When using a custom sanitizer, you are responsible for ensuring that sensitive query parameters are removed before the URL reaches Rollbar.

rollbar-okhttp/build.gradle.kts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
dependencies {
2+
testImplementation(platform("org.junit:junit-bom:5.14.3"))
3+
testImplementation("org.junit.jupiter:junit-jupiter")
4+
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
5+
testImplementation("com.squareup.okhttp3:mockwebserver:5.3.2")
6+
implementation("com.squareup.okhttp3:okhttp:5.3.2")
7+
api(project(":rollbar-api"))
8+
api("org.slf4j:slf4j-api:1.7.25")
9+
}
10+
11+
tasks.test {
12+
useJUnitPlatform()
13+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.rollbar.okhttp;
2+
3+
import com.rollbar.api.payload.data.Level;
4+
5+
/**
6+
* Records network telemetry events and errors for HTTP requests.
7+
*/
8+
public interface NetworkTelemetryRecorder {
9+
/**
10+
* Records a completed network request as a telemetry event.
11+
*
12+
* @param level the severity level to attach to the telemetry event
13+
* @param method the HTTP method (e.g. GET, POST)
14+
* @param url the request URL with userinfo (basic-auth credentials), query parameters,
15+
* and fragment stripped by default; supply a custom sanitizer to
16+
* {@link RollbarOkHttpInterceptor} to change this behavior
17+
* @param statusCode the HTTP response status code as a string (e.g. "200", "404")
18+
*/
19+
void recordNetworkEvent(Level level, String method, String url, String statusCode);
20+
21+
/**
22+
* Records a network error event when an HTTP request fails with an exception.
23+
*
24+
* @param exception the exception thrown during the request
25+
*/
26+
void recordErrorEvent(Exception exception);
27+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.rollbar.okhttp;
2+
3+
import com.rollbar.api.payload.data.Level;
4+
5+
import java.io.IOException;
6+
import java.util.Objects;
7+
8+
import okhttp3.Interceptor;
9+
import okhttp3.Request;
10+
import okhttp3.Response;
11+
12+
import org.jetbrains.annotations.NotNull;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
public class RollbarOkHttpInterceptor implements Interceptor {
17+
18+
private static final Logger LOGGER = LoggerFactory.getLogger(RollbarOkHttpInterceptor.class);
19+
20+
private static final UrlSanitizer DEFAULT_URL_SANITIZER =
21+
url -> url
22+
.newBuilder()
23+
.username("")
24+
.password("")
25+
.query(null)
26+
.fragment(null)
27+
.build()
28+
.toString();
29+
30+
private final NetworkTelemetryRecorder recorder;
31+
private final UrlSanitizer urlSanitizer;
32+
33+
public RollbarOkHttpInterceptor(NetworkTelemetryRecorder recorder) {
34+
this(recorder, DEFAULT_URL_SANITIZER);
35+
}
36+
37+
public RollbarOkHttpInterceptor(
38+
NetworkTelemetryRecorder recorder,
39+
UrlSanitizer urlSanitizer) {
40+
this.recorder = recorder;
41+
this.urlSanitizer = Objects.requireNonNull(urlSanitizer, "urlSanitizer must not be null");
42+
}
43+
44+
@NotNull
45+
@Override
46+
public Response intercept(Chain chain) throws IOException {
47+
Request request = chain.request();
48+
49+
try {
50+
Response response = chain.proceed(request);
51+
52+
if (response.code() >= 400 && recorder != null) {
53+
String sanitizedUrl;
54+
try {
55+
sanitizedUrl = urlSanitizer.sanitize(request.url());
56+
} catch (Exception sanitizerException) {
57+
LOGGER.warn("urlSanitizer threw an exception; "
58+
+ "suppressing to preserve the interceptor contract.", sanitizerException);
59+
return response;
60+
}
61+
try {
62+
recorder.recordNetworkEvent(
63+
Level.CRITICAL,
64+
request.method(),
65+
sanitizedUrl,
66+
String.valueOf(response.code()));
67+
} catch (Exception recorderException) {
68+
LOGGER.warn("NetworkTelemetryRecorder.recordNetworkEvent threw an exception; "
69+
+ "suppressing to preserve the interceptor contract.", recorderException);
70+
}
71+
}
72+
73+
return response;
74+
75+
} catch (IOException e) {
76+
if (recorder != null) {
77+
try {
78+
recorder.recordErrorEvent(e);
79+
} catch (Exception recorderException) {
80+
LOGGER.warn("NetworkTelemetryRecorder.recordErrorEvent threw an exception; "
81+
+ "suppressing to preserve the original IOException.", recorderException);
82+
}
83+
}
84+
85+
throw e;
86+
}
87+
}
88+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.rollbar.okhttp;
2+
3+
import okhttp3.HttpUrl;
4+
5+
@FunctionalInterface
6+
public interface UrlSanitizer {
7+
String sanitize(HttpUrl url);
8+
}

0 commit comments

Comments
 (0)