JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 434
  • Score
    100M100P100Q100662F
  • License ISC

eGain PS logging wrapper utility on Winston

Package Exports

  • ps-chronicle

Readme

ps-chronicle

A robust, extensible logger for Node.js, built on top of Winston. Designed for eGain PS and multi-tenant applications, with support for JSON and simple log formats, custom log levels, and per-request context.

Features

  • Easy-to-use API with TypeScript support
  • Custom log levels: error, wspayload, warn, info, debug
  • Log formats: JSON (default) or simple
  • Per-request context: requestId, customerName, methodName, etc.
  • Extensible: add your own Winston transports
  • Timezone: All timestamps are in GMT
  • Timestamp format: Timestamps are in ISO 8601 (24-hour) format (e.g., 2024-06-01T17:45:23.123Z)
  • Sensitive data redaction: Automatically redacts sensitive fields (e.g., password, token, secret, apiKey, authorization) from log output (case-insensitive, and custom keys are merged with defaults). The redaction string is configurable (default: ***)
  • Performance metrics logging: Easily log operation durations and memory usage
  • Colorized console output: Colorize log levels in the console for easier reading (dev/debug). Note: Only applies to LogFormat.SIMPLE.
  • Structured error logging: Error objects are logged with name, message, stack, status, code, and primitive custom fields; large/nested fields are summarized
  • Dynamic log level adjustment: Change log level at runtime with setLogLevel()

Requirements

  • Node.js 16.0.0 or later is required (uses Object.hasOwn and other modern features)

Installation

npm install ps-chronicle

Dual ESM & CommonJS Usage

This package supports both CommonJS (require) and ESM (import) usage out of the box. Node.js and modern bundlers will automatically use the correct build for your environment.

CommonJS (require)

// In Node.js or legacy projects
const { PsChronicleLogger, LogLevel, LogFormat } = require('ps-chronicle');

ESM (import)

// In ESM projects or with "type": "module"
import { PsChronicleLogger, LogLevel, LogFormat } from 'ps-chronicle';
  • The correct build (CJS or ESM) is chosen automatically by Node.js and bundlers.
  • All features and types are available in both modes.

Usage

1. Import the logger and enums

const { PsChronicleLogger, LogLevel, LogFormat } = require('ps-chronicle');

or (ESM):

import { PsChronicleLogger, LogLevel, LogFormat } from 'ps-chronicle';

2. Initialize your logger

const logger = new PsChronicleLogger({
  fileName: 'myfile.js',           // Optional: will be included in log output
  logLevel: LogLevel.INFO,         // Optional: default is LogLevel.DEBUG
  format: LogFormat.JSON,          // Optional: 'json' (default) or 'simple'
  sensitiveKeys: ['password', 'token', 'secret', 'apiKey', 'authorization'], // Optional: customize redacted fields (merged with defaults, case-insensitive)
  colorize: true,                  // Optional: enable colorized console output (only applies to LogFormat.SIMPLE)
  redactionString: '***'           // Optional: string to use for redacted fields (default: ***)
});
// Note: If you use colorize: true with LogFormat.JSON, colorization will be ignored and a warning will be shown.

3. Set per-request or per-operation context

logger.setRequestId(context.awsRequestId);         // e.g., from AWS Lambda context.awsRequestId
logger.setCustomerName('TMXXXX');                  // Set the customer/tenant name
logger.setMethodName('myFunction()');              // Set the current method name

Note: Global context (customerName, requestId) is set via instance methods. These values are shared across all logger instances.

4. Log messages

logger.log(LogLevel.INFO, 'Informational message', { foo: 'bar' });
logger.log(LogLevel.ERROR, 'Error occurred', { error: new Error('Something went wrong') });

5. Wait for logger (for async/await environments)

If your function is async (e.g., AWS Lambda), ensure all logs are flushed before exit:

await logger.waitForLogger();

Colorized Console Output

You can enable colorized log output in the console for easier reading during development or debugging. This is especially useful with LogFormat.SIMPLE.

Note: The colorize option only applies to LogFormat.SIMPLE. If you use colorize: true with LogFormat.JSON, colorization will be ignored and a warning will be shown.

Example:

const logger = new PsChronicleLogger({
  fileName: 'color-demo.js',
  logLevel: LogLevel.DEBUG,
  format: LogFormat.SIMPLE, // Use SIMPLE for colorized output
  colorize: true
});

logger.log(LogLevel.INFO, 'This is an info message');
logger.log(LogLevel.WARN, 'This is a warning');
logger.log(LogLevel.ERROR, 'This is an error');
logger.log(LogLevel.DEBUG, 'This is a debug message');
  • Colorization only affects console output, not file or JSON logs.
  • Each log level is shown in a different color for quick visual scanning.
  • If you use LogFormat.JSON, colorization is ignored.

Performance Metrics Logging

You can easily log operation durations and memory usage to monitor and optimize your application's performance.

Timing Operations (Manual)

const start = logger.startTimer();
// ... your code ...
logger.logPerformance('DB query', start);

Timing Async Functions (Automatic)

await logger.measurePerformance('fetchData', async () => {
  await fetchData();
});

Logging Memory Usage

logger.logMemoryUsage('After processing');

Use cases:

  • Measure how long a function or operation takes
  • Automatically log duration and errors for async functions
  • Log memory usage at any point in your code

Structured Error Logging

When you log an error object, the logger will automatically serialize it to include only the most relevant fields:

  • name, message, stack, status, code, and any primitive custom fields
  • Large or deeply nested fields (objects/arrays) are summarized as [Object] or [Array]

Example:

try {
  // ...
} catch (err) {
  logger.log(LogLevel.ERROR, 'API call failed', { error: err });
}

Output:

{
  "level": "error",
  "message": "API call failed",
  "xadditionalInfo": {
    "error": {
      "name": "Error",
      "message": "Something went wrong",
      "stack": "...",
      "status": 500,
      "code": "E_API_FAIL",
      "details": "[Object]"
    }
  },
  "timestamp": "2024-06-01T17:45:23.123Z"
}

Dynamic Log Level Adjustment

You can change the log level at runtime for a logger instance:

logger.setLogLevel(LogLevel.ERROR); // Only log errors and above from now on
logger.setLogLevel(LogLevel.DEBUG); // Log everything from debug and above

Sensitive Data Redaction

You can automatically redact sensitive fields (such as password, token, secret, apiKey, authorization) from your log output. By default, these fields are redacted as ***, but you can customize the string using the redactionString option in the constructor. Custom keys are merged with the defaults, and redaction is case-insensitive.

Example:

const logger = new PsChronicleLogger({
  fileName: 'example2.js',
  logLevel: LogLevel.INFO,
  format: LogFormat.JSON,
  sensitiveKeys: ['Authorization', 'PASSWORD'], // Custom keys (case-insensitive, merged with defaults)
  redactionString: '***' // Optional: string to use for redacted fields (default: ***)
});

logger.log(LogLevel.INFO, 'Logging user data', {
  username: 'alice',
  password: 'supersecret',
  token: 'abc123',
  Authorization: 'Bearer xyz',
  profile: {
    apiKey: 'my-api-key',
    nested: { secret: 'hidden', PASSWORD: 'another' }
  }
});

Output:

{
  "level": "info",
  "message": "Logging user data",
  "xadditionalInfo": {
    "username": "alice",
    "password": "***",
    "token": "***",
    "Authorization": "***",
    "profile": {
      "apiKey": "***",
      "nested": { "secret": "***", "PASSWORD": "***" }
    }
  },
  "timestamp": "2024-06-01T17:45:23.123Z"
}
  • The redactionString option is optional. If not provided, the default is ***.
  • The timestamp field is always in ISO 8601 (24-hour) format.

API

Constructor

new PsChronicleLogger(options)
  • options.fileName (string, optional): File name to include in logs
  • options.logLevel (LogLevel, optional): Minimum log level (default: DEBUG)
  • options.format (LogFormat, optional): Log output format (default: JSON)
  • options.transports (array, optional): Custom Winston transports
  • options.sensitiveKeys (array, optional): List of sensitive keys to redact from log output (merged with defaults, case-insensitive)
  • options.colorize (boolean, optional): Enable colorized console output (for development/debugging)
  • options.redactionString (string, optional): String to use for redacted sensitive fields (default: ***)

Methods

  • setRequestId(requestId: string)
  • setCustomerName(customerName: string)
  • setMethodName(methodName: string)
  • log(level: LogLevel, message: string, ...meta: object[])
  • waitForLogger(): Promise<void>
  • startTimer(): number — Start a timer for performance measurement
  • logPerformance(operation: string, startTime: number, extraMeta?: object) — Log the duration of an operation
  • measurePerformance(operation: string, fn: () => Promise<T>, extraMeta?: object) — Measure and log the duration of an async function
  • logMemoryUsage(label?: string) — Log current memory usage
  • setLogLevel(level: LogLevel) — Dynamically change the log level for this logger instance
  • getLogLevel(): LogLevel — Get the current log level
  • isLevelEnabled(level: LogLevel): boolean — Check if a log level is enabled
  • getMethodName(): string — Get the current method name
  • getCustomerName(): string | undefined — Get the global customer name
  • getRequestId(): string | undefined — Get the global request ID

Enums

  • LogLevel: ERROR, WS_PAYLOAD, WARN, INFO, DEBUG
  • LogFormat: JSON, SIMPLE

Deferring Expensive Work

If you have expensive computations for log metadata, you can avoid unnecessary work by checking if a log level is enabled before building the log message. Use the isLevelEnabled(level) method:

if (logger.isLevelEnabled(LogLevel.DEBUG)) {
  logger.log(LogLevel.DEBUG, 'Debug info', expensiveMeta());
}

This ensures that expensive work is only performed if the log will actually be emitted at the current log level.


Global Context: customerName and requestId

You can set customerName and requestId globally for all logger instances using the instance methods:

logger.setCustomerName('AcmeCorp');
logger.setRequestId('req-123');
  • These values will be included in all logs from any logger instance.
  • You only need to set them once (e.g., at app startup or per request).
  • The instance methods update global/static values shared by all logger instances.

Logging Metadata: Objects, Strings, and More

The log method always places additional metadata under the xadditionalInfo key. The structure depends on what you pass:

  • If you pass a single object, its fields are included under xadditionalInfo:
    logger.log(LogLevel.INFO, 'User info', { userId: 123, name: 'Alice' });
    // { ..., "xadditionalInfo": { "userId": 123, "name": "Alice" } }
  • If you pass a single string, number, or boolean, it is logged as { '0': [value] } under xadditionalInfo:
    logger.log(LogLevel.INFO, 'Document retrieved', 'Document retrieved successfully');
    // { ..., "xadditionalInfo": { "0": ["Document retrieved successfully"] } }
  • If you pass multiple non-object values, they are logged as { '0': [array of values] } under xadditionalInfo:
    logger.log(LogLevel.INFO, 'IDs', 'a', 'b', 'c');
    // { ..., "xadditionalInfo": { "0": ["a", "b", "c"] } }
  • If you pass both objects and non-objects, the objects are merged and the non-objects are included in a 0 field under xadditionalInfo:
    logger.log(LogLevel.INFO, 'User info', { userId: 123 }, 'extra1', 42);
    // { ..., "xadditionalInfo": { "userId": 123, "0": ["extra1", 42] } }

This makes it easy to log any kind of metadata without worrying about how to wrap your values.


License

ISC