-
Notifications
You must be signed in to change notification settings - Fork 75
[Feat] [SDK-399] Add java agent for network telemetry events #374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 26 commits
5762538
70573e5
3ee326d
e5226c1
8ab880c
ad0b2ac
64e7df9
212ce90
fe91c9a
866b20b
6791575
ba9f0d8
02fe650
ebf0c39
5259919
d9237e4
9510ed8
439a3d2
9b106d0
ab969f1
efd4c20
b46aa7b
7aadaaa
97c3ac3
ac57d62
af9cdee
69fc32a
cbab9d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| # Rollbar Java Agent | ||
|
|
||
| A zero-code-change Java instrumentation agent that automatically captures HTTP network errors (4xx and 5xx responses) as Rollbar telemetry events. | ||
|
|
||
| It works by attaching to the JVM at startup via `-javaagent:` and using ByteBuddy to intercept HTTP calls across all major clients — no library dependencies or code changes are needed in your application. | ||
|
|
||
| ## Instrumented HTTP clients | ||
|
|
||
| | Client | Condition | | ||
| |--------|-----------| | ||
| | `java.net.HttpURLConnection` | Always (JDK built-in) | | ||
| | `java.net.http.HttpClient` — `send()` and `sendAsync()` | Java 11+ only | | ||
| | Apache HttpClient 4.x (`org.apache.http`) | If present on classpath | | ||
| | Apache HttpClient 5.x (`org.apache.hc.client5`) | If present on classpath | | ||
|
|
||
| Only 4xx and 5xx responses are recorded. Successful requests (< 400) produce no telemetry. | ||
|
|
||
| > **Apache HC4/HC5 note:** `execute(request, responseHandler)` overloads route through a separate internal call chain and are not instrumented. Use the standard `execute(request)` / `execute(request, context)` overloads to get telemetry. | ||
|
|
||
| ## Requirements | ||
|
|
||
| - Java 11 or higher | ||
| - `rollbar-java` on the application classpath (for `Rollbar.init(...)`) | ||
|
|
||
| ## Installation | ||
|
|
||
| ### 1. Build the agent JAR | ||
|
|
||
| ```bash | ||
| ./gradlew :rollbar-java-agent:shadowJar | ||
| ``` | ||
|
|
||
| The fat JAR (with ByteBuddy bundled and relocated) is written to: | ||
|
|
||
| ``` | ||
| rollbar-java-agent/build/libs/rollbar-java-agent-<version>.jar | ||
| ``` | ||
|
|
||
| ### 2. Add the agent JVM flag | ||
|
|
||
| Add `-javaagent:` to your JVM startup arguments, pointing at the JAR built above: | ||
|
|
||
| ``` | ||
| -javaagent:/path/to/rollbar-java-agent-<version>.jar | ||
| ``` | ||
|
|
||
| **Gradle:** | ||
| ```kotlin | ||
| jvmArgs("-javaagent:/path/to/rollbar-java-agent-<version>.jar") | ||
| ``` | ||
|
|
||
| **Maven Surefire / Failsafe:** | ||
| ```xml | ||
| <argLine>-javaagent:/path/to/rollbar-java-agent-<version>.jar</argLine> | ||
| ``` | ||
|
|
||
| **Docker / environment variable:** | ||
| ```bash | ||
| JAVA_TOOL_OPTIONS="-javaagent:/path/to/rollbar-java-agent-<version>.jar" | ||
| ``` | ||
|
|
||
| ### 3. Also add the JAR to your classpath | ||
|
|
||
| The agent JAR must also be available on the regular application classpath so that your code can call `RollbarAgent.getTelemetryTracker()`: | ||
|
|
||
| **Gradle:** | ||
| ```kotlin | ||
| dependencies { | ||
| implementation(files("/path/to/rollbar-java-agent-<version>.jar")) | ||
| } | ||
| ``` | ||
|
|
||
| **Maven:** | ||
| ```xml | ||
| <dependency> | ||
| <groupId>com.rollbar</groupId> | ||
| <artifactId>rollbar-java-agent</artifactId> | ||
| <version>${rollbar.version}</version> | ||
| </dependency> | ||
| ``` | ||
|
|
||
| ### 4. Wire into your Rollbar configuration | ||
|
|
||
| ```java | ||
| import com.rollbar.agent.RollbarAgent; | ||
| import com.rollbar.notifier.Rollbar; | ||
|
|
||
| import static com.rollbar.notifier.config.ConfigBuilder.withAccessToken; | ||
|
|
||
| Rollbar rollbar = Rollbar.init( | ||
| withAccessToken("your-access-token") | ||
| .environment("production") | ||
| .telemetryEventTracker(RollbarAgent.getTelemetryTracker()) | ||
| .build() | ||
| ); | ||
| ``` | ||
|
|
||
| That's it. All HTTP calls your application makes from that point on will automatically produce telemetry events in the Rollbar error report for any 4xx or 5xx response. | ||
|
|
||
| ## Behavior | ||
|
|
||
| | Scenario | Action | | ||
| |----------|--------| | ||
| | Response status `< 400` | No telemetry recorded | | ||
| | Response status `>= 400` | Records a network telemetry event with `Level.CRITICAL` | | ||
| | Connection failure / I/O error | Records an error telemetry event | | ||
| | No Rollbar config wired | Events accumulate in the agent store (capacity 100); nothing is sent | | ||
|
|
||
| ## Security | ||
|
|
||
| URLs can carry sensitive data in query parameters or basic-auth credentials. The agent **strips userinfo, query parameters, and the URL fragment** before recording. | ||
|
|
||
| For example, a request to: | ||
| ``` | ||
| https://user:secret@api.example.com/charge?token=sk_live_abc#section | ||
| ``` | ||
| is recorded as: | ||
| ``` | ||
| https://api.example.com/charge | ||
| ``` | ||
|
|
||
| ## Internal API | ||
|
|
||
| `AgentTelemetryStore.initForTesting()` is intended for use in tests only (it replaces the internal tracker instance). Do not call it in production code — use `RollbarAgent.getTelemetryTracker()` as shown above. | ||
|
|
||
| ## Testing | ||
|
|
||
| ### Automated tests | ||
|
|
||
| ```bash | ||
| ./gradlew :rollbar-java-agent:test | ||
| ``` | ||
|
|
||
| This runs the full test suite (WireMock-backed integration tests for each instrumented client). | ||
|
|
||
| ### Manual smoke test | ||
|
|
||
| 1. Build the agent JAR: | ||
| ```bash | ||
| ./gradlew :rollbar-java-agent:shadowJar | ||
| ``` | ||
|
|
||
| 2. Write a small program that triggers a 4xx or 5xx: | ||
| ```java | ||
| import com.rollbar.agent.RollbarAgent; | ||
| import com.rollbar.notifier.Rollbar; | ||
|
|
||
| import java.net.HttpURLConnection; | ||
| import java.net.URL; | ||
|
|
||
| import static com.rollbar.notifier.config.ConfigBuilder.withAccessToken; | ||
|
|
||
| public class SmokeTest { | ||
| public static void main(String[] args) throws Exception { | ||
| Rollbar rollbar = Rollbar.init( | ||
| withAccessToken("your-access-token") | ||
| .environment("test") | ||
| .telemetryEventTracker(RollbarAgent.getTelemetryTracker()) | ||
| .build() | ||
| ); | ||
|
|
||
| // Trigger a 404 — captured as a telemetry event on the next error report | ||
| HttpURLConnection conn = (HttpURLConnection) new URL("https://httpstat.us/404").openConnection(); | ||
| int code = conn.getResponseCode(); | ||
| conn.disconnect(); | ||
|
|
||
| System.out.println("Response: " + code); | ||
|
|
||
| // Send an error to Rollbar — the 404 telemetry event will appear alongside it | ||
| rollbar.error(new RuntimeException("smoke test error")); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| 3. Run with the agent: | ||
| ```bash | ||
| java -javaagent:rollbar-java-agent/build/libs/rollbar-java-agent-<version>.jar \ | ||
| -cp "rollbar-java-agent/build/libs/rollbar-java-agent-<version>.jar:your-app.jar" \ | ||
| SmokeTest | ||
| ``` | ||
|
|
||
| 4. Check your Rollbar dashboard — the error report for "smoke test error" should show a **Network** telemetry event for the 404 in the telemetry timeline. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| plugins { | ||
| `java-library` | ||
| id("com.github.johnrengelman.shadow") version "8.1.1" | ||
| } | ||
|
|
||
| dependencies { | ||
| implementation("net.bytebuddy:byte-buddy:1.14.18") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Support the advertised JVM range Byte Buddy 1.14.18 supports class files only through Java 23 without experimental mode; Java 24 support starts at 1.15.4 and Java 25+ at 1.17.0. The README promises Java 11 or higher, but CI exercises only 11 and 17, so the agent can silently fail to transform HTTP classes on current Java 24/25/26 runtimes. Please upgrade Byte Buddy and add current-LTS/current-JDK coverage, or explicitly narrow the supported range. Compatibility table: https://github.com/raphw/byte-buddy#java-version-compatibility |
||
| implementation("net.bytebuddy:byte-buddy-agent:1.14.18") | ||
| api(project(":rollbar-api")) | ||
| implementation(project(":rollbar-java")) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Keep Rollbar SDK classes out of the shaded agent
Shadow's default dependency behavior: https://gradleup.com/shadow/configuration/dependencies/ |
||
| compileOnly("org.apache.httpcomponents:httpclient:4.5.14") | ||
| compileOnly("org.apache.httpcomponents.client5:httpclient5:5.3.1") | ||
|
|
||
| testImplementation(platform("org.junit:junit-bom:5.14.3")) | ||
| testImplementation("org.junit.jupiter:junit-jupiter") | ||
| testRuntimeOnly("org.junit.platform:junit-platform-launcher") | ||
| testImplementation("org.mockito:mockito-core:5.11.0") | ||
| testImplementation("org.wiremock:wiremock:3.13.2") | ||
| testImplementation("org.apache.httpcomponents:httpclient:4.5.14") | ||
| testImplementation("org.apache.httpcomponents.client5:httpclient5:5.3.1") | ||
| } | ||
|
|
||
| tasks.jar { | ||
| enabled = false | ||
| } | ||
|
|
||
| // java-library wires tasks.jar into apiElements/runtimeElements; replace it with shadowJar so | ||
| // Gradle's variant system and vanniktech publishing both see the fat jar as the primary artifact. | ||
| listOf(configurations.apiElements, configurations.runtimeElements).forEach { cfg -> | ||
| cfg.configure { | ||
| outgoing.artifacts.clear() | ||
| outgoing.artifact(tasks.shadowJar) | ||
| } | ||
| } | ||
|
|
||
| tasks.shadowJar { | ||
| archiveClassifier.set("") | ||
| manifest { | ||
| attributes( | ||
| "Premain-Class" to "com.rollbar.agent.RollbarAgent", | ||
| "Agent-Class" to "com.rollbar.agent.RollbarAgent", | ||
| "Can-Redefine-Classes" to "true", | ||
| "Can-Retransform-Classes" to "true" | ||
| ) | ||
| } | ||
| relocate("net.bytebuddy", "com.rollbar.agent.shaded.bytebuddy") | ||
| mergeServiceFiles() | ||
| } | ||
|
|
||
| // Override root's Java 8 compatibility — this agent targets Java 11+ to support | ||
| // java.net.http.HttpClient instrumentation. | ||
| tasks.withType<JavaCompile>().configureEach { | ||
| options.release.set(11) | ||
| } | ||
|
|
||
| tasks.test { | ||
| useJUnitPlatform() | ||
| val agentJar = tasks.shadowJar.get().archiveFile.get().asFile | ||
| // Load as Java agent (instruments HTTP classes on startup) | ||
| jvmArgs("-javaagent:$agentJar") | ||
| // Also put on test classpath — the TCCL reflection bridge finds agent classes via the | ||
| // system classloader; mirrors production use where rollbar-java-agent is a Gradle/Maven dep | ||
| classpath += files(agentJar) | ||
| dependsOn(tasks.shadowJar) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.rollbar.agent; | ||
|
|
||
| import com.rollbar.api.payload.data.TelemetryEvent; | ||
| import com.rollbar.notifier.provider.Provider; | ||
| import com.rollbar.notifier.telemetry.RollbarTelemetryEventTracker; | ||
| import com.rollbar.notifier.telemetry.TelemetryEventTracker; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public final class AgentTelemetryStore { | ||
|
|
||
| private static volatile TelemetryEventTracker INSTANCE = | ||
| new RollbarTelemetryEventTracker(System::currentTimeMillis, 100); | ||
|
|
||
| private AgentTelemetryStore() {} | ||
|
|
||
| public static TelemetryEventTracker getInstance() { | ||
| return INSTANCE; | ||
| } | ||
|
|
||
| public static List<TelemetryEvent> getAll() { | ||
| return INSTANCE.getAll(); | ||
| } | ||
|
|
||
| public static void initForTesting(Provider<Long> timestampProvider) { | ||
| INSTANCE = new RollbarTelemetryEventTracker(timestampProvider, 100); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Align the implementation with the zero-code-change claim
The instrumentation removes changes at HTTP call sites, but installation still requires adding the agent as an application dependency and changing
Rollbar.init(...)to installRollbarAgent.getTelemetryTracker()(steps 3–4). The behavior table also says that without this wiring events accumulate but are not sent. If SDK-399 requires genuinely zero application-code changes, the agent needs to integrate its tracker automatically; otherwise this claim should be narrowed to "no HTTP client call-site changes" and the stated problem/acceptance criteria updated.