Java & Spring Boot
BugWatch has no Java library to install. Java and Kotlin services report through the OpenTelemetry Java agent, which BugWatch receives natively over OTLP: errors, your application logs, distributed traces and JVM metrics, with no code change to start.
Verified against opentelemetry-javaagent 2.31.1 on JDK 17 / Spring Boot 3.3.
Why an agent instead of an SDKlink
The agent already instruments Spring MVC, WebFlux, JDBC, Kafka, Redis, gRPC and your logging framework. A BugWatch-specific Java SDK would re-implement that and give you less. You also keep a vendor-neutral wire format: the same process can be pointed anywhere that speaks OTLP.
The Android SDK (
cloud.newinstance:bugwatch) is not usable here. It is an Android library that needs anandroid.app.Application, so it cannot load in a server JVM. See Mobile → Android for app work.
1 - Download the agentlink
curl -L -o opentelemetry-javaagent.jar \
https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar2 - Point it at BugWatchlink
OTEL_SERVICE_NAME=my-spring-app
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.newinstance.cloud
OTEL_EXPORTER_OTLP_HEADERS=x-api-key=KEYID:secret
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
OTEL_LOGS_EXPORTER=otlp
OTEL_TRACES_EXPORTER=otlp
OTEL_METRICS_EXPORTER=otlp
OTEL_RESOURCE_ATTRIBUTES=deployment.environment=production,service.version=1.4.2
OTEL_INSTRUMENTATION_LOGBACK_APPENDER_EXPERIMENTAL_MDC_ATTRIBUTES_INCLUDED=*
OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS=io.opentelemetry.instrumentation.resources.ProcessResourceProviderUse the project's DSN key (Settings → DSN keys). The key binds every event to one project and one environment, so sk_live_ and sk_test_ keys are not interchangeable. service.version becomes the release on every issue, which is what gives you regression tracking per deploy.
The last two lines matter more than they look:
-
…MDC_ATTRIBUTES_INCLUDED=*sends your MDC as searchable tags. The older…capture-mdc-attributeskey still works but is deprecated and logs a warning on 2.31+. -
OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS=…ProcessResourceProviderstops the agent attachingprocess.command_argsto every event. Left on, that tag carries your full JVM command line, so any secret passed as a-Dflag rides along on every issue, and it eats tag budget (the cap is 50 per event). Disabling the provider dropped a sample issue from 24 tags to 16 with no loss ofservice.*or MDC. Note thatOTEL_EXPERIMENTAL_RESOURCE_DISABLED_KEYSdoes not work on the Java agent; use the provider list.BugWatch also redacts
process.command_argsandprocess.command_lineto[REDACTED]on arrival, for logs, traces and metrics alike, so you are covered even if you skip this line. Prefer setting it anyway: the provider list means the data never leaves your process, which is a stronger guarantee than masking it after transmission, and it keeps the tag slot free. If you have other attributes to mask, add their keys under Project settings → data redaction.
3 - Run with the agentlink
java -javaagent:./opentelemetry-javaagent.jar -jar target/my-spring-app.jarGradle: add it to bootRun jvmArgs. Containers: JAVA_TOOL_OPTIONS=-javaagent:/app/opentelemetry-javaagent.jar, which needs no change to your entrypoint.
4 - Catch unhandled exceptionslink
Spring returns a 500 for an unhandled exception, but what reaches BugWatch depends on whether anything logged the throwable. Make it explicit:
@RestControllerAdvice
public class BugWatchExceptionReporter {
private static final Logger log = LoggerFactory.getLogger(BugWatchExceptionReporter.class);
@ExceptionHandler(Exception.class)
public ResponseEntity<Void> report(Exception ex) {
log.error("Unhandled exception", ex);
return ResponseEntity.internalServerError().build();
}
}Already have a @ControllerAdvice? Just confirm it passes the exception as the second argument to the logger. log.error("Payment failed: " + ex.getMessage()) throws the stack trace away; log.error("Payment failed", ex) keeps it.
5 - Log from your own codelink
This is the whole custom-instrumentation story: there is no BugWatch API to call. Your existing SLF4J logger is the instrumentation. Every line the app logs is already being shipped.
private static final Logger log = LoggerFactory.getLogger(CheckoutService.class);
public void applyCoupon(String code, String orderId) {
MDC.put("order.id", orderId);
MDC.put("customer.tier", tierFor(orderId));
try {
redeem(code);
} catch (IllegalArgumentException ex) {
log.error("Coupon rejected at checkout", ex);
} finally {
MDC.clear();
}
}That caught exception becomes its own issue, grouped as java.lang.IllegalArgumentException, separate from your unhandled 500s, and carries order.id and customer.tier as tags you can filter on. Clear the MDC in a finally: pooled request threads are reused, and stale MDC will mislabel the next request.
What becomes an issue, and what becomes a loglink
| You write | Where it lands |
|---|---|
log.error("msg", ex) | Issue, grouped by exception type + stack trace |
log.error("msg") (no throwable) | Issue, grouped by message, type Log |
log.warn / info / debug / trace | Logs, searchable, not an issue |
| Uncaught exception through Spring MVC | Issue, once something logs the throwable |
SLF4J levels map straight through: TRACE→trace, DEBUG→debug, INFO→info, WARN→warn, ERROR→error. Anything carrying a throwable is raised to error even if logged at a lower level. The issue cutoff is a per-project setting (Project settings → issue level threshold), error by default.
Volume matters. The agent captures everything Logback emits, framework and startup lines included. A trivial Spring Boot app serving five requests produced 42 events, of which 3 were issues. Set your Logback root level deliberately before pointing production at it, and check your plan's event allowance.
Correlation and the rest of the stacklink
Log records carry the active traceId and spanId, so an issue links to the exact request that produced it in Trace Explorer. Traces and JVM metrics (heap, GC, threads, HTTP server timings) flow from the same agent with no extra setup.
Without the agentlink
If you cannot attach a -javaagent, add the OpenTelemetry Logback appender (io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0) and keep the same OTEL_EXPORTER_OTLP_* variables. You get logs and errors, but not the automatic traces, metrics or framework instrumentation.
Troubleshootinglink
- Nothing at all: check the key is this project's DSN key, and that
OTEL_EXPORTER_OTLP_ENDPOINTis the base URL. The agent appends/v1/logs,/v1/tracesand/v1/metricsitself. - Traces but no issues: nothing is logging the throwable. Add the advice in step 4.
- Stack traces look short: make sure you pass the exception as the second logger argument rather than interpolating
ex.getMessage(). - Add
-Dotel.javaagent.debug=trueto see what the agent exports.