Request Tracker
Automatic request logging middleware that assigns a UUID request ID to every request. It logs method, path, client IP, and timing on the way in and out.
IMPORTANT
This component is auto-registered by BaseApplication during initialize(). No manual registration is needed.
Quick Reference
| Item | Value |
|---|---|
| Package | @venizia/ignis |
| Component | RequestTrackerComponent |
| Middleware | RequestSpyMiddleware |
| Utility | NetworkUtility.getIncomingIp() |
| Runtimes | Both (Bun and Node.js) |
Import Paths
import { RequestTrackerComponent } from '@venizia/ignis';In one example
Nothing to configure - once the application starts, every request is logged automatically.
[SpyMW] [<request-id>][127.0.0.1][=>] GET /hello | query: {} | body: null
[SpyMW] [<request-id>][127.0.0.1][<=] GET /hello | Took: 1.23 (ms)Bodies are logged only in a development environment. Everywhere else - including when NODE_ENV is unset - the body is omitted and query is still logged:
[SpyMW] [<request-id>][127.0.0.1][=>] GET /hello | query: {}
[SpyMW] [<request-id>][127.0.0.1][<=] GET /hello | Took: 1.23 (ms)| Direction | Format |
|---|---|
Incoming (=>) | [requestId][clientIp][=>] METHOD path | query: {...} | body: {...} |
Outgoing (<=) | [requestId][clientIp][<=] METHOD path | Took: X.XX (ms) |
The HTTP method is padded to 8 characters for consistent alignment.
How it works
- One middleware, and an ID it does not install.
requestId()comes fromRestApplication.registerDefaultMiddlewares(), which runs before any component, so it is already in place whenbinding()resolvesRequestSpyMiddlewarefrom the DI container and registers it. The generator is IGNIS'sRequestIdGenerator, not hono'scrypto.randomUUIDdefault - that keeps a server and a browser-Worker BFF stamping the same format. - IP resolution is best-effort, never fatal. The middleware falls through several sources before giving up - see the resolution order below. An unresolved IP never fails the request; this middleware observes traffic, it does not gate it.
- Body logging is environment-gated, and it fails closed.
RequestSpyMiddlewarereadsNODE_ENVonce in its constructor and logs the body only when the value is one oflocal,debug,development,dev,sit. Anything else -staging,uat,production, orNODE_ENVunset entirely - logs query only. Query is always logged in every environment.
WARNING
This rule used to be "anything that is not production", which logged full request bodies in staging, uat and with NODE_ENV unset. If you relied on bodies appearing outside a development environment, set NODE_ENV to one of the five values above.
- Body parsing follows Content-Type. See the outcomes table below for what each Content-Type resolves to. A parse failure throws
'Malformed Body Payload'(HTTP 400). - The middleware is an
IProvider, not a plain function.RequestSpyMiddlewareimplementsIProvider<MiddlewareHandler>from@venizia/ignis-inversion. The container instantiates the class, so it can holdisDebugModestate as an instance field. It then calls.value()to obtain the actual Hono handler.
TIP
The request ID is also available in the framework's error handlers (notFoundHandler, AppErrorMiddleware) - the same ID correlates error logs with the original request.
Common tasks
Correlate logs with a request
Read the requestId context value inside your own handlers or middleware - the same ID appears in every [SpyMW] log line for that request.
const requestId = context.get('requestId');Reuse parseBody for your own middleware
parseBody is a public method - reuse it wherever you need the same Content-Type-aware parsing.
async parseBody(opts: { req: TContext['req'] }): Promise<unknown>Understand the client IP resolution order
| Priority | Source | Notes |
|---|---|---|
| 1 | NetworkUtility.getIncomingIp(context) | Native connection info - hono/bun on Bun, @hono/node-server/conninfo on Node.js |
| 2 | x-real-ip header | Set by reverse proxies (e.g., Nginx proxy_set_header X-Real-IP) |
| 3 | x-forwarded-for header | Standard proxy header |
| 4 | 'unknown' | Logged when none of the above resolve - the request still proceeds |
Understand body-parsing outcomes
| Condition | Result |
|---|---|
No Content-Type header | null |
Content-Length is '0', or no body stream present | null |
Content-Type includes application/json | req.json() |
Content-Type includes multipart/form-data or application/x-www-form-urlencoded | req.parseBody() |
Content-Type is application/octet-stream | Raw body stream |
Any other Content-Type (text, html, xml, etc.) | req.text() |
| Parsing throws for any content type | 'Malformed Body Payload' (HTTP 400) |
Reference
Configuration
No user-configurable options - behavior is fully automatic.
Binding keys
| Key | Constant | Type | Required | Default |
|---|---|---|---|---|
middlewares.RequestSpyMiddleware | RequestTrackerComponent.REQUEST_TRACKER_MW_BINDING_KEY | MiddlewareHandler | Auto | Singleton provider, registered by the constructor |
The key is built as BindingNamespaces.MIDDLEWARE ('middlewares') + . + RequestSpyMiddleware.name.
RequestSpyMiddleware
class RequestSpyMiddleware extends BaseHelper implements IProvider<MiddlewareHandler> {
static readonly REQUEST_ID_KEY = 'requestId';
private isDebugMode: boolean;
// ...
}- Extends
BaseHelperwith scope'SpyMW' - Constructor sets
isDebugMode = process.env.NODE_ENV?.toLowerCase() !== Environment.PRODUCTION value()returns a Hono middleware built viacreateMiddleware()fromhono/factory
Component lifecycle
constructor()- ReceivesBaseApplicationvia DI. Defines the middleware binding as a singleton provider.binding()- Resolves theRequestSpyMiddlewarebinding, throwing if it cannot be resolved. Registers the resolved middleware on the server. The request ID is already installed by the application's default stack.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
Invalid middleware to init request tracker | Please check again binding value | RequestSpyMiddleware binding was unbound or overwritten before binding() ran | Don't unbind or replace middlewares.RequestSpyMiddleware; extend the component instead of removing its binding |
Malformed Body Payload (400) | Body content didn't match its declared Content-Type (e.g., invalid JSON with application/json) | Ensure clients send body content that matches the Content-Type header |
See also
Guides:
- Components Overview - Component system basics
- Middlewares - Request middleware system
Components:
- All Components - Built-in components list
Helpers:
- Logger Helper - Logging utilities
Best Practices:
- Troubleshooting Tips - Debugging with request IDs
- Deployment Strategies - Production logging
Files:
packages/core-server/src/components/request-tracker/component.ts-RequestTrackerComponentpackages/core-server/src/base/middlewares/request-spy/request-spy.middleware.ts-RequestSpyMiddlewarepackages/core-server/src/utilities/network.utility.ts-NetworkUtility.getIncomingIp()