Observability
Sentry set up for every part of the app - the browser, the TanStack Start server, and the Hono API.
@zap-ts/observability tells you when the app breaks. It sends errors to
Sentry. Sentry is a service that collects errors and shows them to you.
The package also sends traces. A trace is a record of how long one request took, step by step.
Each part of the app runs in a different place. The browser is one place. The server is another. So the package has one entry point for each place:
./clientfor the browser./serverfor the TanStack Start server./reactfor the router./hono/cloudflareand./hono/vercelfor the API
Nothing is sent until you set SENTRY_DSN. A DSN is the address of your
Sentry project. Without it, every entry point starts with enabled: false.
So local runs and tests send nothing.
Two entry points that run on import
./client and ./server are not functions you call. You only import them.
The import itself starts Sentry for that place. This is why the
package.json lists both files under sideEffects.
import "@zap-ts/observability/client";
import "@zap-ts/observability/server";
You do not need to write these two lines yourself.
@zap-ts/tanstack-start already writes them, in lib/hydrate and in
lib/server-entry.
In the browser, ./client also turns on two Sentry features. Session replay
records what the user did before the error. The feedback widget lets the user
write you a message. Replay records 10% of normal sessions, and 100% of the
sessions that hit an error.
The router and the server
./react is the part your own code uses. It gives you captureException,
which sends one error to Sentry:
import { captureException } from "@zap-ts/observability/react";
try {
await risky();
} catch (error) {
captureException(error);
}
It also gives you instrumentRouter. It tells Sentry to time each page the
router loads. It does nothing on the server:
import { instrumentRouter } from "@zap-ts/observability/react";
const router = createRouter();
instrumentRouter(router);
./server gives you two middlewares. A middleware is code that runs around
every request. They are called sentryGlobalRequestMiddleware and
sentryGlobalFunctionMiddleware. Together they trace every request and every
server function.
./server also gives you ServerObservabilityLive. It is an Effect layer
that hands you the Sentry client itself, under the tag
ServerObservability. It closes the client when the work ends. Use it only if
the middlewares are not enough.
The API
The API uses Hono, not TanStack Start. Sentry starts in a different way on
Cloudflare and on Vercel. So there is one file for each host. Both export a
function with the same name, sentryMiddleware. It takes the app and a DSN.
The DSN may be null:
import { sentryMiddleware } from "@zap-ts/observability/hono/cloudflare";
const app = new Hono();
app.use("*", sentryMiddleware(app, ENV.SENTRY_DSN ?? null));
Add it before every other middleware. Then it wraps the whole request. It
reports the errors that reach app.onError. It does not report the 3xx and
4xx ones, because those are normal answers, not bugs.
Environment
SENTRY_DSN=
SENTRY_ORG=
SENTRY_PROJECT=
SENTRY_AUTH_TOKEN=
Only SENTRY_DSN is read while the app runs.
The other three are read when you build the app. The Sentry plugin in
tooling/vite/src/config.ts uses them to upload source maps. A source map
turns the built code in a stack trace back into your own code. Each value is
passed only if you set it. A build without them still works. You just get
stack traces that are harder to read.
All four are declared in the root .env.schema. See
Environment to learn how varlock turns them into the
typed ENV that the code imports.
Related
- TanStack Start - it already imports the client and server entry points
- Environment - where
SENTRY_DSNcomes from - Analytics - what users do, instead of what breaks
- Going to production - setting a real DSN