# 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.
