GenerateSaaS

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 in packages/config/src/index.ts. When enabled it wraps the whole app as the first middleware; when off it is never registered, so requests are not logged at the API layer.

What you get

  • One log line per request: METHOD /path STATUS durationMs <cache-tag>.
  • Wall-clock duration measured with performance.now() (rounded to ms).
  • A cache tag showing how the response was served:
TagMeaning
(empty)Served fresh (handler ran).
cachedReturned from the Redis response cache (c.set("cached", true)).
etag304 Not Modified - client revalidated, no body sent.

Configuration

config.performanceMonitor is { enabled: boolean }. The middleware registers when the value is not false, so a missing key still logs.

packages/config/src/index.ts
performanceMonitor: {
  enabled: true
}
KeyTypeDefaultDescription
enabledbooleantrueWhen 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:

packages/api/src/index.ts
if (config.performanceMonitor?.enabled !== false) {
  app.use(performanceMonitor);
}

It records start before await next(), computes the duration after, then logs. Because it wraps everything, the duration includes all downstream middleware (CORS, rate limiting, cache, route handler).

Reading the logs

Output goes to the @repo/runtime logger (consola), which writes to stdout - your local 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).

GET /api/posts 200 12ms cached
POST /api/posts 201 84ms
GET /api/posts 304 3ms etag

Frequently asked questions

Does this add an /metrics endpoint or a dashboard? No. It is a structured log line per request, not a metrics scrape target or UI. Pipe stdout to your platform's log aggregator.

Why is the cache tag empty on most requests? The tag only appears when the response was 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.

On this page