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 an android.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.jar

2 - 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.ProcessResourceProvider

Use 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-attributes key still works but is deprecated and logs a warning on 2.31+.

  • OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS=…ProcessResourceProvider stops the agent attaching process.command_args to every event. Left on, that tag carries your full JVM command line, so any secret passed as a -D flag 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 of service.* or MDC. Note that OTEL_EXPERIMENTAL_RESOURCE_DISABLED_KEYS does not work on the Java agent; use the provider list.

    BugWatch also redacts process.command_args and process.command_line to [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.jar

Gradle: 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 writeWhere 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 / traceLogs, searchable, not an issue
Uncaught exception through Spring MVCIssue, 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_ENDPOINT is the base URL. The agent appends /v1/logs, /v1/traces and /v1/metrics itself.
  • 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=true to see what the agent exports.