Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,194 @@
|
||||
# Logging Conventions
|
||||
|
||||
> Owner: Phase-02 Observability. Last update: 2026-05-17 (Task #34).
|
||||
|
||||
This document is the source of truth for **how to log in this codebase**.
|
||||
Phase-02 Task #34 migrated every server-side `console.*` call to Pino
|
||||
and locked the rule in via ESLint. Read this before adding new log
|
||||
statements.
|
||||
|
||||
## TL;DR
|
||||
|
||||
- Use the Pino logger appropriate to your runtime — never `console.*`.
|
||||
- Pino argument order: **object first, message string second**.
|
||||
- Never log raw PII (email, password, token, full prompt body, full
|
||||
webhook payload, Stripe card data).
|
||||
- Errors go in the `err` field so Pino's built-in serializer kicks in.
|
||||
|
||||
## Which logger?
|
||||
|
||||
| Runtime | Logger | Location |
|
||||
|---|---|---|
|
||||
| React Router web (SSR / loaders / actions / `.server.ts`) | `webLogger` (or `createScopedLogger("scope")`) | `apps/web/app/utils/logger.server.ts` |
|
||||
| Hono API service | `apiLogger` | `apps/api/src/logger.ts` |
|
||||
| BullMQ worker | `workerLogger` | `apps/worker/src/logger.ts` |
|
||||
| Package code (`packages/*/src/**`) | `createChildLogger(defaultLogger, { component: "<pkg-name>", scope: "<feature>" })` from `@toolctrl/logger` | `packages/logger/src/index.ts` |
|
||||
|
||||
For larger files / modules, scope your logger:
|
||||
|
||||
```ts
|
||||
// apps/web/app/modules/users/services/AuthService.ts
|
||||
import { createScopedLogger } from "~/utils/logger.server";
|
||||
const authLogger = createScopedLogger("auth");
|
||||
```
|
||||
|
||||
The scope name appears as a `scope: "auth"` field on every record, so
|
||||
Loki/Grafana queries like `{service="web"} | json | scope="auth"` work
|
||||
without having to grep through unrelated messages.
|
||||
|
||||
## Pino argument order
|
||||
|
||||
```ts
|
||||
// ❌ VORHER (console.* style — backwards)
|
||||
console.log("User signed in", { userId, tenantId });
|
||||
console.error("Failed to send email", error);
|
||||
|
||||
// ✅ NACHHER (Pino style — object first, msg second)
|
||||
authLogger.info({ userId, tenantId, op: "user.signin" }, "User signed in");
|
||||
authLogger.error({ err: error, userId, op: "user.signin" }, "Failed to send email");
|
||||
```
|
||||
|
||||
The `err` key is special: Pino's built-in serializer flattens it to
|
||||
`{ type, message, stack }` and auto-redacts `error.config.headers.authorization`.
|
||||
If you log the error under any other key, the serializer does NOT run.
|
||||
|
||||
## Log levels
|
||||
|
||||
| Level | Use for |
|
||||
|---|---|
|
||||
| `fatal` | Process is about to exit / data was lost. Never reached normally. |
|
||||
| `error` | An operation failed and we cannot recover. Send to Sentry. |
|
||||
| `warn` | Unexpected condition, but operation continued (fallback used, missing config). |
|
||||
| `info` | Significant business event (subscription created, user signed in, seed run started). |
|
||||
| `debug` | Verbose diagnostic for development. Hidden in prod by default. |
|
||||
| `trace` | Per-iteration / hot-path debug. Almost never used. |
|
||||
|
||||
Production default level is `info` (env: `LOG_LEVEL=info`). Bump to
|
||||
`debug` per service with `LOG_LEVEL=debug` if you need to capture a
|
||||
specific flow.
|
||||
|
||||
## Forbidden fields
|
||||
|
||||
Never log these — even if the user "doesn't think they matter":
|
||||
|
||||
- Raw `email` — use `emailFingerprint(email)` from the seed/auth utils
|
||||
if you need correlation.
|
||||
- Raw `password`, `passwordHash`, `apiKey`, `secret`, `token`,
|
||||
`sessionToken`, `bearer`.
|
||||
- AI **prompt bodies** — only log `{ promptHash, promptLength, model, tenantId }`.
|
||||
See `apps/web/app/modules/promptBuilder/services/.server/PromptBuilderService.ts`
|
||||
`hashPrompt()` for the SHA-256 helper.
|
||||
- Stripe **webhook bodies** — log only `{ eventType, eventId }`.
|
||||
- ConvertKit / CRM API **response bodies** (contain subscriber email).
|
||||
- Request **headers** containing `authorization`, `cookie`, `set-cookie`.
|
||||
|
||||
`packages/logger/src/index.ts` defines a defensive **redact list** at
|
||||
the Pino instance level — fields named `password`, `token`, `secret`,
|
||||
`apiKey`, `authorization`, `cookie` (plus their `*.<field>` variants)
|
||||
are replaced with `[Redacted]` even if a developer forgets. Do not
|
||||
rely on this as your primary defence — redaction does NOT cascade
|
||||
into nested objects with non-matching key names.
|
||||
|
||||
## Pretty vs JSON
|
||||
|
||||
| Environment | Mode | Why |
|
||||
|---|---|---|
|
||||
| Dev (`NODE_ENV !== "production"`) | Pretty-printed (auto-detect via `pino-pretty` if installed) | Readable in your terminal. |
|
||||
| Prod / Staging | JSON (default) | Coolify → Loki / Grafana / Sentry-Breadcrumb pipeline consumes JSON. Pretty-print would break ingestion. |
|
||||
|
||||
Override with `LOG_PRETTY=1` if you need pretty in prod (only for a
|
||||
short debug session — never permanent).
|
||||
|
||||
## ESLint enforcement
|
||||
|
||||
`no-console: 'error'` is active for:
|
||||
|
||||
- `**/*.server.ts` and `**/*.server.tsx`
|
||||
- `apps/api/src/**/*.ts`, `apps/worker/src/**/*.ts`
|
||||
- `packages/*/src/**/*.ts`
|
||||
- `apps/web/app/utils/**/*.ts`
|
||||
- `apps/web/app/modules/**/services/**/*.ts`
|
||||
- `apps/web/app/modules/**/db/**/*.ts`
|
||||
|
||||
Override `'off'` for: `**/*.test.ts(x)`, `**/*.spec.ts(x)`, `scripts/**`,
|
||||
and client-bundle files (`apps/web/app/components/**/*.tsx`,
|
||||
`apps/web/app/routes/**/*.tsx` without `.server.` suffix).
|
||||
|
||||
The lefthook pre-commit hook runs `scripts/eslint-staged.mjs` which
|
||||
fails the commit on any new `console.*` in server code. Verified
|
||||
2026-05-17 by injecting `console.log` into `apps/api/src/test-tmp.ts`
|
||||
and observing the hook block the commit.
|
||||
|
||||
## Migration inventory script
|
||||
|
||||
```bash
|
||||
pnpm exec tsx scripts/observability/scan-console.ts
|
||||
# generates docs/observability/console-migration-inventory.md
|
||||
# exit 1 with --check if any live console.* remain in server paths
|
||||
```
|
||||
|
||||
Wire this into CI eventually (already runs locally; CI run is tracked
|
||||
in Task #36 follow-ups).
|
||||
|
||||
## Examples
|
||||
|
||||
### Standard info event
|
||||
|
||||
```ts
|
||||
authLogger.info(
|
||||
{ userId, tenantId, op: "user.signin", method: "password" },
|
||||
"User signed in",
|
||||
);
|
||||
```
|
||||
|
||||
### Error with err serializer
|
||||
|
||||
```ts
|
||||
try {
|
||||
await sendEmail(...);
|
||||
} catch (error) {
|
||||
emailLogger.error(
|
||||
{ err: error, recipientFingerprint: emailFingerprint(to), op: "email.send" },
|
||||
"Failed to send email",
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### AI prompt (PII-safe)
|
||||
|
||||
```ts
|
||||
promptLogger.info(
|
||||
{
|
||||
op: "executeTemplate.createChatCompletion",
|
||||
templateId,
|
||||
model,
|
||||
promptHash: hashPrompt(prompt), // SHA-256, first 16 hex chars
|
||||
promptLength: prompt.length,
|
||||
tenantId,
|
||||
},
|
||||
"Creating AI chat completion",
|
||||
);
|
||||
```
|
||||
|
||||
### Stripe / webhook (PII-safe)
|
||||
|
||||
```ts
|
||||
pricingLogger.info(
|
||||
{
|
||||
op: "stripe.webhook.received",
|
||||
eventType: event.type,
|
||||
eventId: event.id,
|
||||
livemode: event.livemode,
|
||||
},
|
||||
"Stripe webhook received",
|
||||
);
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- `docs/improvement-plan/02-observability/logging-migration.md` — full
|
||||
task spec (Task #34) including PII rationale.
|
||||
- `packages/logger/src/index.ts` — Pino setup + redact paths.
|
||||
- `scripts/observability/scan-console.ts` — inventory script.
|
||||
- `docs/observability/console-migration-inventory.md` — auto-generated
|
||||
per-file inventory (0 hits after Phase-02 #34).
|
||||
Reference in new issue
Block a user