npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@eqxjs/nest-logger

v3.1.0

Published

NestJS logger module for EQXJS

Readme

@eqxjs/nest-logger

A comprehensive, production-ready logging module for NestJS applications with built-in OpenTelemetry integration, structured logging, and support for multiple message formats.

Version Test Coverage Node Version

Features

Key Features:

  • 🚀 NestJS Integration - Seamless integration with NestJS framework (v11+)
  • 📊 OpenTelemetry Support - Built-in metrics, traces, and spans with distributed tracing
  • 🏷️ Structured Logging - Consistent log format with rich metadata (27+ fields)
  • 🔒 Sensitive Data Masking - Automatic masking of sensitive fields (password, token, apiKey, etc.)
  • 📝 Multiple Log Levels - Debug, Info, Log, Warn, Error, Verbose support with environment filtering
  • 🎯 Message Formats - Support for M1 (broker/queue), M2 (HTTP/protocol), and M3 (service) message protocols
  • Performance Optimized - Efficient logging with caching, fast paths, and minimal overhead
  • 🧪 Well Tested - 100% line coverage, 98%+ statement coverage with 297 test cases
  • 🔍 Telemetry Metrics - Counter and histogram metrics for observability with configurable filtering
  • 📦 TypeScript - Full TypeScript support with comprehensive type definitions and JSDoc
  • ⏱️ High-Resolution Timing - Built-in TimeDiff class for precise performance measurements
  • 🎨 Builder Pattern - Fluent LoggerDtoBuilder for complex log construction
  • 🌍 UTC+7 Timezone - Standardized timestamps with configurable timezone support
  • 🔧 Message Truncation - Automatic message truncation (4096 chars) to prevent log overflow
  • 📋 14 Action Tags - Predefined action constants for consistent categorization

Installation

# Using yarn
yarn add @eqxjs/nest-logger

# Using npm
npm install @eqxjs/nest-logger

Requirements:

  • Node.js >= 22
  • NestJS >= 11

Feature Overview

Logger Types Comparison

| Feature | CustomLogger | BaseAppLogger | AppLogger | LoggerFormat (M1/M2/M3) | |---------|--------------|---------------|-----------|-------------------------| | Basic Logging | ✅ | ✅ | ✅ | ✅ | | Structured Metadata | ❌ | ✅ | ✅ | ✅ | | OpenTelemetry | ❌ | ✅ | ✅ | ✅ | | Message Formats | ❌ | ❌ | ✅ | ✅ | | Summary Logs | ❌ | ✅ | ✅ | ✅ | | Sensitive Masking | ✅ | ✅ | ✅ | ✅ | | Use Case | Simple apps | Advanced apps | Multi-format | Specific protocols |

Supported Log Levels

All loggers support these levels (controlled by LOG_LEVEL environment variable):

  • debug - Detailed diagnostic information
  • info - General informational messages
  • log - Alias for info level
  • warn - Warning messages for potentially harmful situations
  • error - Error events that might still allow the app to continue
  • verbose - Most detailed information for deep debugging

Metadata Fields

LoggerDto includes 27+ fields for comprehensive logging:

| Category | Fields | |----------|--------| | Core | level, timestamp, message, action | | Application | appName, componentName, componentVersion | | Tracking | sessionId, transactionId, recordName, guid | | Context | channel, broker, device, user, public | | Use Case | useCase, useCaseStep | | Result | appResult, appResultCode, serviceTime, stack | | Infrastructure | instance, originateServiceName, recordType |

Quick Start

1. Import the Module

import { Module } from '@nestjs/common';
import { CustomLoggerModule } from '@eqxjs/nest-logger';

@Module({
  imports: [CustomLoggerModule],
})
export class AppModule {}

2. Use the Logger

import { Injectable } from '@nestjs/common';
import { CustomLogger, AppLogger } from '@eqxjs/nest-logger';

@Injectable()
export class MyService {
  constructor(
    private readonly logger: CustomLogger,
    private readonly appLogger: AppLogger,
  ) {}

  doSomething() {
    this.logger.log('Simple log message', 'MyService');
    this.logger.error('Error occurred', 'MyService');
  }
}

Message Protocol Data Structures

The logger supports three message protocols (M1, M2, M3), each with specific required properties:

DataM1I - Broker/Queue Messages

Required Properties:

  • header: DataHeaderI - Message header with identity
  • protocol: DataProtocolI - Protocol-specific information
  • body: Record<string, unknown> - Message payload
import { DataM1I } from '@eqxjs/nest-logger';

const data: DataM1I = {
  header: {
    sessionId: 'session-123',
    transactionId: 'txn-456',
    broker: 'kafka',
    channel: 'order-queue',
    useCase: 'ProcessOrder',
    identity: {
      device: 'mobile-app',
      user: 'user-123',
      public: 'public-id'
    }
  },
  protocol: {
    topic: 'order.created',
    command: 'PROCESS',
    qos: '1'
  },
  body: {
    orderId: '12345',
    amount: 100.00
  }
};

DataM2I - HTTP/Protocol Messages

Required Properties:

  • header: DataHeaderI - Message header with identity
  • body: Record<string, unknown> - Message payload

Optional Properties:

  • protocol: DataProtocolI - HTTP/protocol information
import { DataM2I } from '@eqxjs/nest-logger';

const data: DataM2I = {
  header: {
    sessionId: 'session-123',
    transactionId: 'txn-456',
    channel: 'web',
    useCase: 'CreatePayment',
    identity: {
      user: 'user-123',
      public: 'public-id'
    }
  },
  body: {
    paymentId: 'pay-123',
    status: 'completed'
  }
};

DataM3I - Service/Database Messages

Required Properties:

  • service: DataServiceI - Service-specific information with identity
  • body: Record<string, unknown> - Message payload

Optional Properties:

  • header: DataHeaderI - Message header with identity
import { DataM3I } from '@eqxjs/nest-logger';

const data: DataM3I = {
  service: {
    serviceName: 'PostgreSQL',
    name: 'users-db',
    invoke: 'SELECT',
    identity: {
      device: 'db-server-1',
      user: 'db-user'
    }
  },
  body: {
    query: 'SELECT * FROM users WHERE id = $1',
    resultCount: 1
  }
};

Identity Structure

All message types include an identity object with optional fields:

identity: {
  device?: string | string[];  // Device identifier(s)
  public?: string;             // Public identifier
  user?: string;               // User identifier
}

Usage Examples

Basic Logging

CustomLogger - Simple Logging

import { CustomLogger } from '@eqxjs/nest-logger';

export class UserService {
  private readonly logger = new CustomLogger('UserService');

  createUser(userData: any) {
    this.logger.log('Creating new user');
    this.logger.debug('User data received', 'UserService');
    this.logger.warn('Deprecated API used', 'UserService');
    this.logger.error('Failed to create user', 'UserService');
    this.logger.verbose('Detailed processing info', 'UserService');
  }
}

Real-World Examples

Example 1: HTTP Request/Response Logging

import { Injectable } from '@nestjs/common';
import { AppLogger, ActionMessage, TimeDiff, DataM2I } from '@eqxjs/nest-logger';

@Injectable()
export class PaymentController {
  private readonly logger = new AppLogger('PaymentAPI', 'PaymentController');

  async processPayment(request: any) {
    const timer = new TimeDiff();
    const sessionId = request.headers['x-session-id'];
    const transactionId = request.headers['x-transaction-id'];

    const data: DataM2I = {
      header: {
        sessionId,
        transactionId,
        channel: 'web',
        useCase: 'ProcessPayment',
        identity: {},
      },
      body: {
        amount: request.body.amount,
        currency: request.body.currency,
      },
    };

    // Log incoming request
    this.logger.loggerM2.info(
      'api.payment',
      ActionMessage.httpRequest(),
      data,
      `Payment request received: ${request.body.amount}`
    );

    try {
      // Process payment logic...
      const result = await this.executePayment(request.body);

      // Log successful response
      this.logger.loggerM2.summarySuccess(
        'api.payment',
        'Payment processed successfully',
        '200',
        timer.diff(),
        data
      );

      return result;
    } catch (error) {
      // Log error response
      this.logger.loggerM2.summaryError(
        'api.payment',
        'Payment processing failed',
        '500',
        timer.diff(),
        data,
        [error.stack]
      );

      throw error;
    }
  }
}

Example 2: Kafka Message Consumer

import { Injectable } from '@nestjs/common';
import { AppLogger, ActionMessage, TimeDiff, DataM1I } from '@eqxjs/nest-logger';

@Injectable()
export class OrderConsumer {
  private readonly logger = new AppLogger('OrderService', 'KafkaConsumer');

  async consumeOrderMessage(message: any) {
    const timer = new TimeDiff();

    const data: DataM1I = {
      header: {
        sessionId: message.sessionId,
        transactionId: message.transactionId,
        broker: 'kafka',
        channel: 'order-topic',
        useCase: 'ProcessOrder',
        useCaseStep: 'Consume',
        identity: {},
      },
      protocol: {
        topic: 'order.created',
        command: 'CONSUME',
      },
      body: {
        orderId: message.orderId,
        customerId: message.customerId,
      },
    };

    // Log message consumption start
    this.logger.loggerM1.info(
      'order.created',
      ActionMessage.consume(),
      data,
      `Consuming order message: ${message.orderId}`
    );

    try {
      // Process the message
      await this.processOrder(message);

      // Log successful consumption
      this.logger.loggerM1.info(
        'order.created',
        ActionMessage.consumed(),
        data,
        `Order message consumed successfully`
      );

      this.logger.loggerM1.summarySuccess(
        'order.created',
        'Order processed',
        '200',
        timer.diff(),
        data
      );
    } catch (error) {
      // Log consumption failure
      this.logger.loggerM1.error(
        'order.created',
        ActionMessage.exception(),
        data,
        `Failed to consume order message: ${error.message}`
      );

      this.logger.loggerM1.summaryError(
        'order.created',
        'Order processing failed',
        '500',
        timer.diff(),
        data,
        [error.stack]
      );

      throw error;
    }
  }
}

Example 3: Database Operations

import { Injectable } from '@nestjs/common';
import { AppLogger, ActionMessage, TimeDiff, DataM3I } from '@eqxjs/nest-logger';

@Injectable()
export class UserRepository {
  private readonly logger = new AppLogger('UserService', 'UserRepository');

  async findUserById(userId: string, sessionId: string, transactionId: string) {
    const timer = new TimeDiff();

    const data: DataM3I = {

      service: {
        serviceName: 'PostgreSQL',
        invoke: 'SELECT',
        name: 'users',
        identity: {},
      },
      body: {
        userId: userId,
        operation: 'findById',
      },
    };

    // Log database request
    this.logger.loggerM3.debug(
      'db.users',
      ActionMessage.dbRequest(),
      data,
      `Querying user by ID: ${userId}`
    );

    try {
      const user = await this.executeQuery(`SELECT * FROM users WHERE id = $1`, [userId]);

      // Log database response
      this.logger.loggerM3.debug(
        'db.users',
        ActionMessage.dbResponse(),
        data,
        `User found: ${user.email}`
      );

      this.logger.loggerM3.summarySuccess(
        'db.users',
        'User retrieved',
        '200',
        timer.diff(),
        data
      );

      return user;
    } catch (error) {
      this.logger.loggerM3.summaryError(
        'db.users',
        'Database query failed',
        '500',
        timer.diff(),
        data,
        [error.stack]
      );

      throw error;
    }
  }
}

Example 4: Microservice Communication

import { Injectable } from '@nestjs/common';
import { AppLogger, ActionMessage, TimeDiff, DataM3I } from '@eqxjs/nest-logger';

@Injectable()
export class NotificationService {
  private readonly logger = new AppLogger('NotificationService', 'EmailClient');

  async sendEmail(emailData: any, sessionId: string, transactionId: string) {
    const timer = new TimeDiff();

    const data: DataM3I = {
      service: {
        serviceName: 'EmailService',
        operation: 'SEND',
        endpoint: '/api/v1/email/send',
      },
    };

    // Log outbound call
    this.logger.loggerM3.info(
      'notification.email',
      ActionMessage.outbound(),
      data,
      `Sending email to: ${emailData.recipient}`
    );

    try {
      const response = await this.emailClient.send(emailData);

      // Log successful response
      this.logger.loggerM3.info(
        'notification.email',
        ActionMessage.inbound(),
        data,
        `Email sent successfully`
      );

      this.logger.loggerM3.summarySuccess(
        'notification.email',
        'Email delivered',
        '200',
        timer.diff(),
        data
      );

      return response;
    } catch (error) {
      this.logger.loggerM3.summaryError(
        'notification.email',
        'Email delivery failed',
        '500',
        timer.diff(),
        data,
        [error.stack]
      );

      throw error;
    }
  }
}

Example 5: Business Logic with Comprehensive Logging

import { Injectable } from '@nestjs/common';
import { 
  AppLogger, 
  ActionMessage, 
  TimeDiff, 
  logStringify 
} from '@eqxjs/nest-logger';

@Injectable()
export class OrderProcessingService {
  private readonly logger = new AppLogger('OrderService', 'OrderProcessor');

  async processOrder(orderData: any, sessionId: string, transactionId: string) {
    const overallTimer = new TimeDiff();

    // Log start of business logic
    this.logger.app.info(
      'Starting order processing',
      ActionMessage.appLogic(),
      'OrderProcessingService',
      orderData.orderId,
      sessionId,
      transactionId,
      orderData.channel,
      '1.0.0',
      'ProcessOrder',
      'Start',
      orderData.userId,
      orderData.device
    );

    try {
      // Step 1: Validate order
      const validationTimer = new TimeDiff();
      this.logger.app.debug(
        'Validating order data',
        '[VALIDATION]',
        'OrderProcessingService',
        orderData.orderId,
        sessionId,
        transactionId
      );
      
      await this.validateOrder(orderData);
      
      this.logger.app.verbose(
        `Order validation completed in ${validationTimer.diff()}ms`,
        '[VALIDATION]'
      );

      // Step 2: Check inventory
      const inventoryTimer = new TimeDiff();
      this.logger.app.debug(
        'Checking inventory availability',
        '[INVENTORY_CHECK]',
        'OrderProcessingService',
        orderData.orderId,
        sessionId,
        transactionId
      );
      
      const inventory = await this.checkInventory(orderData.items);
      
      this.logger.app.info(
        `Inventory checked: ${logStringify(inventory)}`,
        '[INVENTORY_CHECK]',
        'OrderProcessingService',
        orderData.orderId
      );

      // Step 3: Process payment
      const paymentTimer = new TimeDiff();
      this.logger.app.info(
        'Processing payment',
        '[PAYMENT]',
        'OrderProcessingService',
        orderData.orderId,
        sessionId,
        transactionId
      );
      
      const payment = await this.processPayment(orderData.payment);
      
      this.logger.app.info(
        'Payment processed successfully',
        '[PAYMENT]',
        'OrderProcessingService',
        orderData.orderId
      );

      // Step 4: Create order
      this.logger.app.info(
        'Creating order record',
        '[ORDER_CREATE]',
        'OrderProcessingService',
        orderData.orderId,
        sessionId,
        transactionId
      );
      
      const order = await this.createOrder(orderData, payment);

      // Log final success summary with telemetry
      this.logger.app.summarySuccess(
        'Order processed successfully',
        '200',
        overallTimer.diff(),
        'OrderProcessingService',
        orderData.orderId,
        sessionId,
        transactionId,
        orderData.channel,
        '1.0.0',
        'ProcessOrder',
        'Complete',
        orderData.userId,
        orderData.device,
        order.publicId
      );

      return order;

    } catch (error) {
      // Log detailed error with context
      this.logger.app.error(
        `Order processing failed: ${error.message}`,
        ActionMessage.exception(),
        'OrderProcessingService',
        orderData.orderId,
        sessionId,
        transactionId
      );

      // Log error summary with telemetry
      this.logger.app.summaryError(
        'Order processing failed',
        error.code || '500',
        overallTimer.diff(),
        'OrderProcessingService',
        orderData.orderId,
        sessionId,
        transactionId,
        orderData.channel,
        '1.0.0',
        'ProcessOrder',
        'Failed',
        orderData.userId,
        orderData.device,
        undefined,
        [error.stack]
      );

      throw error;
    }
  }
}

Advanced Logging with AppLogger

BaseAppLogger - Structured Logging

import { AppLogger } from '@eqxjs/nest-logger';

export class PaymentService {
  private readonly logger = new AppLogger('PaymentService', 'PaymentContext');

  processPayment(paymentData: any) {
    // Detailed structured log
    this.logger.app.info(
      'Processing payment',
      '[PAYMENT_PROCESSING]',
      'PaymentService',
      'payment-record-123',
      'session-456',
      'txn-789',
      'mobile-app',
      '1.0.0',
      'ProcessPayment',
      'Validation',
      '[email protected]',
      ['mobile', 'ios'],
      'public-id-123'
    );
  }
}

Summary Logging with Telemetry

import { AppLogger, TimeDiff } from '@eqxjs/nest-logger';

export class OrderService {
  private readonly logger = new AppLogger('OrderService');

  async createOrder(orderData: any) {
    const timer = new TimeDiff();

    try {
      // Process order...
      const result = await this.processOrder(orderData);
      
      // Log success with metrics
      this.logger.app.summarySuccess(
        'Order created successfully',
        '200',
        timer.diff(),
        'OrderService',
        'order-123',
        'session-456',
        'txn-789',
        'web',
        '2.0.0',
        'CreateOrder',
        'Complete'
      );

      return result;
    } catch (error) {
      // Log error with stack trace
      this.logger.app.summaryError(
        'Order creation failed',
        '500',
        timer.diff(),
        'OrderService',
        'order-123',
        'session-456',
        'txn-789',
        'web',
        '2.0.0',
        'CreateOrder',
        'Failed',
        undefined,
        undefined,
        [error.stack]
      );

      throw error;
    }
  }
}

Message Format Loggers (M1, M2, M3)

M1 Message Format

import { AppLogger } from '@eqxjs/nest-logger';
import { DataM1I } from '@eqxjs/nest-logger';

export class KafkaConsumerService {
  private readonly logger = new AppLogger('KafkaConsumer');

  consumeMessage(message: any) {
    const data: DataM1I = {
      header: {
        sessionId: 'session-123',
        transactionId: 'txn-456',
        broker: 'kafka',
        channel: 'queue',
        useCase: 'MessageProcessing',
        identity: {},
      },
      protocol: {
        topic: 'payment.topic',
        command: 'PROCESS',
      },
      body: {
        messageId: message.id,
        payload: message.data,
      },
    };

    // Log with M1 format
    this.logger.loggerM1.info(
      'payment.topic',
      '[CONSUMING]',
      data,
      'Processing payment message'
    );

    // Summary for M1
    this.logger.loggerM1.summarySuccess(
      'payment.topic',
      'Message processed',
      '200',
      150,
      data
    );
  }
}

M2 Message Format

import { AppLogger } from '@eqxjs/nest-logger';
import { DataM2I } from '@eqxjs/nest-logger';

export class HttpService {
  private readonly logger = new AppLogger('HttpService');

  handleRequest(req: any) {
    const data: DataM2I = {
      header: {
        sessionId: req.sessionId,
        transactionId: req.transactionId,
        identity: {},
      },
      protocol: {
        requestId: req.id,
        method: req.method,
        path: req.path,
      },
      body: {
        endpoint: req.path,
        params: req.params,
      },
    };

    this.logger.loggerM2.info(
      'api.endpoint',
      '[HTTP_REQUEST]',
      data,
      'Incoming HTTP request'
    );
  }
}

M3 Message Format

import { AppLogger } from '@eqxjs/nest-logger';
import { DataM3I } from '@eqxjs/nest-logger';

export class DatabaseService {
  private readonly logger = new AppLogger('DatabaseService');

  queryDatabase(query: string) {
    const data: DataM3I = {
      header: {
        sessionId: 'session-123',
        transactionId: 'txn-456',
        identity: {},
      },
      service: {
        serviceName: 'PostgreSQL',
        invoke: 'SELECT',
        name: 'users',
        identity: {},
      },
      body: {
        query: query,
        database: 'users_db',
      },
    };

    this.logger.loggerM3.debug(
      'db.query',
      '[DB_REQUEST]',
      data,
      `Executing query: ${query}`
    );
  }
}

Utility Functions

Time Performance Measurement

import { TimeDiff } from '@eqxjs/nest-logger';

export class PerformanceService {
  async trackOperation() {
    const timer = new TimeDiff();

    // Perform operation
    await this.heavyOperation();

    const duration = timer.diff(); // Returns time in milliseconds
    console.log(`Operation took ${duration}ms`);
  }
}

Date Formatting

import { dateFormat } from '@eqxjs/nest-logger';

const timestamp = dateFormat(); // Returns: "2026-01-12T10:30:00.000Z"

Action Message Constants

import { ActionMessage } from '@eqxjs/nest-logger';

// Use predefined action tags
console.log(ActionMessage.consume());      // [CONSUMING]
console.log(ActionMessage.consumed());     // [CONSUMED]
console.log(ActionMessage.produce());      // [PRODUCING]
console.log(ActionMessage.produced());     // [PRODUCED]
console.log(ActionMessage.httpRequest());  // [HTTP_REQUEST]
console.log(ActionMessage.httpResponse()); // [HTTP_RESPONSE]
console.log(ActionMessage.wsRecv());       // [WS_RECEIVED]
console.log(ActionMessage.wsSent());       // [WS_SENT]
console.log(ActionMessage.dbRequest());    // [DB_REQUEST]
console.log(ActionMessage.dbResponse());   // [DB_RESPONSE]
console.log(ActionMessage.appLogic());     // [APP_LOGIC]
console.log(ActionMessage.inbound());      // [INBOUND]
console.log(ActionMessage.outbound());     // [OUTBOUND]
console.log(ActionMessage.exception());    // [EXCEPTION]

Additional Helper Functions

Log Level Checking

import { isLevelEnable } from '@eqxjs/nest-logger';

process.env.LOG_LEVEL = 'info,error';

if (isLevelEnable('debug')) {
  // This won't execute
  console.log('Debug is enabled');
}

if (isLevelEnable('info')) {
  // This will execute
  console.log('Info is enabled');
}

Message Masking

import { maskMessageReplacer, logStringify } from '@eqxjs/nest-logger';

process.env.LOG_MASK_KEYS = 'password,apiKey,token';

const sensitiveData = {
  username: 'john',
  password: 'secret123',
  apiKey: 'abc-def-ghi',
  email: '[email protected]'
};

// Stringify with automatic masking
const masked = logStringify(sensitiveData);
console.log(masked);
// Output: {"username":"john","password":"*****","apiKey":"*****","email":"[email protected]"}

// Use replacer function directly with JSON.stringify
const manualMask = JSON.stringify(sensitiveData, maskMessageReplacer);

Configuration

Environment Variables

Configure the logger behavior using these environment variables:

| Variable | Type | Default | Description | |----------|------|---------|-------------| | LOG_LEVEL | string | - | Comma-separated list of enabled log levels: debug,info,log,warn,error,verbose | | LOG_MASK_KEYS | string | - | Comma-separated list of field names to mask (e.g., password,token,apiKey,secret) | | APP_VERSION | string | - | Application version included in telemetry and logs | | DESTINATION | string | - | Deployment destination/environment name | | TELEMETRY_IGNORE_CODE | string | - | Comma-separated list of result codes to exclude from telemetry metrics |

Example Configuration:

# .env file
LOG_LEVEL=debug,info,warn,error,verbose
LOG_MASK_KEYS=password,secret,token,apiKey,creditCard,ssn
APP_VERSION=1.0.0
DESTINATION=production
TELEMETRY_IGNORE_CODE=404,401,403

Log Level Filtering

Control which log levels are output by configuring the LOG_LEVEL environment variable:

// Only logs matching the LOG_LEVEL env var will be output
process.env.LOG_LEVEL = 'info,error';

logger.debug('This will NOT be logged');
logger.info('This WILL be logged');
logger.error('This WILL be logged');
logger.verbose('This will NOT be logged');

Sensitive Data Masking

Protect sensitive information by automatically masking specified fields:

// Set masked fields via environment
process.env.LOG_MASK_KEYS = 'password,creditCard,ssn,token';

const userData = {
  username: 'john',
  password: 'secret123',
  creditCard: '4111-1111-1111-1111',
  email: '[email protected]',
  token: 'abc-def-123'
};

logger.log(userData);
// Output: { username: 'john', password: '*****', creditCard: '*****', email: '[email protected]', token: '*****' }

Telemetry Filtering

Exclude specific result codes from telemetry metrics to reduce noise:

// Don't track 404 and 401 errors in telemetry
process.env.TELEMETRY_IGNORE_CODE = '404,401';

// These won't be recorded in OpenTelemetry metrics
logger.app.summaryError('Not found', '404', 100);
logger.app.summaryError('Unauthorized', '401', 50);

// This will be recorded
logger.app.summaryError('Server error', '500', 200);

Message Truncation

Long messages are automatically truncated to prevent log overflow:

// Messages longer than 4096 characters are truncated
const longMessage = 'a'.repeat(5000);
logger.info(longMessage);
// Logged message will be truncated to 4096 chars + '...'

// The limit is defined in DEFAULT_VALUES.MAX_MESSAGE_LENGTH
import { DEFAULT_VALUES } from '@eqxjs/nest-logger';
console.log(DEFAULT_VALUES.MAX_MESSAGE_LENGTH); // 4096

OpenTelemetry Integration

The logger automatically integrates with OpenTelemetry for metrics and tracing:

Metrics Collected

  • Counter: total_request - Total number of operations
  • Histogram: latency - Operation duration in milliseconds

Span Attributes

  • application.code - Result code
  • application.success - Operation success status
  • application.duration - Processing time
  • application.time - Timestamp
  • application.source - Originating service
  • application.stack - Error stack trace (if applicable)

Example with Telemetry

// Summary methods automatically record telemetry
this.logger.app.summarySuccess(
  'Operation completed',
  '200',
  150,  // Service time in ms
  'UserService',
  'record-123'
);

// This will:
// 1. Log the summary
// 2. Increment the counter metric
// 3. Record histogram metric
// 4. Create a span with attributes

API Reference

CustomLogger

Basic logger with standard log levels. Extends Winston logger functionality.

Constructor:

new CustomLogger(context?: string)

Methods:

  • log(message: any, context?: string): void - Info level logging
  • error(message: any, trace?: string, context?: string): void - Error level logging
  • warn(message: any, context?: string): void - Warning level logging
  • debug(message: any, context?: string): void - Debug level logging
  • verbose(message: any, context?: string): void - Verbose level logging

Example:

const logger = new CustomLogger('MyService');
logger.log('Application started');
logger.debug('Debug information');
logger.warn('Warning message');
logger.error('Error occurred', 'stack trace');
logger.verbose('Detailed verbose log');

BaseAppLogger

Advanced logger with structured logging and OpenTelemetry integration.

Constructor:

new BaseAppLogger(appName?: string, context?: string)

All Parameters (for logging methods):

  • message: any - The log message (required)
  • action?: string - Action tag (default: 'none')
  • originateServiceName?: string - Source service (default: 'none')
  • recordName?: string - Record identifier (default: 'none')
  • sessionId?: string - Session ID (default: 'none')
  • transactionId?: string - Transaction ID (default: 'none')
  • channel?: string - Channel identifier (default: 'none')
  • componentVersion?: string - Component version (default: 'none')
  • useCase?: string - Use case name (default: 'none')
  • useCaseStep?: string - Use case step (default: 'none')
  • user?: string - User identifier (default: 'none')
  • device?: string | string[] - Device identifier(s) (default: 'none')
  • public_?: string - Public identifier (default: 'none')
  • opt?: LoggerOpt - Optional configuration object

Logging Methods:

debug(message, action?, ...): void
info(message, action?, ...): void
log(message, action?, ...): void  // Alias for info()
warn(message, action?, ...): void
error(message, action?, ...): void
verbose(message, action?, ...): void

Summary Methods:

summarySuccess(
  appResult?: string,
  appResultCode?: string,
  serviceTime?: number,
  originateServiceName?: string,
  recordName?: string,
  sessionId?: string,
  transactionId?: string,
  channel?: string,
  componentVersion?: string,
  useCase?: string,
  useCaseStep?: string,
  user?: string,
  device?: string | string[],
  public_?: string,
  opt?: LoggerOpt
): LoggerDto

summaryError(
  appResult?: string,
  appResultCode?: string,
  serviceTime?: number,
  originateServiceName?: string,
  recordName?: string,
  sessionId?: string,
  transactionId?: string,
  channel?: string,
  componentVersion?: string,
  useCase?: string,
  useCaseStep?: string,
  user?: string,
  device?: string | string[],
  public_?: string,
  stack?: string[],
  opt?: LoggerOpt
): LoggerDto

Example:

const logger = new BaseAppLogger('MyApp', 'MyContext');

// Simple logging
logger.info('User logged in', '[USER_LOGIN]');

// Full structured logging
logger.info(
  'Payment processed',
  '[PAYMENT_SUCCESS]',
  'PaymentService',
  'pay-123',
  'sess-456',
  'txn-789',
  'mobile',
  '1.0.0',
  'ProcessPayment',
  'Complete',
  '[email protected]',
  ['mobile', 'android'],
  'public-ref-123'
);

// Summary with telemetry
logger.summarySuccess('Success', '200', 150, 'PaymentService', 'pay-123');
logger.summaryError('Failed', '500', 200, 'PaymentService', 'pay-123', undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, ['Error stack']);

AppLogger

Wrapper class providing access to all logger types (app, M1, M2, M3).

Constructor:

new AppLogger(appName?: string, context?: string)

Properties:

  • app: BaseAppLogger - Standard structured logger
  • loggerM1: LoggerFormat - M1 message format logger
  • loggerM2: LoggerFormat - M2 message format logger
  • loggerM3: LoggerFormat - M3 message format logger

Example:

const logger = new AppLogger('MyApp');

// Use standard logger
logger.app.info('Standard log');

// Use M1 format
logger.loggerM1.info('topic', '[ACTION]', dataM1, 'Message');

// Use M2 format
logger.loggerM2.info('topic', '[ACTION]', dataM2, 'Message');

// Use M3 format
logger.loggerM3.info('topic', '[ACTION]', dataM3, 'Message');

LoggerFormat (M1/M2/M3)

Format-specific loggers for different message protocols.

Constructor:

new LoggerFormat(baseAppLogger: BaseAppLogger, messageType: 'M1' | 'M2' | 'M3')

Logging Methods:

debug(topic: string, action: string, data: DataM1I | DataM2I | DataM3I, message?: string, opt?: LoggerOpt): void
info(topic: string, action: string, data: DataM1I | DataM2I | DataM3I, message?: string, opt?: LoggerOpt): void
log(topic: string, action: string, data: DataM1I | DataM2I | DataM3I, message?: string, opt?: LoggerOpt): void
error(topic: string, action: string, data: DataM1I | DataM2I | DataM3I, message?: string, opt?: LoggerOpt): void
warn(topic: string, action: string, data: DataM1I | DataM2I | DataM3I, message?: string, opt?: LoggerOpt): void
verbose(topic: string, action: string, data: DataM1I | DataM2I | DataM3I, message?: string, opt?: LoggerOpt): void

Summary Methods:

summarySuccess(
  topic: string,
  appResult: string,
  appResultCode: string,
  serviceTime: number,
  data: DataM1I | DataM2I | DataM3I,
  opt?: LoggerOpt
): LoggerDto

summaryError(
  topic: string,
  appResult: string,
  appResultCode: string,
  serviceTime: number,
  data: DataM1I | DataM2I | DataM3I,
  stack?: string[],
  opt?: LoggerOpt
): LoggerDto

Example:

import { AppLogger, DataM1I } from '@eqxjs/nest-logger';

const logger = new AppLogger('KafkaService');
const data: DataM1I = {
  header: {
    sessionId: 'sess-123',
    transactionId: 'txn-456',
    broker: 'kafka'
  }
};

// M1 format logging
logger.loggerM1.info('payment.topic', '[CONSUMING]', data, 'Processing message');
logger.loggerM1.summarySuccess('payment.topic', 'Success', '200', 150, data);

Utility Classes and Functions

TimeDiff

Measures time elapsed using high-resolution timer.

Constructor:

new TimeDiff()

Methods:

  • diff(): number - Returns elapsed time in milliseconds

Example:

import { TimeDiff } from '@eqxjs/nest-logger';

const timer = new TimeDiff();
await someAsyncOperation();
const duration = timer.diff();
console.log(`Operation took ${duration}ms`);

ActionMessage

Static class providing predefined action tags for consistent logging.

Static Methods:

  • consume(): string - Returns [CONSUMING]
  • consumed(): string - Returns [CONSUMED]
  • produce(): string - Returns [PRODUCING]
  • produced(): string - Returns [PRODUCED]
  • httpRequest(): string - Returns [HTTP_REQUEST]
  • httpResponse(): string - Returns [HTTP_RESPONSE]
  • wsRecv(): string - Returns [WS_RECEIVED]
  • wsSent(): string - Returns [WS_SENT]
  • dbRequest(): string - Returns [DB_REQUEST]
  • dbResponse(): string - Returns [DB_RESPONSE]
  • appLogic(): string - Returns [APP_LOGIC]
  • inbound(): string - Returns [INBOUND]
  • outbound(): string - Returns [OUTBOUND]
  • exception(): string - Returns [EXCEPTION]

Example:

import { ActionMessage } from '@eqxjs/nest-logger';

logger.info('Consuming message', ActionMessage.consume());
logger.info('Message consumed', ActionMessage.consumed());
logger.info('HTTP request received', ActionMessage.httpRequest());

Helper Functions

dateFormat()

Returns current timestamp in ISO 8601 format.

import { dateFormat } from '@eqxjs/nest-logger';

const timestamp = dateFormat();
console.log(timestamp); // "2026-01-12T10:30:00.000Z"

isLevelEnable(level: string): boolean

Checks if a log level is enabled based on LOG_LEVEL environment variable.

import { isLevelEnable } from '@eqxjs/nest-logger';

process.env.LOG_LEVEL = 'info,error';

if (isLevelEnable('debug')) {
  // Won't execute
}

if (isLevelEnable('info')) {
  // Will execute
}

logStringify(message: any): string

Converts any value to string with automatic sensitive data masking.

import { logStringify } from '@eqxjs/nest-logger';

process.env.LOG_MASK_KEYS = 'password,token';

const data = { username: 'john', password: 'secret' };
const masked = logStringify(data);
console.log(masked); // {"username":"john","password":"*****"}

maskMessageReplacer(key: string, value: any): any

JSON replacer function for masking sensitive fields.

import { maskMessageReplacer } from '@eqxjs/nest-logger';

process.env.LOG_MASK_KEYS = 'apiKey,secret';

const data = { apiKey: 'abc123', name: 'John' };
const masked = JSON.stringify(data, maskMessageReplacer);
console.log(masked); // {"apiKey":"*****","name":"John"}

Interfaces

DataM1I

Message format for broker/queue operations.

interface DataM1I {
  header: DataHeaderI;
}

DataM2I

Message format for HTTP/protocol operations.

interface DataM2I {
  header: DataHeaderI;
  protocol: DataProtocolI;
}

DataM3I

Message format for service-to-service operations.

interface DataM3I {
  header: DataHeaderI;
  service: DataServiceI;
}

DataHeaderI

Common header fields for all message formats.

interface DataHeaderI {
  sessionId?: string;
  transactionId?: string;
  broker?: string;
  channel?: string;
  useCase?: string;
  useCaseStep?: string;
  [key: string]: unknown;
}

LoggerOpt

Optional configuration for logging.

interface LoggerOpt {
  broker?: string;
  [key: string]: unknown;
}

LoggerDto

Data transfer object for log entries.

class LoggerDto {
  level?: string;
  timestamp?: string;
  message?: string;
  action?: string;
  componentName?: string;
  appName?: string;
  // ... and many more fields
}

Best Practices

1. Choose the Right Logger

// For simple applications - use CustomLogger
const logger = new CustomLogger('MyApp');
logger.log('Simple message');

// For structured logging - use BaseAppLogger
const appLogger = new BaseAppLogger('MyApp', 'UserService');
appLogger.info('User action', '[USER_CREATE]');

// For multiple formats - use AppLogger
const multiLogger = new AppLogger('MyApp');
multiLogger.app.info('Standard log');
multiLogger.loggerM1.info('kafka.topic', '[CONSUMING]', data);

2. Use Appropriate Log Levels

Choose the right level based on the situation:

// DEBUG - Detailed diagnostic information (development only)
logger.debug('Request payload:', { userId: 123, action: 'create' });

// INFO - General informational messages (normal operations)
logger.info('User logged in successfully', '[USER_LOGIN]');

// WARN - Potentially harmful situations (recoverable issues)
logger.warn('API rate limit approaching 80%', '[RATE_LIMIT]');

// ERROR - Error events that might still allow the app to continue
logger.error('Failed to send email notification', '[EMAIL_ERROR]');

// VERBOSE - Most detailed information (deep debugging only)
logger.verbose('Internal state:', { state: complexObject });

3. Include Meaningful Context

Always provide context to make logs searchable and traceable:

// ❌ Bad - No context
logger.log('User created');

// ✅ Good - With context
logger.log('User created', 'UserService');

// ✅ Better - Structured with action tag
logger.app.info(
  'User created successfully',
  '[USER_CREATE]',
  'UserService',
  'user-123'  // recordName
);

// ✅ Best - Full metadata for distributed tracing
logger.app.info(
  'User created successfully',
  '[USER_CREATE]',
  'UserService',
  'user-123',      // recordName
  'sess-456',      // sessionId
  'txn-789',       // transactionId
  'mobile',        // channel
  '1.0.0',         // componentVersion
  'CreateUser',    // useCase
  'Complete'       // useCaseStep
);

4. Use Summary Logging for Operations

Track operation duration and outcomes with summary logs:

// ✅ Best Practice - Track operation with telemetry
const timer = new TimeDiff();

try {
  const result = await operation();
  
  // Log success with metrics (automatically creates OpenTelemetry data)
  logger.app.summarySuccess(
    'Operation completed',
    '200',
    timer.diff(),
    'ServiceName',
    'record-123',
    sessionId,
    transactionId
  );
  
  return result;
} catch (error) {
  // Log error with stack trace and metrics
  logger.app.summaryError(
    'Operation failed',
    error.code || '500',
    timer.diff(),
    'ServiceName',
    'record-123',
    sessionId,
    transactionId,
    undefined,
    undefined,
    undefined,
    undefined,
    undefined,
    undefined,
    [error.stack]  // Include stack trace
  );
  
  throw error;
}

5. Leverage Message Formats

Use the appropriate format for your use case:

// ✅ M1 for message broker/queue operations
logger.loggerM1.info(
  'order.created',
  ActionMessage.consume(),
  dataM1,
  'Processing order message'
);

// ✅ M2 for HTTP/protocol operations
logger.loggerM2.info(
  'api.users',
  ActionMessage.httpRequest(),
  dataM2,
  'POST /api/users'
);

// ✅ M3 for database/service operations
logger.loggerM3.debug(
  'db.users',
  ActionMessage.dbRequest(),
  dataM3,
  'SELECT * FROM users WHERE id = ?'
);

6. Use Action Constants

Leverage predefined action tags for consistency:

import { ActionMessage } from '@eqxjs/nest-logger';

// ✅ Use constants instead of strings
logger.info('Message received', ActionMessage.consume());
logger.info('Request sent', ActionMessage.httpRequest());
logger.error('Exception occurred', ActionMessage.exception());

// ❌ Avoid - Manual strings (typos, inconsistency)
logger.info('Message received', '[CONSUMMING]');  // Typo!
logger.info('Request sent', '[HTTP-REQUEST]');    // Wrong format

7. Mask Sensitive Data

Configure masking to protect sensitive information:

// Set up masking in environment
process.env.LOG_MASK_KEYS = 'password,apiKey,token,creditCard,ssn';

// ✅ All sensitive fields will be automatically masked
const user = {
  username: 'john',
  password: 'secret123',  // Will be masked
  email: '[email protected]'
};

logger.info('User data', user);
// Output: { username: 'john', password: '*****', email: '[email protected]' }

8. Configure Environment Variables

Set up proper configuration for each environment:

// Development
process.env.LOG_LEVEL = 'debug,info,log,warn,error,verbose';
process.env.LOG_MASK_KEYS = 'password,token';
process.env.TELEMETRY_IGNORE_CODE = '';

// Production
process.env.LOG_LEVEL = 'info,warn,error';
process.env.LOG_MASK_KEYS = 'password,token,apiKey,secret,creditCard,ssn';
process.env.TELEMETRY_IGNORE_CODE = '404,401,403';
process.env.APP_VERSION = '1.0.0';
process.env.DESTINATION = 'production';

9. Use TimeDiff for Performance Tracking

Measure and log operation performance:

import { TimeDiff } from '@eqxjs/nest-logger';

// ✅ Track operation duration
async function processData() {
  const timer = TimeDiff.start();
  
  // Step 1
  await step1();
  logger.debug(`Step 1 completed in ${timer.diff()}ms`);
  
  // Step 2
  await step2();
  logger.debug(`Step 2 completed in ${timer.diff()}ms`);
  
  // Total time
  const total = timer.end();
  logger.info(`Total processing time: ${total}ms`);
}

10. Handle Errors Consistently

Establish a consistent pattern for error handling:

async function businessOperation() {
  const timer = new TimeDiff();
  const sessionId = 'sess-123';
  const transactionId = 'txn-456';
  
  try {
    logger.app.info(
      'Starting operation',
      ActionMessage.appLogic(),
      'MyService',
      'record-123',
      sessionId,
      transactionId
    );
    
    const result = await performOperation();
    
    logger.app.summarySuccess(
      'Operation successful',
      '200',
      timer.diff(),
      'MyService',
      'record-123',
      sessionId,
      transactionId
    );
    
    return result;
    
  } catch (error) {
    logger.app.error(
      `Operation failed: ${error.message}`,
      ActionMessage.exception(),
      'MyService',
      'record-123',
      sessionId,
      transactionId
    );
    
    logger.app.summaryError(
      'Operation failed',
      error.code || '500',
      timer.diff(),
      'MyService',
      'record-123',
      sessionId,
      transactionId,
      undefined,
      undefined,
      undefined,
      undefined,
      undefined,
      undefined,
      [error.stack]
    );
    
    throw error;
  }
}

Performance Considerations

  • Caching: Logger caches hostname and app version for performance
  • Fast Paths: String messages use optimized fast paths
  • Message Truncation: Long messages automatically truncated to 4096 chars
  • Level Filtering: Disabled log levels have minimal overhead (early return)
  • Lazy Evaluation: Complex operations only performed for enabled levels

Troubleshooting

Logs Not Appearing

Check that the log level is enabled:

process.env.LOG_LEVEL = 'debug,info,warn,error,verbose';
// Make sure the level you're using is in this list

Sensitive Data Still Visible

Ensure masking is configured:

process.env.LOG_MASK_KEYS = 'password,token,apiKey,secret';
// Add all field names that should be masked

Telemetry Not Working

Verify OpenTelemetry is configured in your application and the result codes aren't ignored:

// Check if code is in ignore list
process.env.TELEMETRY_IGNORE_CODE = '404,401';
// These codes won't be tracked in telemetry

TypeScript Type Errors

Import the correct interfaces:

import { 
  DataM1I, 
  DataM2I, 
  DataM3I,
  LoggerOpt 
} from '@eqxjs/nest-logger';

Testing

The module includes comprehensive test coverage (100% lines):

# Run tests
yarn test

# Run tests with coverage
yarn test:cov

# Watch mode
yarn test:watch

Development

Please see docs/README.md for development guidelines.

Contributing

Please see CONTRIBUTING.md for contribution guidelines.

License

ISC

Support

For issues, questions, or contributions, please visit the GitHub repository.

Changelog

See CHANGELOG for release notes and version history.


Made with ❤️ by the EQXJS Team

  • Use M2 for HTTP/protocol-specific operations
  • Use M3 for service-to-service operations
  1. Configure Environment Variables
    • Set LOG_LEVEL in production to reduce log volume
    • Configure LOG_MASK_KEYS to protect sensitive data
    • Use TELEMETRY_IGNORE_CODE to filter noise from metrics

Testing

The module includes comprehensive test coverage (98.32%):

# Run tests
yarn test

# Run tests with coverage
yarn test:cov

# Watch mode
yarn test:watch

Development

Please see docs/README.md for development guidelines.

Contributing

Please see CONTRIBUTING.md for contribution guidelines.

License

ISC

Support

For issues, questions, or contributions, please visit the GitHub repository.

Changelog

See CHANGELOG for release notes and version history.


Made with ❤️ by the EQXJS Team