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



โจ 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
``bashUsing yarn
yarn add @eqxjs/nest-logger
Requirements:
- Node.js >= 22
- NestJS >= 11
Feature Overview
$3
| 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 |
$3
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
$3
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
$3
`typescript
import { Module } from '@nestjs/common';
import { CustomLoggerModule } from '@eqxjs/nest-logger';@Module({
imports: [CustomLoggerModule],
})
export class AppModule {}
`$3
`typescript
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:
$3
Required Properties:
-
header: DataHeaderI - Message header with identity
- protocol: DataProtocolI - Protocol-specific information
- body: Record - Message payload `typescript
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
}
};
`$3
Required Properties:
-
header: DataHeaderI - Message header with identity
- body: Record - Message payloadOptional Properties:
-
protocol: DataProtocolI - HTTP/protocol information`typescript
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'
}
};
`$3
Required Properties:
-
service: DataServiceI - Service-specific information with identity
- body: Record - Message payloadOptional Properties:
-
header: DataHeaderI - Message header with identity`typescript
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
}
};
`$3
All message types include an
identity object with optional fields:`typescript
identity: {
device?: string | string[]; // Device identifier(s)
public?: string; // Public identifier
user?: string; // User identifier
}
`Usage Examples
$3
#### CustomLogger - Simple Logging
`typescript
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');
}
}
`$3
#### Example 1: HTTP Request/Response Logging
`typescript
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
`typescript
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
`typescript
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
`typescript
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
`typescript
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;
}
}
}
`$3
#### BaseAppLogger - Structured Logging
`typescript
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',
'user@example.com',
['mobile', 'ios'],
'public-id-123'
);
}
}
`#### Summary Logging with Telemetry
`typescript
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;
}
}
}
`$3
#### M1 Message Format
`typescript
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
`typescript
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
`typescript
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}
);
}
}
`$3
#### Time Performance Measurement
`typescript
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
`typescript
import { dateFormat } from '@eqxjs/nest-logger';const timestamp = dateFormat(); // Returns: "2026-01-12T10:30:00.000Z"
`#### Action Message Constants
`typescript
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
`typescript
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
`typescript
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: 'john@example.com'
};
// Stringify with automatic masking
const masked = logStringify(sensitiveData);
console.log(masked);
// Output: {"username":"john","password":"","apiKey":"","email":"john@example.com"}
// Use replacer function directly with JSON.stringify
const manualMask = JSON.stringify(sensitiveData, maskMessageReplacer);
`Configuration
$3
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:
`bash
.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
`$3
Control which log levels are output by configuring the
LOG_LEVEL environment variable:`typescript
// 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');
`$3
Protect sensitive information by automatically masking specified fields:
`typescript
// 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: 'john@example.com',
token: 'abc-def-123'
};
logger.log(userData);
// Output: { username: 'john', password: '', creditCard: '', email: 'john@example.com', token: '*' }
`$3
Exclude specific result codes from telemetry metrics to reduce noise:
`typescript
// 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);
`$3
Long messages are automatically truncated to prevent log overflow:
`typescript
// 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:
$3
- Counter:
total_request - Total number of operations
- Histogram: latency - Operation duration in milliseconds$3
-
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)$3
`typescript
// 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
$3
Basic logger with standard log levels. Extends Winston logger functionality.
Constructor:
`typescript
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 loggingExample:
`typescript
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');
`$3
Advanced logger with structured logging and OpenTelemetry integration.
Constructor:
`typescript
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 objectLogging Methods:
`typescript
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:
`typescript
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
): LoggerDtosummaryError(
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:
`typescript
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',
'user@example.com',
['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']);
`$3
Wrapper class providing access to all logger types (app, M1, M2, M3).
Constructor:
`typescript
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 loggerExample:
`typescript
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');
`$3
Format-specific loggers for different message protocols.
Constructor:
`typescript
new LoggerFormat(baseAppLogger: BaseAppLogger, messageType: 'M1' | 'M2' | 'M3')
`Logging Methods:
`typescript
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:
`typescript
summarySuccess(
topic: string,
appResult: string,
appResultCode: string,
serviceTime: number,
data: DataM1I | DataM2I | DataM3I,
opt?: LoggerOpt
): LoggerDtosummaryError(
topic: string,
appResult: string,
appResultCode: string,
serviceTime: number,
data: DataM1I | DataM2I | DataM3I,
stack?: string[],
opt?: LoggerOpt
): LoggerDto
`Example:
`typescript
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);
`$3
#### TimeDiff
Measures time elapsed using high-resolution timer.
Constructor:
`typescript
new TimeDiff()
`Methods:
-
diff(): number - Returns elapsed time in millisecondsExample:
`typescript
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:
`typescript
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.
`typescript
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.`typescript
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.
`typescript
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.
`typescript
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"}
`$3
#### DataM1I
Message format for broker/queue operations.
`typescript
interface DataM1I {
header: DataHeaderI;
}
`#### DataM2I
Message format for HTTP/protocol operations.
`typescript
interface DataM2I {
header: DataHeaderI;
protocol: DataProtocolI;
}
`#### DataM3I
Message format for service-to-service operations.
`typescript
interface DataM3I {
header: DataHeaderI;
service: DataServiceI;
}
`#### DataHeaderI
Common header fields for all message formats.
`typescript
interface DataHeaderI {
sessionId?: string;
transactionId?: string;
broker?: string;
channel?: string;
useCase?: string;
useCaseStep?: string;
[key: string]: unknown;
}
`#### LoggerOpt
Optional configuration for logging.
`typescript
interface LoggerOpt {
broker?: string;
[key: string]: unknown;
}
`#### LoggerDto
Data transfer object for log entries.
`typescript
class LoggerDto {
level?: string;
timestamp?: string;
message?: string;
action?: string;
componentName?: string;
appName?: string;
// ... and many more fields
}
`Best Practices
$3
`typescript
// 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);
`$3
Choose the right level based on the situation:
`typescript
// 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
Always provide context to make logs searchable and traceable:
`typescript
// โ 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
);
`$3
Track operation duration and outcomes with summary logs:
`typescript
// โ
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;
}
`$3
Use the appropriate format for your use case:
`typescript
// โ
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 = ?'
);
`$3
Leverage predefined action tags for consistency:
`typescript
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
`$3
Configure masking to protect sensitive information:
`typescript
// 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: 'john@example.com'
};
logger.info('User data', user);
// Output: { username: 'john', password: '*', email: 'john@example.com' }
`$3
Set up proper configuration for each environment:
`typescript
// 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';
`$3
Measure and log operation performance:
`typescript
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);
}
`$3
Establish a consistent pattern for error handling:
`typescript
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
$3
Check that the log level is enabled:
`typescript
process.env.LOG_LEVEL = 'debug,info,warn,error,verbose';
// Make sure the level you're using is in this list
`$3
Ensure masking is configured:
`typescript
process.env.LOG_MASK_KEYS = 'password,token,apiKey,secret';
// Add all field names that should be masked
`$3
Verify OpenTelemetry is configured in your application and the result codes aren't ignored:
`typescript
// Check if code is in ignore list
process.env.TELEMETRY_IGNORE_CODE = '404,401';
// These codes won't be tracked in telemetry
`$3
Import the correct interfaces:
`typescript
import {
DataM1I,
DataM2I,
DataM3I,
LoggerOpt
} from '@eqxjs/nest-logger';
`Testing
The module includes comprehensive test coverage (100% lines):
`bash
Run tests
yarn testRun tests with coverage
yarn test:covWatch 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
5. 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 metricsTesting
The module includes comprehensive test coverage (98.32%):
`bash
Run tests
yarn testRun tests with coverage
yarn test:covWatch mode
yarn test:watch
``Please see docs/README.md for development guidelines.
Please see CONTRIBUTING.md for contribution guidelines.
ISC
For issues, questions, or contributions, please visit the GitHub repository.
See CHANGELOG for release notes and version history.
---
Made with โค๏ธ by the EQXJS Team