async-request-logger
v1.0.0
Published
A logger package that tracks request IDs through API calls using AsyncLocalStorage for Express and NestJS
Maintainers
Readme
Request Logger
A powerful TypeScript logger package that tracks request IDs throughout API calls using AsyncLocalStorage. Works seamlessly with both Express and NestJS applications.
Features
- 🔍 Request ID Tracking: Automatically tracks request IDs across your entire request lifecycle
- 🔄 AsyncLocalStorage: Uses Node.js AsyncLocalStorage for context propagation without parameter passing
- 🎨 Colored Logs: Optional colored console output for better readability
- 📦 Framework Agnostic Core: Use with Express, NestJS, or standalone
- 💪 TypeScript First: Full TypeScript support with type definitions
- 🎯 Zero Dependencies: Core logger has no runtime dependencies
Installation
npm install @your-scope/request-loggerPeer Dependencies
For Express:
npm install express @types/expressFor NestJS:
npm install @nestjs/common @nestjs/core rxjsUsage
Express
import express from 'express';
import { requestIdMiddleware, RequestLogger } from '@your-scope/request-logger';
const app = express();
const logger = new RequestLogger({ serviceName: 'my-api' });
// Add the middleware at the top of your middleware stack
app.use(requestIdMiddleware());
app.get('/api/users', async (req, res) => {
logger.info('Fetching users');
// Call other functions - the request ID is automatically tracked
await getUsersFromDatabase();
res.json({ users: [] });
});
async function getUsersFromDatabase() {
// The logger will automatically include the request ID from the current context
logger.debug('Querying database for users');
// Your database logic here
logger.info('Successfully fetched users from database');
}
app.listen(3000, () => {
console.log('Server running on port 3000');
});Custom Request ID Header
app.use(requestIdMiddleware({
headerName: 'x-correlation-id',
generateId: () => `custom-${Date.now()}`,
setResponseHeader: true
}));Attach Request ID to Request Object
import { requestIdMiddlewareWithAttach, RequestWithId } from '@your-scope/request-logger';
app.use(requestIdMiddlewareWithAttach());
app.get('/api/users', (req: RequestWithId, res) => {
console.log('Request ID:', req.requestId);
logger.info('Handling request');
res.json({ requestId: req.requestId });
});NestJS
Step 1: Add the Interceptor Globally
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { RequestIdInterceptor } from '@your-scope/request-logger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Add the interceptor globally
app.useGlobalInterceptors(new RequestIdInterceptor());
await app.listen(3000);
}
bootstrap();Step 2: Use the Logger Service
// app.module.ts
import { Module } from '@nestjs/common';
import { LoggerService } from '@your-scope/request-logger';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [
LoggerService,
UsersService,
],
})
export class AppModule {}// users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { LoggerService } from '@your-scope/request-logger';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(
private readonly logger: LoggerService,
private readonly usersService: UsersService,
) {}
@Get()
async getUsers() {
this.logger.info('Fetching users from controller');
return this.usersService.getUsers();
}
}// users.service.ts
import { Injectable } from '@nestjs/common';
import { LoggerService } from '@your-scope/request-logger';
@Injectable()
export class UsersService {
constructor(private readonly logger: LoggerService) {}
async getUsers() {
this.logger.debug('Querying database for users');
// Your database logic here
this.logger.info('Successfully fetched users');
return [];
}
}Custom Configuration
// main.ts
app.useGlobalInterceptors(
new RequestIdInterceptor({
headerName: 'x-correlation-id',
generateId: () => `nest-${Date.now()}`,
setResponseHeader: true,
})
);Standalone Usage
You can use the logger without Express or NestJS:
import { RequestLogger } from '@your-scope/request-logger';
const logger = new RequestLogger({
serviceName: 'my-service',
enableColors: true
});
// Manually set context
RequestLogger.runWithContext({ requestId: '12345' }, () => {
logger.info('This log will include requestId: 12345');
someFunction();
});
function someFunction() {
// The request ID is still available here
logger.debug('Inside nested function', { extra: 'metadata' });
}API Reference
RequestLogger
Constructor Options
interface LoggerOptions {
serviceName?: string; // Service name for logs (default: 'app')
enableColors?: boolean; // Enable colored output (default: true)
}Methods
debug(message: string, meta?: any): Log debug messageinfo(message: string, meta?: any): Log info messagewarn(message: string, meta?: any): Log warning messageerror(message: string, meta?: any): Log error messagechild(context: Record<string, any>): Create child logger with additional context
Static Methods
RequestLogger.getRequestId(): Get current request IDRequestLogger.getContext(): Get current context objectRequestLogger.setContextValue(key, value): Add value to current contextRequestLogger.runWithContext(context, fn): Run function with specific context
Log Output Format
{
"timestamp": "2025-11-22T10:30:45.123Z",
"level": "INFO",
"service": "my-api",
"message": "Fetching users",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"context": {},
"meta": {}
}Advanced Usage
Adding Custom Context
// Add custom context to the current request
RequestLogger.setContextValue('userId', '12345');
RequestLogger.setContextValue('tenantId', 'acme-corp');
logger.info('User action performed');
// Output will include userId and tenantId in the contextChild Logger with Additional Context
const userLogger = logger.child({
userId: '12345',
module: 'user-service'
});
userLogger.info('Processing user data');
// All logs will include userId and moduleBenefits of AsyncLocalStorage
- No Parameter Passing: Request ID automatically available in nested functions
- Thread-Safe: Each request maintains its own isolated context
- Clean Code: No need to pass logger or requestId through function parameters
- Framework Agnostic: Works with any async operations (database calls, HTTP requests, etc.)
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
