Middleware
Composable handler middleware for transforming and enriching log records.
Overview
Middleware allows you to transform, enrich, filter, and manipulate log records before they reach the handler. Build powerful logging pipelines by composing middleware functions.
Basic Usage
import {
New,
MiddlewareHandler,
JSONHandler,
timestampMiddleware,
hostnameMiddleware
} from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
timestampMiddleware(),
hostnameMiddleware()
]
}));
logger.info('Application started');
// Includes timestamp and hostname automatically
Built-in Middleware
Timestamp Middleware
Add timestamp to every log:
import { timestampMiddleware } from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
timestampMiddleware() // Adds 'timestamp' attribute
]
}));
Hostname Middleware
Add hostname/server name:
import { hostnameMiddleware } from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
hostnameMiddleware() // Adds 'hostname' attribute
]
}));
PID Middleware
Add process ID:
import { pidMiddleware } from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
pidMiddleware() // Adds 'pid' attribute
]
}));
Rate Limit Middleware
Limit log rate:
import { rateLimitMiddleware } from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
rateLimitMiddleware(100) // Max 100 logs/second
]
}));
Deduplication Middleware
Prevent duplicate logs:
import { dedupeMiddleware } from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
dedupeMiddleware(5000) // Dedupe within 5 seconds
]
}));
logger.info('Duplicate message');
logger.info('Duplicate message'); // Skipped!
Enrich Middleware
Add custom attributes:
import { enrichMiddleware } from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
enrichMiddleware({
app: 'my-app',
version: '1.0.0',
env: process.env.NODE_ENV
})
]
}));
Transform Middleware
Transform attributes:
import { transformMiddleware } from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
transformMiddleware((record) => {
// Uppercase all message
record.message = record.message.toUpperCase();
return record;
})
]
}));
Conditional Middleware
Apply middleware conditionally:
import { conditionalMiddleware, timestampMiddleware } from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
conditionalMiddleware(
(record) => record.level >= Level.ERROR,
timestampMiddleware()
)
]
}));
Error Boundary Middleware
Catch middleware errors:
import { errorBoundaryMiddleware } from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
errorBoundaryMiddleware(), // Prevents middleware errors from breaking logs
riskyMiddleware()
]
}));
Custom Middleware
Create your own middleware:
import type { HandlerMiddleware } from '@omdxp/jslog';
function customMiddleware(): HandlerMiddleware {
return (record) => {
// Add request ID from async context
const requestId = getRequestId();
if (requestId) {
record.attrs.push({
key: 'requestId',
value: requestId
});
}
return record;
};
}
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [customMiddleware()]
}));
Middleware Composition
Stack multiple middleware:
import {
New,
MiddlewareHandler,
JSONHandler,
timestampMiddleware,
hostnameMiddleware,
pidMiddleware,
enrichMiddleware,
rateLimitMiddleware,
dedupeMiddleware
} from '@omdxp/jslog';
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [
// Order matters!
timestampMiddleware(),
hostnameMiddleware(),
pidMiddleware(),
enrichMiddleware({ app: 'my-app' }),
dedupeMiddleware(1000),
rateLimitMiddleware(100)
]
}));
MetricsMiddleware
Track logging statistics:
import {
New,
MiddlewareHandler,
MetricsMiddleware,
JSONHandler
} from '@omdxp/jslog';
const metrics = new MetricsMiddleware();
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [metrics.middleware()]
}));
// Log some stuff
logger.info('Message 1');
logger.warn('Warning');
logger.error('Error');
// Get statistics
const stats = metrics.getStats();
console.log('Total logs:', stats.total);
console.log('By level:', stats.byLevel);
console.log('Errors:', stats.errors);
// Reset metrics
metrics.reset();
Real-World Examples
Request Context Enrichment
import { AsyncLocalStorage } from 'async_hooks';
const requestContext = new AsyncLocalStorage<{
requestId: string;
userId?: string;
}>();
function requestContextMiddleware(): HandlerMiddleware {
return (record) => {
const context = requestContext.getStore();
if (context) {
record.attrs.push(
{ key: 'requestId', value: context.requestId },
{ key: 'userId', value: context.userId || 'anonymous' }
);
}
return record;
};
}
const logger = New(new MiddlewareHandler({
handler: new JSONHandler(),
middleware: [requestContextMiddleware()]
}));
// In Express middleware
app.use((req, res, next) => {
requestContext.run({
requestId: generateRequestId(),
userId: req.user?.id
}, next);
});
PII Redaction
function piiRedactionMiddleware(): HandlerMiddleware {
const emailRegex = /[\w.-]+@[\w.-]+\.\w+/g;
const phoneRegex = /\d{3}-\d{3}-\d{4}/g;
return (record) => {
// Redact message
record.message = record.message
.replace(emailRegex, '[EMAIL]')
.replace(phoneRegex, '[PHONE]');
// Redact attributes
record.attrs = record.attrs.map(attr => {
if (typeof attr.value === 'string') {
return {
...attr,
value: attr.value
.replace(emailRegex, '[EMAIL]')
.replace(phoneRegex, '[PHONE]')
};
}
return attr;
});
return record;
};
}
Performance Tracking
function performanceMiddleware(): HandlerMiddleware {
const startTime = Date.now();
let logCount = 0;
return (record) => {
logCount++;
const uptime = Date.now() - startTime;
const logsPerSecond = (logCount / uptime) * 1000;
record.attrs.push(
{ key: 'logCount', value: logCount },
{ key: 'logsPerSecond', value: Math.round(logsPerSecond) }
);
return record;
};
}
Environment-Based Routing
function environmentRoutingMiddleware(): HandlerMiddleware {
return (record) => {
const env = process.env.NODE_ENV;
// Add environment tag
record.attrs.push({ key: 'env', value: env });
// In development, add extra debug info
if (env === 'development') {
record.attrs.push(
{ key: 'source', value: `${record.source?.file}:${record.source?.line}` }
);
}
return record;
};
}
Best Practices
Do:
- Order middleware intentionally (enrichment before filtering)
- Keep middleware functions pure and fast
- Use error boundaries for risky middleware
- Document what each middleware does
Don't:
- Perform expensive operations in middleware
- Mutate records in place without cloning
- Forget that middleware runs on every log
- Stack too many middleware (impacts performance)
Performance Tips
// Good: Fast, simple middleware
function fastMiddleware(): HandlerMiddleware {
const hostname = os.hostname();
return (record) => {
record.attrs.push({ key: 'host', value: hostname });
return record;
};
}
// Bad: Slow middleware with I/O
function slowMiddleware(): HandlerMiddleware {
return (record) => {
// Don't do this!
const data = fs.readFileSync('/some/file');
record.attrs.push({ key: 'data', value: data });
return record;
};
}
See Also
- AsyncHandler - Non-blocking logging
- FilterHandler - Conditional logging
- Handlers - Handler concepts