Skip to main content

@globalart/nestjs-logger

Professional logging module for NestJS with clean architecture, multiple formats, and built-in security features.

Installation

npm install @globalart/nestjs-logger

Overview

This package provides a comprehensive logging solution for NestJS applications with support for multiple output formats, automatic HTTP request logging, and built-in security features. Built on Clean Architecture principles, it offers excellent performance and extensibility.

Key Features

  • Clean Architecture - Separation of concerns with SOLID principles
  • Type Safety - Full TypeScript support with IntelliSense
  • Performance Optimized - Minimal allocations and efficient queries
  • Multiple Formats - Text, JSON, and Pino output support
  • HTTP Logging - Automatic request/response logging
  • Security - Automatic sanitization of sensitive data
  • Extensible - Easy to add custom formatters and transports

Quick Start

import { Module } from "@nestjs/common";
import { LoggerModule } from "@globalart/nestjs-logger";

@Module({
imports: [
LoggerModule.forRoot({
level: "info",
timestamp: true,
colors: true,
format: "text",
}),
],
})
export class AppModule {}

Configuration

Basic Configuration

import { RequestMethod } from "@nestjs/common";

LoggerModule.forRoot({
level: "debug",
timestamp: true,
colors: true,
format: "json",
sensitiveFields: ["password", "secret"],
exclude: [
{ method: RequestMethod.GET, path: "/health" },
{ method: RequestMethod.ALL, path: "/favicon.ico" },
],
});

Async Configuration

import { ConfigService } from "@nestjs/config";

LoggerModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
level: configService.get("LOG_LEVEL", "info"),
format: configService.get("LOG_FORMAT", "text"),
colors: !configService.get("PRODUCTION"),
sensitiveFields: configService.get("SENSITIVE_FIELDS", []),
}),
inject: [ConfigService],
});

Usage Examples

Service Logging

import { Injectable } from "@nestjs/common";
import { InjectLogger, LoggerService } from "@globalart/nestjs-logger";

@Injectable()
export class UserService {
constructor(
@InjectLogger()
private readonly logger: LoggerService
) {}

async createUser(userData: CreateUserDto) {
this.logger.log({
message: "Creating new user",
metadata: { email: userData.email },
});

try {
const user = await this.userRepository.save(userData);
this.logger.log({
message: "User created successfully",
metadata: { id: user.id },
});
return user;
} catch (error) {
this.logger.error({
message: "Failed to create user",
trace: error.stack,
metadata: { email: userData.email },
});
throw error;
}
}
}

HTTP Logging

import { Controller, UseInterceptors } from "@nestjs/common";
import { HttpLoggerInterceptor, LogContext } from "@globalart/nestjs-logger";

@Controller("users")
@UseInterceptors(HttpLoggerInterceptor)
@LogContext("UserController")
export class UserController {
// All HTTP requests will be automatically logged
}

Global HTTP Logging

import { Module } from "@nestjs/common";
import { APP_INTERCEPTOR } from "@nestjs/core";
import { LoggerModule, HttpLoggerInterceptor } from "@globalart/nestjs-logger";

@Module({
imports: [LoggerModule.forRoot()],
providers: [
{
provide: APP_INTERCEPTOR,
useClass: HttpLoggerInterceptor,
},
],
})
export class AppModule {}

Output Formats

Text Format (Default)

[2024-01-15T10:30:45.123Z] [INFO] [UserService] Creating new user {"email":"user@example.com"}
[2024-01-15T10:30:45.335Z] [INFO] [HttpLogger] GET /users - 200 (12ms)

JSON Format

{
"timestamp": "2024-01-15T10:30:45.123Z",
"level": "info",
"message": "Creating new user",
"context": "UserService",
"metadata": { "email": "user@example.com" }
}

Pino Format

{
"level": 30,
"time": 1642247445123,
"pid": 1234,
"hostname": "app-server",
"req": {
"id": "req-123",
"method": "GET",
"url": "/users",
"remoteAddress": "127.0.0.1"
},
"res": { "statusCode": 200 },
"responseTime": 12,
"msg": "request completed"
}

API Reference

LoggerService Methods

MethodDescription
log(options: LogOptions)Information message
error(options: LogOptions)Error with stack trace
warn(options: LogOptions)Warning message
debug(options: LogOptions)Debug information
verbose(options: LogOptions)Verbose logging

LogOptions Interface

interface LogOptions {
message: string;
context?: string;
metadata?: Record<string, unknown>;
trace?: string;
}

Configuration Options

OptionTypeDefaultDescription
levelLogLevel'info'Logging level
timestampbooleantrueShow timestamp
colorsbooleantrueColored output
formatLogFormat'text'Output format
contextstringundefinedDefault context
sensitiveFieldsstring[][...]Fields to sanitize
excludeExcludeOption[][]HTTP requests to exclude from logging
logRequestsbooleanfalseLog every handled HTTP request
fieldNamingFieldNaming'camelCase'Case of the log entry's own field names
includeHeadersstring[]undefinedLog only these headers; all of them when unset
logRequestBodybooleantrueInclude the request body in the log entry
logResponseBodybooleanfalseInclude the response body in the log entry
maxBodyLengthnumber2048Truncate a serialized body longer than this

Field Naming

Log entries name their own fields in camelCase by default — responseTime, statusCode, remoteAddress, traceId. Set fieldNaming: "snake_case" when the log pipeline expects snake_case keys:

LoggerModule.forRoot({
format: "pino",
logRequests: true,
fieldNaming: "snake_case",
});
{ "level": 30, "req": { "remote_address": "10.0.0.1" }, "res": { "status_code": 200 }, "response_time": 56 }

Only the entry's own fields are renamed. Everything the application or the client supplied — headers, metadata, query, params and the request and response bodies — keeps its keys verbatim, so a payload field named accountId stays accountId. Applies to the json and pino formats; text output is unaffected.

HTTP Payload Logging

By default every request header is written to the log entry, which makes lines large enough to be dropped by log backends that enforce a per-line size limit, and leaks cookie and other credential-carrying headers into log storage. Pass includeHeaders to keep only the headers you actually query on, and cap payloads with maxBodyLength:

LoggerModule.forRoot({
format: "pino",
logRequests: true,
includeHeaders: ["user-agent", "x-request-id", "content-type"],
logRequestBody: true,
logResponseBody: true,
maxBodyLength: 2048,
});

Header matching is case-insensitive and applies to both request and response headers. Bodies are sanitized with sensitiveFields first and truncated afterwards; a truncated body is replaced by its serialized prefix followed by ...[TRUNCATED <n> chars].

Decorators

@LogContext

Set context for class or method:

@Controller("users")
@LogContext("UserController")
export class UserController {
@Get()
@LogContext("UserController.getAllUsers")
async getAllUsers() {
// Context will be "UserController.getAllUsers"
}
}

@LogMetadata

Add metadata to logs:

@Controller("users")
export class UserController {
@Get()
@LogMetadata({ operation: "getAllUsers", version: "v1" })
async getAllUsers() {
// Metadata will be included in all logs
}
}

@ExcludeLogging

Exclude specific endpoints from logging:

@Controller("health")
export class HealthController {
@Get()
@ExcludeLogging()
async check() {
return { status: "ok" };
}
}

Excluding HTTP Requests

Configure which requests to exclude from logging:

LoggerModule.forRoot({
exclude: [
// Health checks
{ method: RequestMethod.GET, path: "/health" },
{ method: RequestMethod.GET, path: "/metrics" },

// Static assets
{ method: RequestMethod.ALL, path: "/favicon.ico" },
{ method: RequestMethod.ALL, path: "/robots.txt" },

// Wildcard patterns
{ method: RequestMethod.GET, path: "/api/*/health" },
{ method: RequestMethod.ALL, path: "/static/*" },
],
});

Security Features

Automatic Data Sanitization

The logger automatically sanitizes sensitive fields:

  • password, pass
  • token, accessToken, refreshToken
  • secret, key, apiKey
  • authorization, auth
  • credential, credentials

Custom Sensitive Fields

LoggerModule.forRoot({
sensitiveFields: ["password", "secret", "customField"],
});

Performance Features

  • Minimal allocations in hot paths
  • Lazy initialization to reduce startup time
  • Optimized context resolution with caching
  • Efficient data sanitization with minimal overhead
  • Format-specific optimizations for different output types

Customization

Custom Formatter

import { Injectable } from "@nestjs/common";
import { BaseFormatter, LogEntry } from "@globalart/nestjs-logger";

@Injectable()
export class CustomFormatter extends BaseFormatter {
format(entry: LogEntry): string {
return `[${entry.level.toUpperCase()}] ${entry.message}`;
}
}

Custom Writer

import { Injectable } from "@nestjs/common";
import { ILogWriter } from "@globalart/nestjs-logger";

@Injectable()
export class FileWriter implements ILogWriter {
write(formattedLog: string): void {
// Write to file, database, or external service
}
}

Best Practices

  • Use Structured Logging - Include relevant metadata in your log entries
  • Set Appropriate Log Levels - Use debug/verbose for development, info/warn/error for production
  • Exclude Health Checks - Prevent log spam from monitoring endpoints
  • Sanitize Sensitive Data - Configure sensitive fields to prevent data leakage
  • Use Context - Set meaningful context names for better log organization
  • Performance Monitoring - Use HTTP logging to monitor API performance