Observability
GiftWrapt ships with no third-party observability by default. Operators who want error reporting or a metrics endpoint can opt in to either family independently, pointed at whichever backend they operate.
Stance
Section titled “Stance”Two families, two gates each:
- Env owns the host / secret.
SENTRY_DSNfor error reporting,METRICS_TOKENfor the metrics endpoint. Set per deploy, not editable from the admin UI. - Admin owns the runtime kill-switch.
enableSentryandenableMetricstoggles in Admin > Observability, default off, hot-reloadable per request.
Both gates must be true to emit. If you flip the env without flipping the toggle (or vice versa), nothing leaves the server.
Client-side analytics (Plausible, Umami, PostHog, Vercel Analytics) is deliberately not integrated. This page covers only operator-side error reports and metrics. See Privacy and Telemetry for the broader stance.
Sentry-compatible Error Reporting
Section titled “Sentry-compatible Error Reporting”Server-side errors (caught at the Nitro error hook and the TanStack server-fn loggingMiddleware) and browser-side errors (caught at the React ErrorBoundary plus Sentry’s React SDK) are forwarded to the backend you point SENTRY_DSN at. Works with sentry.io, self-hosted Glitchtip, or any compatible HTTP ingest.
Required env
Section titled “Required env”SENTRY_DSN- the project DSN from your Sentry / Glitchtip backend.
Optional env
Section titled “Optional env”SENTRY_ENVIRONMENT- defaults toNODE_ENV.SENTRY_RELEASE- typically the build SHA. Set in CI for source-map alignment.SENTRY_TRACES_SAMPLE_RATE- 0 to 1, defaults to 0 (errors only, no tracing spans).
Enabling
Section titled “Enabling”- Set
SENTRY_DSN(and any optional env vars above) in your deployment env. - Restart the app so CSP picks up the DSN origin for
connect-src. - In Admin > Observability, flip Enable Sentry Error Reporting on.
Disabling
Section titled “Disabling”Flip the admin toggle off. Server-side capture stops on the next request. Browser SDKs already loaded in open tabs keep running until those tabs reload or the deploy restarts.
What gets sent
Section titled “What gets sent”- Stack traces, error messages, the matched request route, the request URL with query string stripped.
- Item / list / claim / user-id fields that happen to be in scope at the throw site - deliberate debugging signal so the operator can act on real bugs.
- Sentry’s
sendDefaultPii: falseis set, so the SDK does not auto-attach IPs or default user info.
What is stripped (in src/lib/observability/scrubber.ts):
- Query strings on the request URL.
- Cookie + Authorization headers (and any header key matching
password/token/apiKey/secret). - Recursive strip of those same keys inside event extras, contexts, and breadcrumb data.
Recipients of gift lists never see Sentry events; the SDK runs server-side and in the operator’s own browser sessions, not in a recipient’s UI surface. Spoiler protection in the app is enforced at the rendering layer regardless of what does or doesn’t end up in the operator’s error tracker.
Prometheus Metrics
Section titled “Prometheus Metrics”Counters and histograms accumulate in memory on every deployment regardless of toggle state; the admin toggle and bearer token gate exposure, not accumulation. When you turn the endpoint on, the counters reflect activity from the moment the process started, not the moment you flipped the toggle.
Required env
Section titled “Required env”-
METRICS_TOKEN- bearer token, minimum 16 chars. Generate one with:Terminal window openssl rand -hex 32
Enabling
Section titled “Enabling”- Set
METRICS_TOKENin your deployment env. - In Admin > Observability, flip Enable Prometheus Metrics Endpoint on.
- Configure your Prometheus scraper to hit
/api/metricswithAuthorization: Bearer $METRICS_TOKEN.
Endpoint behavior
Section titled “Endpoint behavior”- Returns 404 (not 401) when disabled or the token is wrong, to avoid endpoint-existence disclosure to internet scanners.
- Returns Prometheus exposition format (
text/plain; version=0.0.4) on success. - All labels are enum-bounded. Routes with dynamic segments (
/lists/$listId, etc.) are normalized to/lists/<id>so cardinality stays bounded.
Exported Metrics
Section titled “Exported Metrics”Synced from the core registry at build time. Edit core/src/lib/observability/metrics.ts and re-run pnpm metrics:catalog in core/ to refresh.
| Metric | Type | Labels | Description |
|---|---|---|---|
claims_created_total | counter | (none) | Gift claims created (gifter assigned to an item). |
claims_deleted_total | counter | (none) | Gift claims deleted (unclaim). |
cron_run_duration_ms | histogram | job, outcome | Cron run duration in milliseconds, by job and resolved outcome. |
cron_run_outcomes_total | counter | job, outcome | Cron run outcomes, by job. Outcomes match cron_runs.status (success, error, skipped). |
http_request_duration_ms | histogram | method, route, status_class | HTTP request duration in milliseconds, by matched route template, method, and status class. |
intelligence_runs_completed_total | counter | outcome | Intelligence (recommendations) cron runs completed, by outcome. |
items_archived_total | counter | (none) | Items archived (revealed to recipient). |
items_created_total | counter | (none) | Items created on a list. |
items_pending_deletion_total | counter | (none) | Items flipped to pending-deletion via recipient delete on a claimed item. |
lists_created_total | counter | type | Lists created, by type. |
orphan_claims_cleaned_up_total | counter | (none) | Orphan claims hard-deleted by the daily cleanup pass. |
reveals_triggered_total | counter | trigger | Reveals (item archive) triggered, by trigger source. |
Why No Client-Side Analytics
Section titled “Why No Client-Side Analytics”A deliberate skip. Funnel and retention analytics on a family-scale wish-list app aren’t worth the CSP / privacy / cookie-banner cost. Re-evaluate if a future feature concretely needs it.