Performance Monitoring
Log every API request's method, path, status, duration, and cache outcome via a Hono timing middleware, gated by config.performanceMonitor.
A Hono middleware (packages/api/src/middleware/performance.ts) times every request and logs one line through the @repo/runtime logger, gated by config.performanceMonitor.enabled. This is the local logging layer - everything stays in your own stdout; to ship exceptions to a third party see error reporting, an independent flag that can run alongside it.
What you get
One line per request - METHOD /path STATUS durationMs <cache-tag> - with wall-clock duration from performance.now(), rounded to ms.
| Cache tag | Meaning |
|---|---|
| (empty) | Served fresh (the handler ran) |
cached | Returned from the Redis response cache (c.set("cached", true)) |
etag | 304 Not Modified - client revalidated, no body sent |
GET /api/posts 200 12ms cached
POST /api/posts 201 84ms
GET /api/posts 304 3ms etagConfiguration
config.performanceMonitor is { enabled: boolean }. The middleware registers when the value is not false, so a missing key still logs.
performanceMonitor: {
enabled: true
}| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | When false, the middleware is not mounted and the API logs no request lines. |
The fullstack target mounts the API inside a host framework that already logs requests natively, so the CLI writes enabled: false there to avoid double-logging. Standalone backend targets default to true.
How it works
The middleware is the first app.use in packages/api/src/index.ts, registered only when the flag is on:
if (config.performanceMonitor?.enabled !== false) {
app.use(createPerformanceMonitor(requestLogSkips));
}createPerformanceMonitor takes predicates naming requests to serve without a line - high-frequency machine traffic a human never reads - and returns the middleware; pass none to log everything. It records start before await next() and computes the duration after, so the timing covers all downstream middleware (CORS, rate limiting, cache, handler).
Reading the logs
Output goes to the @repo/runtime logger (consola), which writes to stdout: your terminal in dev, your host's log stream in production. There are no environment variables for this feature; verbosity follows the logger's level, which is higher in development (NODE_ENV).
Frequently asked questions
Does this add a /metrics endpoint or a dashboard?
No. It is a structured log line per request, not a scrape target or UI. Pipe stdout to your platform's log aggregator.
Why is the cache tag empty on most requests?
The tag appears only on a cache hit (cached) or a revalidation (etag). Fresh handler responses log no tag. See Caching.
Will it log requests the rate limiter rejects?
Yes. It wraps the entire app, so a 429 is timed and logged like any other response.