Java
HTTP is java.net.http and JSON is internal, so the SDK cannot collide with the Jackson version your application pins.
Before you start
- Where your InsightRecorder runs
- The base URL every call goes to —
http://localhost:8080when you are running it locally, your own hostname otherwise. - An API key
- Settings → API keys, scoped to
logs:writeandbug: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.
- 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)); - 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()); - 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; - 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 itThen look for the line at /app/logs and the report at /app/bugs.
If nothing shows upNothing 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.
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.