@verkut/logger
v0.0.1
Published
Pino-based logger for NestJS applications. Implements NestJS `LoggerService` interface with structured JSON logging, customizable message handlers, and request-scoped context support.
Readme
@verkut/logger
Pino-based logger for NestJS applications. Implements NestJS LoggerService interface with structured JSON logging, customizable message handlers, and request-scoped context support.
Installation
npm install @verkut/logger
# or
pnpm add @verkut/loggerPeer dependencies
npm install @nestjs/commonQuick start
import { NestFactory } from '@nestjs/core';
import { PinoLoggerModule, PinoLoggerService } from '@verkut/logger';
@Module({
imports: [PinoLoggerModule.forRoot()],
})
class AppModule {}
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(PinoLoggerService));
await app.listen(3000);
}Module configuration
forRoot
PinoLoggerModule.forRoot({
// Custom pino options (optional, sensible defaults provided)
loggerOptions: {
level: 'info',
transport: { target: 'pino-pretty' },
},
// Prepend context to log message string (default: true)
// true -> "UserService: something happened"
// false -> "something happened" (context only in structured field)
contextPrefix: true,
// Custom context provider for request-scoped fields (optional)
contextProvider: myContextProvider,
// Custom message handlers (optional)
messageHandlers: {
stringHandler: new MyStringHandler(),
errorHandler: new MyErrorHandler(),
objectHandler: new MyObjectHandler(),
},
});forRootAsync
PinoLoggerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
loggerOptions: {
level: config.get('LOG_LEVEL', 'info'),
},
}),
});Usage
With NestJS Logger wrapper
import { Logger, Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
private readonly logger = new Logger(UserService.name);
createUser(name: string) {
this.logger.log('Creating user');
// output: "UserService: Creating user"
this.logger.error(new Error('Failed to create user'));
// output: "UserService: Failed to create user" + err object in JSON
this.logger.warn({ userId: 1, action: 'delete' });
// output: "UserService: {\"userId\":1,\"action\":\"delete\"}"
}
}Direct injection
import { Injectable } from '@nestjs/common';
import { PinoLoggerService } from '@verkut/logger';
@Injectable()
export class PaymentService {
constructor(private readonly logger: PinoLoggerService) {
this.logger.setContext(PaymentService.name);
}
process() {
this.logger.log('Processing payment');
// output: "PaymentService: Processing payment"
}
}Log methods
All methods follow NestJS variadic LoggerService signature:
logger.log(message, ...optionalParams) // pino: info
logger.error(message, ...optionalParams) // pino: error
logger.warn(message, ...optionalParams) // pino: warn
logger.debug(message, ...optionalParams) // pino: debug
logger.verbose(message, ...optionalParams) // pino: trace
logger.fatal(message, ...optionalParams) // pino: fatalThe last string argument is treated as context. For error() and fatal(), stack traces are auto-detected via regex.
logger.error('fail', 'UserService');
// context = "UserService"
logger.error('fail', error.stack, 'UserService');
// stack = error.stack, context = "UserService"
logger.error(new Error('fail'));
// Error object serialized via pino err serializerMessage handling
Messages are routed to handlers based on type:
| Type | Handler | Behavior |
|------|---------|----------|
| string | StringMessageHandler | Passes through, propagates stack to context |
| Error | ErrorMessageHandler | Extracts .message, puts Error in err field for pino serializer |
| object | ObjectMessageHandler | JSON-stringifies via fast-safe-stringify, handles Error/Date/BigInt |
Custom message handlers
Implement the MessageHandler<T> interface:
import { MessageHandler, LogContext } from '@verkut/logger';
class MyStringHandler implements MessageHandler<string> {
handleMessage(message: string, stack?: string) {
return {
logMessage: message.toUpperCase(),
extraContext: stack ? { stack } : undefined,
};
}
}
// Register
PinoLoggerModule.forRoot({
messageHandlers: {
stringHandler: new MyStringHandler(),
},
});Context
Context priority (lowest to highest):
- Instance context (
setContext()) - Per-call context (last string argument)
- Provider context (
LogContextPort)
Custom context provider
Implement LogContextPort for request-scoped context (e.g., requestId, userId):
import { LogContextPort } from '@verkut/logger';
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class RequestLogContext extends LogContextPort {
private fields: Record<string, unknown> = {};
getContext() {
return this.fields;
}
updateContext(fields: Record<string, unknown>) {
Object.assign(this.fields, fields);
}
}PinoLoggerModule.forRoot({
contextProvider: new RequestLogContext(),
});Default pino options
When no loggerOptions are provided, the following defaults are used:
- Level:
debugin development,infoin production (NODE_ENV) - Transport:
pino-prettywith colorized single-line output - Timestamp: ISO format
License
MIT
