Logger
Winston-based logging with scoped prefixes, multiple transports (console, daily-rotating files, UDP), and a zero-allocation high-frequency logger for performance-critical paths.
Quick Reference
| Class | Extends | Use Case |
|---|---|---|
Logger | -- | General-purpose scoped logger with caching |
LoggerFactory | -- | Factory that builds Logger instances from scope arrays |
HfLogger | -- | Zero-allocation ring-buffer logger for hot paths (~100-300ns) |
HfLogFlusher | -- | Background flusher for HfLogger entries |
DgramTransport | winston-transport.Transport | Custom Winston transport that sends logs over UDP |
Import Paths
// Core classes
import { Logger, LoggerFactory, ApplicationLogger } from '@venizia/ignis-helpers';
// High-frequency logger
import { HfLogger, HfLogFlusher } from '@venizia/ignis-helpers';
// Constants & types
import { LogLevels, LoggerFormats } from '@venizia/ignis-helpers';
import type { TLogLevel, TLoggerFormat } from '@venizia/ignis-helpers';
// Custom logger utilities
import {
defineCustomLogger,
defineLogFormatter,
defineJsonLoggerFormatter,
definePrettyLoggerFormatter,
applicationLogFormatter,
applicationLogger,
} from '@venizia/ignis-helpers';
import type { IFileTransportOptions, ICustomLoggerOptions } from '@venizia/ignis-helpers';
// UDP transport
import { DgramTransport } from '@venizia/ignis-helpers';
import type { IDgramTransportOptions } from '@venizia/ignis-helpers';Creating an Instance
Using LoggerFactory (Recommended)
LoggerFactory.getLogger accepts an array of scope strings, joins them with -, and returns a cached Logger instance.
import { LoggerFactory } from '@venizia/ignis-helpers';
const logger = LoggerFactory.getLogger(['MyService']);
logger.info('Service initialized');
// Output: [MyService] Service initialized
const scopedLogger = LoggerFactory.getLogger(['Payment', 'Stripe']);
scopedLogger.info('Charge created');
// Output: [Payment-Stripe] Charge createdTIP
LoggerFactory is how BaseHelper creates its internal logger. Every helper in the framework gets a scoped logger automatically through this path.
Using Logger.get() Directly
import { Logger } from '@venizia/ignis-helpers';
const logger = Logger.get('MyService');
logger.info('Direct logger access');
// Output: [MyService] Direct logger accessPass a custom Winston logger instance as the second parameter to use your own transport configuration:
import { Logger, defineCustomLogger, applicationLogFormatter } from '@venizia/ignis-helpers';
const customWinstonLogger = defineCustomLogger({
loggerFormatter: applicationLogFormatter,
transports: {
info: { file: { prefix: 'custom', folder: './logs' } },
error: { file: { prefix: 'custom-error', folder: './logs' } },
},
});
const logger = Logger.get('MyService', customWinstonLogger);Custom loggers are cached under a separate key (scope:custom), so a default and custom logger for the same scope can coexist.
Logger Caching
Both methods use internal caching -- the same scope always returns the same Logger instance:
const logger1 = Logger.get('MyService');
const logger2 = Logger.get('MyService');
// logger1 === logger2 (same instance)ApplicationLogger Alias
ApplicationLogger is exported as both a value and a type alias for Logger, providing backward compatibility:
import { ApplicationLogger } from '@venizia/ignis-helpers';
const logger = ApplicationLogger.get('MyService');Usage
Log Levels
The Logger class exposes direct methods for info, warn, error, emerg, and debug. Other levels (alert, http, verbose, silly) are accessible through the generic log() method.
logger.info('User created');
logger.warn('Rate limit approaching');
logger.error('Failed to process payment');
logger.emerg('System out of memory');
logger.debug('Query took 12ms'); // Requires DEBUG=true
logger.log('alert', 'Threshold exceeded'); // Generic method for any levelThe LogLevels class defines all available levels and provides validation:
import { LogLevels } from '@venizia/ignis-helpers';
import type { TLogLevel } from '@venizia/ignis-helpers';
LogLevels.ERROR; // 'error'
LogLevels.ALERT; // 'alert'
LogLevels.EMERG; // 'emerg'
LogLevels.WARN; // 'warn'
LogLevels.INFO; // 'info'
LogLevels.HTTP; // 'http'
LogLevels.VERBOSE; // 'verbose'
LogLevels.DEBUG; // 'debug'
LogLevels.SILLY; // 'silly'
LogLevels.isValid('info'); // true
LogLevels.isValid('unknown'); // false
const level: TLogLevel = 'info';Winston Level Priority
The defineCustomLogger function configures Winston with these numeric priorities by default:
| Level | Priority | Color |
|---|---|---|
error | 0 | red |
alert | 0 | red |
emerg | 0 | red |
warn | 1 | yellow |
info | 2 | green |
http | 3 | magenta |
verbose | 4 | gray |
debug | 5 | blue |
silly | 6 | gray |
Lower numeric values have higher priority. error, alert, and emerg share priority 0.
Method-Scoped Logging
The .for() method creates a sub-scoped logger for specific methods, appending the method name to the scope with a - separator. The resulting logger is also cached.
class UserService {
private logger = LoggerFactory.getLogger(['UserService']);
async createUser(data: CreateUserDto) {
this.logger.for('createUser').info('Creating user: %j', data);
// Output: [UserService-createUser] Creating user: {...}
try {
const user = await this.userRepo.create({ data });
this.logger.for('createUser').info('User created: %s', user.id);
return user;
} catch (error) {
this.logger.for('createUser').error('Failed to create user: %s', error);
throw error;
}
}
}Log Formats
The logger supports two output formats, controlled by the APP_ENV_LOGGER_FORMAT environment variable (default: text).
The LoggerFormats class provides constants and validation:
import { LoggerFormats } from '@venizia/ignis-helpers';
import type { TLoggerFormat } from '@venizia/ignis-helpers';
LoggerFormats.JSON; // 'json'
LoggerFormats.TEXT; // 'text'
LoggerFormats.isValid('json'); // true
const fmt: TLoggerFormat = 'text';JSON Format
APP_ENV_LOGGER_FORMAT=jsonOutput:
{"level":"info","message":"[UserService] User created","timestamp":"2024-01-11T10:30:00.000Z","label":"APP"}Pretty Text Format (Default)
APP_ENV_LOGGER_FORMAT=textOutput:
2024-01-11T10:30:00.000Z [APP] info: [UserService] User createdNOTE
The label shown in log output (e.g. APP) comes from APP_ENV_APPLICATION_NAME (defaults to 'APP'). Set this env var to customize the label for your application.
Custom Formatters
Build formatters directly using the exported helper functions:
import {
defineLogFormatter,
defineJsonLoggerFormatter,
definePrettyLoggerFormatter,
} from '@venizia/ignis-helpers';
// Auto-detect from APP_ENV_LOGGER_FORMAT (or override with format option)
const formatter = defineLogFormatter({ label: 'my-app' });
const jsonFmt = defineLogFormatter({ label: 'my-app', format: 'json' });
// Or use specific formatters directly
const jsonFormatter = defineJsonLoggerFormatter({ label: 'my-app' });
const prettyFormatter = definePrettyLoggerFormatter({ label: 'my-app' });Transports
Every logger created by defineCustomLogger always includes a Console transport at the debug level. File and UDP transports are optional.
File Rotation (DailyRotateFile)
Configure file rotation through environment variables or programmatically via IFileTransportOptions.
Environment variables:
| Variable | Default | Description |
|---|---|---|
APP_ENV_LOGGER_FOLDER_PATH | ./ | Log files directory |
APP_ENV_LOGGER_FILE_FREQUENCY | 1h | Rotation frequency |
APP_ENV_LOGGER_FILE_MAX_SIZE | 100m | Max file size before rotation |
APP_ENV_LOGGER_FILE_MAX_FILES | 5d | Retention period |
APP_ENV_LOGGER_FILE_DATE_PATTERN | YYYYMMDD_HH | Date pattern in filename |
Programmatic configuration:
import { defineCustomLogger, applicationLogFormatter } from '@venizia/ignis-helpers';
const customLogger = defineCustomLogger({
loggerFormatter: applicationLogFormatter,
transports: {
info: {
file: {
prefix: 'my-app',
folder: './logs',
frequency: '24h',
maxSize: '500m',
maxFiles: '30d',
datePattern: 'YYYYMMDD',
},
},
error: {
file: {
prefix: 'my-app-error',
folder: './logs',
maxFiles: '90d',
},
},
},
});Generated filename pattern: {folder}/{prefix}-info-{DATE}.log or {folder}/{prefix}-error-{DATE}.log.
IFileTransportOptions
interface IFileTransportOptions {
prefix: string; // Filename prefix (required)
folder: string; // Output directory (required)
frequency?: string; // Rotation frequency (default: '1h')
maxSize?: string; // Max file size (default: '100m')
maxFiles?: string; // Retention period (default: '5d')
datePattern?: string; // Date pattern in filename (default: 'YYYYMMDD_HH')
}UDP Transport (DgramTransport)
The DgramTransport is a custom Winston transport that sends log entries over UDP. It supports level-based filtering -- only messages matching the configured levels set are forwarded.
import { DgramTransport } from '@venizia/ignis-helpers';
const transport = new DgramTransport({
label: 'my-app',
host: '127.0.0.1',
port: 5000,
levels: ['error', 'warn', 'info'],
socketOptions: { type: 'udp4' },
});Static factory with validation -- returns null if any required field is missing:
const transport = DgramTransport.fromPartial({
label: 'my-app',
host: '127.0.0.1',
port: 5000,
levels: ['error', 'warn'],
socketOptions: { type: 'udp4' },
});
// Returns null if label, host, port, levels (non-empty), or socketOptions is missingThe transport automatically re-establishes the UDP socket if it encounters an error.
Environment variables for the default application logger:
| Variable | Description |
|---|---|
APP_ENV_LOGGER_DGRAM_HOST | UDP log aggregator host |
APP_ENV_LOGGER_DGRAM_PORT | UDP log aggregator port |
APP_ENV_LOGGER_DGRAM_LABEL | Label to identify log source |
APP_ENV_LOGGER_DGRAM_LEVELS | Comma-separated levels to send via UDP |
IDgramTransportOptions
interface IDgramTransportOptions extends Transport.TransportStreamOptions {
label: string; // Label to identify log source
host: string; // UDP host
port: number; // UDP port
levels: Array<string>; // Levels to forward over UDP
socketOptions: dgram.SocketOptions; // Node.js dgram socket options
}ICustomLoggerOptions
interface ICustomLoggerOptions {
logLevels?: { [name: string | symbol]: number };
logColors?: { [name: string | symbol]: string };
loggerFormatter?: ReturnType<typeof winston.format.combine>;
transports: {
info: {
file?: IFileTransportOptions;
dgram?: Partial<IDgramTransportOptions>;
};
error: {
file?: IFileTransportOptions;
dgram?: Partial<IDgramTransportOptions>;
};
};
}Both info and error transport groups support optional file (DailyRotateFile) and dgram (UDP) transports. A console transport is always included. Error file transports are also registered as exception handlers.
Debug Logging Behavior
Debug logs require both conditions to be met:
DEBUG=trueenvironment variable is set (parsed viatoBoolean)NODE_ENVis either unset or is present in theEnvironment.COMMON_ENVSset
The COMMON_ENVS set includes: local, debug, development, alpha, beta, staging, production. You can extend this set with APP_ENV_EXTRA_LOG_ENVS:
DEBUG=true
NODE_ENV=development
APP_ENV_EXTRA_LOG_ENVS=qa,preview # Comma-separated additional environmentsIMPORTANT
The debug flag check is pre-computed at module load time. Changing DEBUG or NODE_ENV at runtime has no effect -- the values are captured once when the module is first imported.
High-Frequency Logger (HfLogger)
For performance-critical applications (e.g., HFT systems, game servers), HfLogger provides zero-allocation logging via a lock-free ring buffer backed by SharedArrayBuffer.
import { HfLogger, HfLogFlusher } from '@venizia/ignis-helpers';
// At initialization time (once):
const logger = HfLogger.get('OrderEngine');
const MSG_ORDER_SENT = HfLogger.encodeMessage('Order sent');
const MSG_ORDER_FILLED = HfLogger.encodeMessage('Order filled');
// Start background flusher
const flusher = new HfLogFlusher();
flusher.start(100); // Flush every 100ms
// In hot path (~100-300ns, zero allocation):
logger.log('info', MSG_ORDER_SENT);
logger.log('info', MSG_ORDER_FILLED);HfLogger API
| Method | Signature | Description |
|---|---|---|
HfLogger.get | (scope: string) => HfLogger | Get or create a cached logger instance |
HfLogger.encodeMessage | (msg: string) => Uint8Array | Pre-encode a message string to bytes (cached) |
logger.log | (level: THfLogLevel, messageBytes: Uint8Array) => void | Write entry to ring buffer |
Supported levels: debug (0), info (1), warn (2), error (3), emerg (4).
HfLogFlusher API
| Method | Signature | Description |
|---|---|---|
flusher.flush | () => Promise<void> | Flush all buffered entries to output |
flusher.start | (intervalMs?: number) => void | Start background flush loop (default: 100ms) |
Ring Buffer Entry Format
Each entry occupies exactly 256 bytes in a 64K-entry (16MB) SharedArrayBuffer:
| Offset | Size | Field |
|---|---|---|
| 0-7 | 8 bytes | Timestamp (BigInt64, nanosecond precision) |
| 8 | 1 byte | Level (0=debug, 1=info, 2=warn, 3=error, 4=emerg) |
| 9-40 | 32 bytes | Scope (fixed-width, padded) |
| 41-255 | 215 bytes | Message (fixed-width, truncated if longer) |
The buffer wraps around at 65,536 entries using bitwise AND masking (writeIndex & (BUFFER_SIZE - 1)).
WARNING
Pre-encode messages at initialization time using HfLogger.encodeMessage(). Calling it in the hot path defeats the zero-allocation purpose because it triggers string encoding on every log call.
Environment Variables
Core Configuration
| Variable | Default | Description |
|---|---|---|
APP_ENV_APPLICATION_NAME | APP | Label prefix shown in all log output |
DEBUG | false | Enable debug-level logging |
NODE_ENV | (unset) | Must be in COMMON_ENVS or unset for debug to activate |
APP_ENV_EXTRA_LOG_ENVS | (empty) | Comma-separated additional environments to allow debug |
APP_ENV_LOGGER_FORMAT | text | Output format (json or text) |
APP_ENV_LOGGER_FOLDER_PATH | ./ | Log files directory |
File Rotation
| Variable | Default | Description |
|---|---|---|
APP_ENV_LOGGER_FILE_FREQUENCY | 1h | Rotation frequency |
APP_ENV_LOGGER_FILE_MAX_SIZE | 100m | Max file size before rotation |
APP_ENV_LOGGER_FILE_MAX_FILES | 5d | Retention period |
APP_ENV_LOGGER_FILE_DATE_PATTERN | YYYYMMDD_HH | Date pattern in filename |
UDP Transport
| Variable | Description |
|---|---|
APP_ENV_LOGGER_DGRAM_HOST | UDP log aggregator host |
APP_ENV_LOGGER_DGRAM_PORT | UDP log aggregator port |
APP_ENV_LOGGER_DGRAM_LABEL | Label to identify log source |
APP_ENV_LOGGER_DGRAM_LEVELS | Comma-separated levels to send via UDP |
Example .env
# Application
APP_ENV_APPLICATION_NAME=my-service
# Core
DEBUG=true
APP_ENV_LOGGER_FORMAT=json
APP_ENV_LOGGER_FOLDER_PATH=./app_data/logs
# File rotation
APP_ENV_LOGGER_FILE_FREQUENCY=24h
APP_ENV_LOGGER_FILE_MAX_SIZE=500m
APP_ENV_LOGGER_FILE_MAX_FILES=30d
# UDP transport
APP_ENV_LOGGER_DGRAM_HOST=127.0.0.1
APP_ENV_LOGGER_DGRAM_PORT=5000
APP_ENV_LOGGER_DGRAM_LABEL=my-app
APP_ENV_LOGGER_DGRAM_LEVELS=error,warn,infoAPI Summary
| Export | Kind | Description |
|---|---|---|
Logger | class | Scoped logger with caching, wraps a Winston logger instance |
ApplicationLogger | value + type alias | Backward-compatible alias for Logger |
LoggerFactory | class | Factory that creates Logger from scope arrays |
HfLogger | class | Zero-allocation ring-buffer logger |
HfLogFlusher | class | Background flusher for HfLogger |
LogLevels | class (constants) | Log level constants (ERROR, ALERT, EMERG, WARN, INFO, HTTP, VERBOSE, DEBUG, SILLY) with isValid() |
LoggerFormats | class (constants) | Format constants (JSON, TEXT) with isValid() |
defineCustomLogger | (opts: ICustomLoggerOptions) => winston.Logger | Create a fully configured Winston logger |
defineLogFormatter | (opts: { label: string; format?: TLoggerFormat }) => winston.Logform.Format | Create a formatter (auto-detects format from env) |
defineJsonLoggerFormatter | (opts: { label: string }) => winston.Logform.Format | Create a JSON formatter |
definePrettyLoggerFormatter | (opts: { label: string }) => winston.Logform.Format | Create a pretty text formatter |
applicationLogFormatter | winston.Logform.Format | Pre-built formatter using APP_ENV_APPLICATION_NAME label |
applicationLogger | winston.Logger | Pre-built default Winston logger instance |
DgramTransport | class | Custom Winston transport for UDP logging |
TLogLevel | type | Union of all log level string literals |
TLoggerFormat | type | Union of 'json' | 'text' |
IFileTransportOptions | interface | Options for daily-rotating file transport |
ICustomLoggerOptions | interface | Options for defineCustomLogger |
IDgramTransportOptions | interface | Options for DgramTransport |
Troubleshooting
Debug logs not appearing
Cause: Debug logging requires both DEBUG=true AND a NODE_ENV that is either unset or present in the COMMON_ENVS set. These values are pre-computed at module load time.
Fix:
- Verify
DEBUG=trueis set in your environment. - Verify
NODE_ENVis set to one of:local,debug,development,alpha,beta,staging,production-- or is unset entirely. - If you use a custom environment name (e.g.
qa), add it toAPP_ENV_EXTRA_LOG_ENVS=qa.
DEBUG=true NODE_ENV=development bun run server:dev"[defineLogger] Invalid logger format | format: {format} | valids: json,text"
Cause: The format option passed to defineLogFormatter (or the APP_ENV_LOGGER_FORMAT environment variable) is not json or text.
Fix: Set APP_ENV_LOGGER_FORMAT to either json or text:
APP_ENV_LOGGER_FORMAT=textUDP transport not sending logs
Cause: DgramTransport.fromPartial() returns null if any required option is missing (label, host, port, levels with at least one entry, or socketOptions). The transport is silently not registered.
Fix:
- Ensure all four dgram env vars are set:
APP_ENV_LOGGER_DGRAM_HOST,APP_ENV_LOGGER_DGRAM_PORT,APP_ENV_LOGGER_DGRAM_LABEL, andAPP_ENV_LOGGER_DGRAM_LEVELS. APP_ENV_LOGGER_DGRAM_LEVELSmust contain at least one level (e.g.error,warn,info). An empty value results in no transport.- Verify the UDP aggregator is reachable from your host (firewall, port binding).
Log label shows "APP" instead of application name
Cause: The default label comes from Defaults.APPLICATION_NAME, which reads APP_ENV_APPLICATION_NAME. If the env var is not set, it falls back to 'APP'.
Fix: Set APP_ENV_APPLICATION_NAME in your environment:
APP_ENV_APPLICATION_NAME=my-serviceSee Also
Related Concepts:
- Services -- Logging in services
- Controllers -- Logging in controllers
Other Helpers:
- Helpers Index -- All available helpers
References:
- Request Tracker Component -- Request logging
External Resources:
- Winston Documentation -- Winston logging library
- winston-daily-rotate-file -- File rotation transport