Create Logger middleware with error collection architecture
Integration Trap: Must register early via router.useEarly() to capture all request lifecycle errors. Critical Behavior: Errors are collected during request processing, not thrown immediately. Performance Alert: Console.log is synchronous and blocks the event loop - avoid massive log volumes.
Optional
options: { Logger configuration
Middleware identifier
Log request bodies (development only, never production)
Log request headers (consider privacy for production)
JSON logs (true) vs colored terminal output (false)
// Just works - beautiful colored output, includes headers for debugging
const logger = new Logger();
router.useEarly(logger);
Gets the middleware handler function.
Returns the function that will be executed when the middleware runs. This getter provides read-only access to the handler function.
The handler function
const middleware = new Middleware((ctx) => {
console.log('Processing request');
});
const handler = middleware.handler;
console.log(typeof handler); // 'function'
// Can be used for testing or introspection
if (handler.toString().includes('console.log')) {
console.log('This middleware includes logging');
}
Gets the middleware identifier.
Returns the identifier string or null if no identifier was set. This getter provides read-only access to the identifier.
The identifier or null if not set
// Middleware with identifier
const authMiddleware = new Middleware((ctx) => {}, 'auth');
console.log(authMiddleware.identifier); // 'auth'
// Middleware without identifier
const loggingMiddleware = new Middleware((ctx) => {});
console.log(loggingMiddleware.identifier); // null
// Use identifier for conditional logic
if (middleware.identifier === 'authentication') {
console.log('This is an authentication middleware');
}
Executes the middleware handler with the given context.
This method invokes the middleware handler function, passing the context object as the first argument. The method supports both synchronous and asynchronous handlers, always returning a Promise.
Execution Flow:
Error Handling: Any errors thrown by the handler (synchronous or asynchronous) are propagated to the caller. This allows middleware errors to be handled by the calling code.
The request/response context
Promise that resolves when the handler completes
const middleware = new Middleware((ctx) => {
console.log(`Processing ${ctx.method} request to ${ctx.path}`);
ctx.data.processedAt = new Date().toISOString();
});
const url = new URL('http://localhost/api/users');
const ctx = new Context('GET', url, new Headers());
// Execute middleware
await middleware.execute(ctx);
console.log(ctx.data.processedAt); // ISO timestamp
// Async middleware
const asyncMiddleware = new Middleware(async (ctx) => {
await new Promise(resolve => setTimeout(resolve, 100));
ctx.data.asyncProcessed = true;
});
await asyncMiddleware.execute(ctx);
console.log(ctx.data.asyncProcessed); // true
// Error handling
const errorMiddleware = new Middleware((ctx) => {
throw new Error('Middleware error');
});
try {
await errorMiddleware.execute(ctx);
} catch (error) {
console.error('Middleware failed:', error.message);
}
// Invalid context
try {
await middleware.execute(null);
} catch (error) {
console.error(error.message); // "Context must be a Context instance"
}
Checks if this middleware has the same identifier as another middleware.
This method is used for duplicate detection and middleware comparison. Two middleware are considered to have the same identifier if:
Note: Middleware with null identifiers are never considered equal, even if both have null identifiers.
The other middleware to compare with
True if both middlewares have the same non-null identifier
const auth1 = new Middleware((ctx) => {}, 'authentication');
const auth2 = new Middleware((ctx) => {}, 'authentication');
const logging = new Middleware((ctx) => {}, 'logging');
const generic = new Middleware((ctx) => {});
// Same identifier
auth1.hasSameIdentifier(auth2); // true
auth2.hasSameIdentifier(auth1); // true
// Different identifiers
auth1.hasSameIdentifier(logging); // false
logging.hasSameIdentifier(auth1); // false
// Null identifiers (never equal)
auth1.hasSameIdentifier(generic); // false
generic.hasSameIdentifier(auth1); // false
generic.hasSameIdentifier(new Middleware((ctx) => {})); // false
// Invalid parameters
auth1.hasSameIdentifier(null); // false
auth1.hasSameIdentifier({}); // false
auth1.hasSameIdentifier('string'); // false
// Use for duplicate prevention
function addMiddlewareIfNotExists(middlewareList, newMiddleware) {
const hasDuplicate = middlewareList.some(existing =>
existing.hasSameIdentifier(newMiddleware)
);
if (!hasDuplicate) {
middlewareList.push(newMiddleware);
}
}
Logger - The debugging companion you actually want to use
After years of wrestling with logging libraries that seemed designed to make debugging harder, not easier, we built something different. This logger does exactly what you need and nothing you don't.
The Problem This Solves
You know the drill: Your app crashes in production, you check the logs, and get a wall of JSON that tells you everything except what you actually need to know. Or worse, a stack trace that looks like this:
Good luck figuring out where YOUR code went wrong.
What You Get Instead
Development Mode: Beautiful, scannable terminal output that actually helps
Production Mode: Structured JSON that compliance auditors love
Zero Dependencies, Maximum Value
This logger has exactly zero external dependencies. No security vulnerabilities from packages you never asked for. No breaking changes from upstream maintainers who decided to rewrite everything. Just reliable logging that works.
Smart Error Collection
Unlike most loggers that crash your request when an error occurs, this one collects errors throughout the request lifecycle and logs them properly at the end. Your users get proper responses, you get proper debugging info.
Compliance Ready
Production logs automatically include everything needed for SOC2, ISO 27001, and GDPR compliance:
Example: Basic Usage
Example: Error Collection in Action
Example: Custom Error Properties
Configuration Philosophy
We believe good defaults beat extensive configuration. This logger works perfectly out of the box for 90% of use cases. The few options we provide are there because they actually matter in real applications.
production
: Switch between beautiful development output and structured JSONincludeHeaders
: Control header logging (useful for privacy/compliance)includeBody
: Debug request bodies in development (never in production)No configuration files, no environment variables, no magic strings. Just import, instantiate, and start shipping.