Skip to main content
Version: 1.0.0

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