SDKs / Java

Java

HTTP is java.net.http and JSON is internal, so the SDK cannot collide with the Jackson version your application pins.

Requires Java 17+Package com.insightrecorder.insightSource on GitHub →

Before you start

Where your InsightRecorder runs
The base URL every call goes to — http://localhost:8080 when you are running it locally, your own hostname otherwise.
An API key
Settings → API keys, scoped to logs:write and bug:write. The token (crk_…) is shown exactly once — it is hashed at rest — and is revocable. Do not use a login JWT: it expires within the hour and is not meant for machines.
  1. 01

    Create the client and shipper

    Close the shipper on shutdown, or the final batch is lost.

    InsightClient client = InsightClient.builder()
        .baseUrl(System.getenv("INSIGHT_BASE_URL"))
        .apiKey(System.getenv("INSIGHT_API_KEY"))
        .build();
    
    Shipper shipper = client.shipper();
    Runtime.getRuntime().addShutdownHook(new Thread(shipper::close));
  2. 02

    Ship a log line

    Bridge your Logback or Log4j2 appender to this call; map the level, the formatted message, and MDC entries.

    shipper.send(LogEntry.of("checkout failed")
        .level("error")
        .traceId(traceId)
        .attr("order_id", "1234")
        .build());
  3. 03

    Report unhandled exceptions

    ErrorReporter is framework-agnostic on purpose — wiring it in is a handful of lines you own, and the SDK never drags in jakarta.servlet or Spring.

    ErrorReporter reporter = ErrorReporter.builder(client)
        .shipper(shipper).service("checkout-api").build();
    
    // in a servlet filter or @ControllerAdvice:
    reporter.report(e, ErrorReporter.Request.of(
            req.getMethod(), req.getRequestURI(), req.getHeader("traceparent")));
    throw e;
  4. 04

    Verify it is working

    Ship one line and close the shipper. Closing is what forces the batch out, so a smoke test that skips it proves nothing.

    shipper.send(LogEntry.of("insightrecorder smoke test")
        .level("error")
        .traceId("smoke-1")
        .build());
    
    shipper.close(); // or let the shutdown hook do it

    Then look for the line at /app/logs and the report at /app/bugs.

    If nothing shows up

    Nothing arrived? Check shipper.dropped() — a rising count means the queue is undersized. Delivery errors are silent unless you set onError. And confirm the shutdown hook actually runs; a kill -9 loses the final batch.

Before you ship

  • For background jobs: executor.submit(reporter.wrap(task)) reports and rethrows.
  • reporter.installUncaughtExceptionHandler() is the JVM-wide catch-all.
  • Interruption is never swallowed: the thread's interrupt flag is restored.
PROMPT Install the Java SDK with an AI agent
Raw .md

Paste this into a coding agent running inside your project, or point the agent at /docs/prompts/java directly. It is self-contained: the agent needs no prior knowledge of InsightRecorder.

# Install and wire the InsightRecorder Java SDK

Paste this into a coding agent (Claude Code, Cursor, …) running inside your
Java service's repository. It is self-contained — the agent needs no prior
knowledge of InsightRecorder.

---

You are working in a Java service. Install and wire the InsightRecorder SDK so it
ships its logs to InsightRecorder and reports unhandled exceptions.

**Context you need:**

- InsightRecorder collects application logs and bug reports. The InsightRecorder Java SDK
  (`com.insightrecorder.insight`) is the supported way for a Java service to talk to it.
- Requires **Java 17+**. It has **zero third-party dependencies** — HTTP is
  `java.net.http`, JSON is internal — so it cannot conflict with the Jackson or
  HTTP-client versions this project already pins.
- Authentication uses a long-lived **API key** shaped like `crk_…`, created in
  the InsightRecorder UI under *Settings → API keys* with the `logs:write` scope (add
  `bug:write` to report errors). **Do not** use a login JWT: those expire after
  an hour and are not meant for machines.

  Add `bug:read` for anything the SDK reads back (`listBugs/getBug`), and `logs:read` if you query logs. A write-only key is the common mistake: shipping works, the first read returns 403.

- **Wire trace correlation if the application uses OpenTelemetry.** A log line
  and the request that wrote it are one investigation, and that join is the
  reason to ship logs here rather than anywhere else — see the "Trace
  correlation" section of the README for what this language needs (several do it
  automatically; the rest take one hook). Without it, every log call has to carry
  `trace_id` by hand, which nobody sustains past the first week.

**Steps:**

1. Add the dependency using whatever build tool this repo uses (Maven `pom.xml`
   or Gradle `build.gradle[.kts]`) — match the existing style and dependency
   ordering. If the artifact is not yet published to a repository the project
   can reach, add the SDK sources under `src/main/java/com/insightrecorder/insight/`
   instead and say so in your summary.

2. Read configuration from the environment, never hard-code it: `INSIGHT_BASE_URL`
   and `INSIGHT_API_KEY`. Wire them through the project's existing configuration
   mechanism (Spring `@ConfigurationProperties`, MicroProfile Config, plain
   `System.getenv`, …). If either is empty, the service must start normally with
   shipping disabled — telemetry is never a startup dependency.

3. Create the client **once** (a singleton: a Spring `@Bean`, a CDI producer, or
   a static holder — follow the project's pattern) and a shipper alongside it:

   ```java
   InsightClient client = InsightClient.builder()
       .baseUrl(baseUrl)
       .apiKey(apiKey)
       .build();

   Shipper shipper = client.shipper();
   ```

4. Bridge the service's existing logging to the shipper. Match what the repo
   uses:
   - **Logback**: add a small `AppenderBase<ILoggingEvent>` whose `append()`
     builds a `LogEntry` and calls `shipper.send(...)`; register it in
     `logback.xml` **alongside** the console appender, never replacing it.
   - **Log4j2**: the equivalent custom `Appender`.
   - **java.util.logging**: a custom `Handler`.

   Map fields as: level name → `.level(...)`, the formatted message → the entry
   message, an MDC/trace id → `.traceId(...)`, and remaining MDC entries →
   `.attr(key, value)`.

5. Report unhandled exceptions with `ErrorReporter`, which builds the whole
   report — stack trace, request, trace id, and the recent log trail:

   ```java
   ErrorReporter reporter = ErrorReporter.builder(client)
       .shipper(shipper)
       .service("<service name>")
       .build();
   ```

   Wire it where this project already handles exceptions — a servlet filter
   registered high in the chain, a Spring `@ControllerAdvice`, or a JAX-RS
   `ExceptionMapper`:

   ```java
   reporter.report(e, ErrorReporter.Request.of(
       req.getMethod(), req.getRequestURI(), req.getHeader("traceparent")));
   throw e; // rethrow, so existing error handling still runs
   ```

   The SDK ships no servlet or Spring dependency on purpose — those six lines
   are the adapter, and the README has the full filter. For background jobs use
   `executor.submit(reporter.wrap(task))`, and add
   `reporter.installUncaughtExceptionHandler()` as a JVM-wide catch-all.

6. Close the shipper on shutdown — a Spring `@PreDestroy`, a
   `Runtime.getRuntime().addShutdownHook(...)`, or try-with-resources for a
   short-lived process. Without it the final batch is lost.

**Rules — do not violate these:**

- Do **not** write an appender that opens an HTTP connection per log event. The
  SDK batches on a daemon thread, caps each request at `batchSize`, retries with
  backoff, and respects the server's quota.
- Do **not** block on `shipper.send(...)` — it is non-blocking on purpose and
  drops when its queue is full.
- Do **not** swallow `InterruptedException` anywhere you touch the SDK; restore
  the interrupt flag.
- Do **not** log the API key, and do not commit it.
- Do **not** capture request bodies, headers, cookies, or query strings in
  reports: they routinely carry credentials and personal data.

**Verify before you finish:**

1. The project's build and test commands pass.
2. Start the service with both env vars set, exercise an endpoint, then confirm
   the log lines appear in InsightRecorder at `/app/logs`.
3. Trigger a deliberate exception and confirm a bug appears at `/app/bugs`.
4. Confirm the service still starts with both variables **unset**.
5. Report what you changed, and state explicitly where `shipper.close()` runs.

If anything is ambiguous — which logging framework, where beans are declared,
how shutdown is handled — inspect the repository and follow what is already
there. Do not restructure the service to fit the SDK.