@eqxjs/nest-logger
v3.1.0
Published
NestJS logger module for EQXJS
Keywords
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.
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-loggerRequirements:
- 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 identityprotocol: DataProtocolI - Protocol-specific informationbody: 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 identitybody: 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 identitybody: 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,403Log 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); // 4096OpenTelemetry 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 codeapplication.success- Operation success statusapplication.duration- Processing timeapplication.time- Timestampapplication.source- Originating serviceapplication.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 attributesAPI 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 loggingerror(message: any, trace?: string, context?: string): void- Error level loggingwarn(message: any, context?: string): void- Warning level loggingdebug(message: any, context?: string): void- Debug level loggingverbose(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?, ...): voidSummary 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
): LoggerDtoExample:
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 loggerloggerM1: LoggerFormat- M1 message format loggerloggerM2: LoggerFormat- M2 message format loggerloggerM3: 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): voidSummary 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
): LoggerDtoExample:
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 format7. 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 listSensitive Data Still Visible
Ensure masking is configured:
process.env.LOG_MASK_KEYS = 'password,token,apiKey,secret';
// Add all field names that should be maskedTelemetry 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 telemetryTypeScript 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:watchDevelopment
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
- Configure Environment Variables
- Set
LOG_LEVELin production to reduce log volume - Configure
LOG_MASK_KEYSto protect sensitive data - Use
TELEMETRY_IGNORE_CODEto filter noise from metrics
- Set
Testing
The module includes comprehensive test coverage (98.32%):
# Run tests
yarn test
# Run tests with coverage
yarn test:cov
# Watch mode
yarn test:watchDevelopment
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
