# Install and wire the InsightRecorder PHP SDK

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

---

You are working in a PHP application (most likely Laravel or Symfony). 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/insight-recorder` package is the supported way for a PHP
  application to talk to it.
- Requires **PHP 8.1+** and has **zero runtime dependencies** — HTTP is
  `ext-curl`, JSON is `ext-json` — so it cannot conflict with the Guzzle
  version this project 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.
- **PHP has no background threads.** The SDK buffers in memory and flushes at
  the end of the request, after `fastcgi_finish_request()` on PHP-FPM, so the
  user never waits. Do not try to make it asynchronous some other way.

- **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. `composer require insightrecorder/insight-recorder`.

2. Read configuration from the environment, never hard-code it:
   `INSIGHT_BASE_URL` and `INSIGHT_API_KEY`. Wire them through whatever this
   project already uses — a Laravel `config/` file reading `env()`, Symfony
   parameters, or plain `getenv()`. If either is empty, the application must
   boot normally with shipping disabled: telemetry is never a boot dependency.

3. Register the client and shipper as **singletons** in the container, where
   this project registers its services (a Laravel `ServiceProvider`, Symfony
   `services.yaml`):

   ```php
   $client = new Client(config('insight.base_url'), config('insight.api_key'));
   $shipper = $client->shipper();
   ```

   One shipper per request; do not construct one per log line.

4. Bridge logging through Monolog — which is how both Laravel and Symfony log —
   adding the handler **alongside** the existing ones, never replacing them:

   ```php
   $logger->pushHandler(new InsightHandler($shipper));
   ```

   In Laravel, add it as a channel in `config/logging.php` and include that
   channel in the `stack`. In Symfony, register it as a Monolog handler in
   `config/packages/monolog.yaml`.

5. Report unhandled exceptions with `ErrorReporter`:

   ```php
   $reporter = new ErrorReporter($client, shipper: $shipper, service: '<service name>');
   ```

   Wire it where this project already handles exceptions — Laravel's
   `withExceptions(...)` in `bootstrap/app.php` (or `Handler::report()` on
   Laravel 10 and earlier), or a Symfony `kernel.exception` listener. Report and
   let the exception continue, so the framework's error page still renders.

6. Flushing is automatic at the end of the request. If this project runs
   **Octane, RoadRunner, Swoole, or queue workers**, call `$shipper->flush()`
   at the end of each request or job as well — those processes outlive a single
   request, so the shutdown hook fires far too late.

**Rules — do not violate these:**

- Do **not** write a Monolog handler that makes an HTTP call per log record.
  The SDK batches, caps each request at `batchSize`, retries with backoff, and
  respects the server's quota (429 + `Retry-After`).
- Do **not** call `flush()` inside the request path for every line — that
  defeats the buffering and puts a network round trip on the user's latency.
- 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 test command passes (`vendor/bin/phpunit`, `php artisan test`).
2. With both env vars set, exercise a route and confirm the log lines appear in
   InsightRecorder at `/app/logs`.
3. Throw a deliberate exception and confirm a bug appears at `/app/bugs`.
4. Confirm the app still boots with both variables **unset**.
5. Report what you changed, and state explicitly where the shipper is flushed.

If anything is ambiguous — which framework version, how logging is configured,
whether the app runs under Octane — inspect the repository and follow what is
already there. Do not restructure the application to fit the SDK.
