Changelog - 2026-01-11
Logger Optimization & High-Frequency Logger
This release focuses on logging performance improvements, including optimized caching, method-scoped logging with .for(), and a new zero-allocation HfLogger for high-frequency trading and performance-critical applications.
Overview
- Logger Optimization: Pre-computed formatting, cached instances, direct Winston method calls
- Method-Scoped Logging: New
.for()method for contextual logging - HfLogger: Zero-allocation logger using SharedArrayBuffer for ~100-300ns latency
- Configurable File Rotation: Environment variables for file rotation settings
- JSON/Text Formats: Configurable output format via environment variable
New Features
Method-Scoped Logging with .for()
File: packages/helpers/src/helpers/logger/application-logger.ts
Problem: Tracing logs to specific methods required manual prefix formatting.
Solution: The .for() method creates sub-scoped loggers automatically.
class UserService {
private logger = Logger.get('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: %s', error);
throw error;
}
}
}Benefits:
- Clear method context in log output
- Cached sub-loggers for performance
- Consistent logging pattern across codebase
HfLogger - High-Frequency Logger
File: packages/helpers/src/helpers/logger/hf-logger.ts
Problem: Standard logging has overhead that's unacceptable for HFT and performance-critical paths.
Solution: Zero-allocation logger using SharedArrayBuffer ring buffer with pre-encoded messages.
import { HfLogger, HfLogFlusher } from '@venizia/ignis-helpers';
// At initialization (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);Performance Characteristics:
| Metric | Value |
|---|---|
| Log latency | ~100-300 nanoseconds |
| Buffer size | 64K entries (16MB) |
| Entry size | 256 bytes fixed |
| Allocation | Zero in hot path |
Configurable File Rotation
File: packages/helpers/src/helpers/logger/default-logger.ts
Problem: File rotation settings were hardcoded.
Solution: Environment variables and programmatic configuration for rotation settings.
Environment Variables:
| 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 (days) |
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'
}
}
}
});JSON/Text Output Formats
Problem: Log format was fixed.
Solution: Configurable output format via APP_ENV_LOGGER_FORMAT.
# JSON format (for log aggregators)
APP_ENV_LOGGER_FORMAT=json
# Output: {"level":"info","message":"[UserService] User created","timestamp":"..."}
# Pretty text format (default, for development)
APP_ENV_LOGGER_FORMAT=text
# Output: 2026-01-11T10:30:00.000Z [app] info: [UserService] User createdPerformance Improvements
Logger Class Optimization
File: packages/helpers/src/helpers/logger/application-logger.ts
Problem: Logger had per-call overhead from prefix formatting and method binding.
Solution:
- Pre-computed formatted prefix at construction
- Static cache inside Logger class
- Direct Winston method calls instead of bound methods
- Fast path for default logger (no cache key computation)
export class Logger {
private static cache = new Map<string, Logger>();
private readonly _formattedPrefix: string;
private constructor(scope: string, logger: winston.Logger) {
this._formattedPrefix = `[${scope}] `; // Pre-computed once
this._logger = logger;
}
static get(scope: string): Logger {
// Fast path with caching
let cached = this.cache.get(scope);
if (cached) { return cached; }
cached = new Logger(scope, applicationLogger);
this.cache.set(scope, cached);
return cached;
}
info(message: string, ...args: any[]) {
// Direct call, no binding overhead
this._logger.info(this._formattedPrefix + message, ...args);
}
}| Scenario | Improvement |
|---|---|
| Logger instantiation | ~5x faster (cached) |
| Log method call | ~2x faster (no binding) |
| Prefix formatting | ~3x faster (pre-computed) |
Files Changed
Helpers Package (packages/helpers)
| File | Changes |
|---|---|
src/helpers/logger/application-logger.ts | Optimized Logger class with caching, .for() method, pre-computed prefix |
src/helpers/logger/default-logger.ts | Configurable file rotation options via env vars |
src/helpers/logger/hf-logger.ts | New HfLogger for zero-allocation logging |
src/helpers/logger/index.ts | Export HfLogger and HfLogFlusher |
src/__tests__/logger/hf-logger.test.ts | 50 comprehensive tests for HfLogger |
Core Package (packages/core)
| File | Changes |
|---|---|
src/helpers/inversion/container.ts | Updated to use Logger type |
src/base/middlewares/*.ts | Updated imports for Logger |
Documentation (packages/docs)
| File | Changes |
|---|---|
wiki/references/helpers/logger.md | Complete documentation update |
wiki/references/helpers/network.md | Updated with configuration interfaces |
No Breaking Changes
All changes are backward compatible:
ApplicationLoggeris exported as a type alias forLogger- Existing code using
LoggerFactory.getLogger()continues to work - New
.for()method is additive - Environment variables have sensible defaults
Best Practices
Use Method-Scoped Logging
// Good - clear context
this.logger.for('createOrder').info('Processing order: %s', orderId);
// Less clear
this.logger.info('[createOrder] Processing order: %s', orderId);Pre-encode HfLogger Messages
// Good - pre-encoded at init
const MSG_TICK = HfLogger.encodeMessage('Tick received');
logger.log('debug', MSG_TICK);
// Bad - encodes on every call
logger.log('debug', HfLogger.encodeMessage('Tick received'));Choose the Right Logger
| Use Case | Logger |
|---|---|
| General application | Logger / LoggerFactory |
| High-frequency trading | HfLogger |
| Performance-critical paths | HfLogger |
| Debug/development | Logger with DEBUG=true |