diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 919b5a557..d3202921c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,7 +15,7 @@ body: id: version attributes: label: HealthLog version - description: Output of `/api/version` or the tag of the GHCR image you're running. + description: Settings, About shows it, as does `/api/version`, or the tag of the GHCR image you're running. placeholder: "1.4.19" validations: required: true @@ -55,11 +55,27 @@ body: 3. See error validations: required: true + - type: input + id: error-id + attributes: + label: Error ID + description: If the failure showed an error ID (eight characters, e.g. `3f9a2c1b`), paste it here. The operator finds the event with it under Admin, App logs. + placeholder: "3f9a2c1b" + - type: textarea + id: support-info + attributes: + label: Support info / support report + description: | + Everyone: Settings, About, "Copy support info". + Operators: Admin, System status, "Support report", then Copy. + Both are redacted (no names, emails, values or tokens); read them before you paste. + See https://github.com/MBombeck/HealthLog/blob/main/docs/self-hosting/support.md + render: text - type: textarea id: logs attributes: - label: Relevant logs / screenshots - description: Paste any relevant log output (redact secrets) or attach screenshots. Wide-event logs from `/admin/app-logs` are useful. + label: Screenshots / other logs + description: Attach screenshots, or paste other log output if you have it. Redact secrets and personal data first. render: shell - type: input id: browser diff --git a/docs/README.md b/docs/README.md index dfe00df1a..34465adf4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,7 +33,10 @@ notes, and machine-readable specs that ship alongside the source: three access levels, the eight sections a grant can be narrowed to, managed profiles for somebody with no login and the guardians who look after them, what revocation does and does not undo, and which trust - properties of the instance are unchanged by any of it. + properties of the instance are unchanged by any of it. And + [`self-hosting/support.md`](./self-hosting/support.md) — reporting a + problem: error ids, the support info every account can copy, the + operator's redacted support report, and finding an error in the admin log. - [`using/`](./using/) — notes on using the app that sit close to the code. [`using/search.md`](./using/search.md) covers the command palette's record search: opening it, what it finds, how matching works, and its diff --git a/docs/self-hosting/getting-started.md b/docs/self-hosting/getting-started.md index fac39c9b6..aa6ed6ba4 100644 --- a/docs/self-hosting/getting-started.md +++ b/docs/self-hosting/getting-started.md @@ -218,6 +218,11 @@ NEXT_PUBLIC_DASHBOARD_SNAPSHOT=false pnpm build ## Troubleshooting +- **Where to look.** For a bug report, copy the support report (Admin, + System status) and the support info (Settings, About), and note the error + id the failure showed. Admin, App logs finds the event behind an error id. + What each contains, and what it never contains, is in + [`support.md`](./support.md). - **`app` container exits with a Prisma migration error.** The Postgres healthcheck races the migration on slow disks. Run `docker compose restart app` once the database has stabilised — diff --git a/docs/self-hosting/support.md b/docs/self-hosting/support.md new file mode 100644 index 000000000..d9e8af841 --- /dev/null +++ b/docs/self-hosting/support.md @@ -0,0 +1,105 @@ +# Reporting a problem + +HealthLog does not send anything home, and nobody but the operator can read +an instance's logs. So a bug report has to carry what is needed to find the +fault. Two texts do that, and both are redacted before you see them. + +| Who | Where | What it is | +| -------- | ---------------------------------------- | ------------------------------------------ | +| Everyone | Settings, About, **Copy support info** | Your app version, settings and connections | +| Operator | Admin, System status, **Support report** | The whole instance, accounts as numbers | + +Add the **error id** of the failure if you have one, and paste both texts into +the [bug report](https://github.com/MBombeck/HealthLog/issues/new?template=bug_report.yml). +Read them before you post. They are built to contain nothing personal, but +you are the one publishing them. + +## Error ids + +When the server fails (a 5xx answer), the message shows **Error ID** and +eight characters, for example `3f9a2c1b`, with a copy button that copies the +full id. A page that fails to render shows one too when the server has one +to give: the request id of the failed call, or the digest the server printed +beside the stack in its log. + +The id is the request id of the failing call. Every error answer of the API +carries it in `meta.requestId` and in the `x-request-id` header. An incoming +`x-request-id` is only adopted when it consists of letters, digits and hyphens +(8 to 64 characters); anything else is replaced with a fresh id. + +The operator finds the event under **Admin, App logs** by typing the id (or +its first 8 characters) into **Error ID**. A search by id reads the second log +buffer, which keeps the last 1,000 warnings and errors of the process. Ordinary +traffic cannot push them out, so an error stays findable for much longer than +in the main buffer of 500 events. Both buffers live in memory: a restart +empties them, and a split `web` / `worker` deployment has one per process. + +The same search works on the API: + +``` +GET /api/admin/app-logs?requestId=3f9a2c1b&ring=problems +``` + +A 4xx answer (a refused input, a missing permission) shows no id. Its message +says what to change. + +## Support info (everyone) + +**Settings, About, Copy support info** puts together, in your browser: + +- server version and build, and your browser; +- language, time zone and the modules you have switched on; +- the AI state: whether a provider is set up, your usage level, and which AI + features are available and why not; +- for each connected source (Withings, Oura, and so on), its health state, + reason, last success, newest data and the last recorded sync attempts; +- the last five error ids of this browser session. + +It never contains your username, email address, name, server address, +tokens, recorded values or record ids. Nothing is sent anywhere; the text +goes to your clipboard. + +## Support report (operator) + +**Admin, System status, Support report** creates a text with **Copy** and +**Download**. It is created when you press a button, not on page load, and +shown above the buttons so you can read it first. It contains: + +- **Build:** version, build SHA, build time, Node, process type, heap limit, + uptime. +- **Database:** PostgreSQL version, server time zone, size, last applied + migration, unfinished migrations. +- **Configuration**, as yes or no only: secure cookie flag, OIDC, OIDC only, + Loki, GlitchTip, off-host backup, APNs, Web Push, SMTP, GeoLite2, the number + of `NOTIFICATION_PRIVATE_ORIGINS` entries, operator AI key, central Codex, + the AI provider types accounts chose, demo mode. +- **Jobs:** whether the worker runs in this process and when it last + reported, the newest finished job, and every queue with failures in the + last 72 hours with an error class (`timeout`, `TypeError`, `P2002`, + `ECONNREFUSED`, an HTTP status), never the message. +- **Connections:** per source, how many accounts are in each health state and + reason, which accounts are not `ok`, and open watchdog incidents. +- **AI:** the usage level default and ceiling, accounts per usage level, and + the provider ledger per provider type (working, failing, credential + refused, paused). +- **Notifications:** per channel, delivered, failed and skipped in the last 7 + days, with the most frequent failure reasons. +- **Backups:** newest in-database copy, the last weekly run, off-host + freshness, and the last restore drill. +- **Recent warnings and errors:** the 20 most frequent action names in the + warning and error buffer of this process, with count, last seen and error + class. + +Accounts appear as `Account 1`, `Account 2` and so on, numbered by creation. +The report never contains an email address, a username, an account id, a +recorded value, a token, or the host name of another service. As a second +layer, the finished text is scrubbed for the instance's own usernames, +emails, names and ids, for tokens, URLs, email addresses, ids, IP addresses +and host names before it is shown. + +## Logs beyond the report + +`docker compose logs app` prints every wide event as one JSON line; +`LOG_LEVEL=debug` adds debug events. With `LOKI_ENDPOINT` set the same events +go to Loki, and GlitchTip (Admin, General) receives unexpected server errors. +Each line carries `request_id`, so an error id finds its line there too.