Skip to content

Changelog - 2026-07-18

Logger Overhaul

Breaking Change New Feature Enhancement Bug Fix

In one line. Consumers now type against ILogger and never name a provider; the app picks winston or pino with one line at the entrypoint and loads only that one; HfLogger emits correct output and implements ILogger.

Architecture

modules/logger/ follows IGNIS's house format:

common/       ILogger, TLogLevel, LogLevels, LoggerFormats
base/         AbstractLogger -> BaseLogger (scope, prefix, DEBUG gate, .for(), one write() sink)
formatting/   formatLogMessage (deep %s inspection + secret redaction)
winston/      WinstonLogger + define.ts, formatters, transports   -> sub-path /winston
pino/         PinoLogger + define.ts                              -> sub-path /pino
hf/           HfLogger, HfLogFlusher (separate ring-buffer pipeline)
factory.ts    LoggerFactory - the one selection point
  • ILogger is the contract. LoggerFactory.getLogger() and BaseHelper.logger return ILogger; which provider backs it is invisible.
  • New exports: ILogger, AbstractLogger, BaseLogger, WinstonLogger, PinoLogger, ILoggerProvider.

Provider registration and single-provider loading

typescript
// entrypoint - order-independent
import { LoggerFactory } from '@venizia/ignis-helpers';
import { PinoLogger } from '@venizia/ignis-helpers/pino';

LoggerFactory.use({ provider: PinoLogger });
  • Swap-on-use. The factory hands out stable wrappers and re-points every one at use(), so a module-level const logger = LoggerFactory.getLogger([...]) captured at import time still follows the registration. Wrapper cost: one property read.
  • Exactly ONE provider loads. Wrappers resolve their delegate at the first log call, and the winston default sits behind a createRequire boundary a bundler cannot resolve. An app registering pino never loads or bundles winston.
  • Both providers are sub-path only. The root barrel is provider-free; all winston names live at @venizia/ignis-helpers/winston, pino at /pino. All provider packages are optional peers.
  • Compiled binaries (bun build --compile) MUST register explicitly - only a class reference carries a provider into a bundle.

Pino provider (guide): NDJSON to stdout by default, pino-pretty on APP_ENV_LOGGER_FORMAT=text, pino-roll rotation honoring the existing APP_ENV_LOGGER_FILE_* variables. Roughly 2.5x faster per line than winston (~0.5us vs ~1.25us). Pino stays pino-native - JSON key names, single-destination output, and no UDP transport differ from winston by design.

Five log levels

debug, info, warn, error, emerg - each a direct method, plus log(level, ...) for dynamic choice. alert, http, verbose, and silly were removed (zero call sites, no consuming infrastructure; alert duplicated emerg). The default debug floor admits every level on both providers.

LevelPriorityColor
error0red
emerg0red
warn1yellow
info2green
debug3blue

HfLogger rework

  • Correct output. Entries carry explicit scope/message length bytes - no NUL padding, no stale tails from a longer overwritten message.
  • ILogger conformance. All five level methods plus log and for; args are formatted (deep inspection + redaction), never dropped. The pre-encoded bytes hot path survives as an overload.
  • Exact lap accounting. Every drain batch reports how many entries the ring overwrote; the default sink emits a warn marker.
  • Lazy 16MB ring (allocated on first HfLogger.get(), not at import) on a plain ArrayBuffer - the design was always single-thread, so SharedArrayBuffer shared nothing.
  • Flusher lifecycle: stop(), an unref'd interval, batched yielding drains, and pluggable sink / filePath options.
  • Measured ~46-66ns per enqueue, ~14x faster than pino sync on the same machine; heap flat over 1M logs.

Secret redaction kill-switch

APP_ENV_LOGGER_DO_REDACT=false (that literal only - fail-closed) turns redaction into a pass-through for local debugging, covering log arguments, fetcher request logs, and connection URLs. Unset or any other value keeps today's behavior. Never disable in production.

Breaking changes

ChangeMigration
Winston names left the root barrelImport Logger, WinstonLogger, defineCustomLogger, formatters, DgramTransport, and their option types from @venizia/ignis-helpers/winston
winston is an optional peerApps relying on the default: bun add winston winston-transport winston-daily-rotate-file
Compiled binariesCall LoggerFactory.use({ provider }) explicitly
applicationLogger removed (value and ApplicationLogger type alias)ApplicationLogger.get('YourScope'); annotate with ILogger
: Logger annotationsWiden to : ILogger (also for .for() results)
ApplicationLogger is no longer a classinstanceof AbstractLogger for provider-agnostic checks
Removed levels alert/http/verbose/sillyalert -> emerg, http/verbose -> info or debug, silly -> debug
ICustomLoggerOptions field nameslogLevels -> levels, logColors -> colors, loggerFormatter -> formatter
HfLogger flushed line formatNow <ISO time> [<levelName>] <scope> <message>; the old NUL-padded shape was corrupt output, not a contract

Untyped call sites (this.logger.info(...), LoggerFactory.getLogger([...]), .for('method')) need no change.

Details

  • HfLogger entry layout v2 (256 bytes): float64 epoch-ms timestamp at 0-7, level code at 8, scope length at 9, scope at 10-41, message length at 42, message at 43-255 (cap 213 bytes). Encode cache is FIFO-bounded at 4096.
  • Winston pipeline: two-stage formatting - shared prep on the logger, per-transport assembly - so console colorizes while file and UDP lines stay ANSI-free. File transports remain opt-in via APP_ENV_LOGGER_FOLDER_PATH.
  • Environment variables, the level guide, and every API signature: Full reference.