GenerateSaaS

Error Reporting

Send server, browser, and desktop errors to Sentry or PostHog from committed config - off by default, and off loads nothing.

config.observability wires error reporting into the Hono API, the browser, and the desktop app if you ship one. It ships off, and off loads nothing: each surface checks its provider's credential before dynamically importing an SDK. This is capture, not tracing - for per-request timing logs see performance monitoring.

Turn it on

Copy the key: Sentry's DSN (Settings → Projects → Client Keys), or PostHog's Project API key (phc_…) plus your instance host.
Paste it into config.observability in packages/config/src/index.ts.
Rebuild and restart. No env var, no CLI option to re-run.
packages/config/src/index.ts
observability: {
  sentry: { dsn: "https://examplePublicKey@o0.ingest.sentry.io/0" },
  posthog: { publicKey: "phc_yourkey", host: "https://us.i.posthog.com", llmContent: false }
},

Presence is the switch - no enabled flag, no provider selector. A provider reports when its credential is non-empty, and both may run at once. Keys are committed rather than env because they authorize sending only; SENTRY_DSN and POSTHOG_API_KEY override them server-side, but posthog.host does not, so an env key alone never arms PostHog.

No key, no reporting - silently. Keep the values literal: the bundler drops the SDK branch only when it sees the value inline, so a computed credential ships the SDK to every visitor.

The SDK has to be in your project. The observability manifest option decides at generation time which SDKs you get, stripping the rest with their adapters. To add one, put it in the observability array in .generatesaas/manifest.json and run generatesaas update.

Choose a provider

SentryPostHog
API / browser SDK@sentry/node, @sentry/browserposthog-node, posthog-js
Desktop app@sentry/electronNo Electron SDK exists
Crashes outside a requestReportedNot reported - no process handlers
Environment tagdevelopment / productionNone - use a separate PostHog project locally
LLM analytics-$ai_generation events

A PostHog desktop build reports nothing - it falls back to a no-op reporter plus one startup warning. Pick Sentry if desktop or boot-time crashes matter.

What gets captured

SurfaceWhere it livesReports
APIpackages/api/src/observability.ts, from app.onErrorUnhandled request errors, with the session user
Browser startupapps/web/instrumentation-client.tsArms window.onerror, rejections, breadcrumbs at boot
Error boundariesapps/web/lib/observability.tsRender errors React catches
Desktop mainapps/desktop/src/main/observability.tsMain-process exceptions and Electron's crash reporter
Desktop rendererapps/desktop/src/renderer/lib/observability.tsUncaught and React boundary errors, over Sentry's IPC channel

From your own code: await captureServerError(err, { userId }) (@repo/api/observability) on the server, captureClientError(err) (apps/web/lib/observability.ts) in the browser. Both no-op when off, and neither needs a flush.

404s and 403s are never reported. isReportableError (@repo/observability) drops anything with a status below 500 and always reports status-less errors. It sits inside captureClientError, so your own calls inherit it.

A visitor who declines cookies still has their errors reported, with their user id when signed in - capture never consults the consent store. Analytics is untouched. Gate it yourself by returning early from the browser wrapper.

LLM analytics (PostHog only)

With a PostHog key and AI enabled, every model call is recorded as a $ai_generation. Built-in and BYOK runs both report; BYOK sends tokens and latency but no cost, since that is the user's own provider bill.

PropertyValue
$ai_trace_idthe run id credit settlements are keyed by
$ai_model, $ai_providerthe model that ran, after any fallback
$ai_input_tokens, $ai_output_tokensthe usage the provider reported
$ai_input_cost_usd, $ai_output_cost_usd, $ai_total_cost_usdLIVE rates credits are debited from, not estimates
$ai_latencywall-clock seconds around the call
$ai_is_error, $ai_errorset when the call failed
$ai_input, $ai_output_choicesprompt and completion, only when llmContent: true - files, tool calls and tool results become placeholders, and reasoning is dropped

Prompts and completions stay put unless you opt in. llmContent is false until you set it to true, so nothing your users type reaches PostHog by default. Opting in sends their message text verbatim to a third party - check it against your privacy policy first. Every number is captured either way.

What this does not include

Capture only. Each gap is a deliberate opt-in:

  • Automatic tracing - needs init() before every other module loads. Move it to instrument.mjs: node --import ./instrument.mjs ....
  • Source-map upload - traces arrive minified. Add the Sentry bundler plugin (or posthog-cli) plus SENTRY_AUTH_TOKEN in CI.
  • The framework SDK - this is vanilla @sentry/browser + @sentry/node. Run the @sentry/nextjs wizard, then drop initBrowserReporting from instrumentation-client.ts. Replace the body of lib/observability.ts rather than deleting it (the boundaries import from it), keeping its isReportableError line.
  • Boot crashes on fullstack - the frontend loads @repo/api on the first request, so add initServerReporting() to the existing register() in apps/web/instrumentation.ts; never replace that file. separate arms at process start already.

Sentry adds up to 2 seconds to a 500 response - flush(2000) is awaited before the response goes out, because serverless freezes the function the instant it returns.

On this page