@hiprax/logger
v0.7.0
Published
Fully typed Winston-based logger with rotation, timezone support, and Express middleware
Maintainers
Readme
@hiprax/logger
Fully typed, production-grade logging toolkit for Node.js applications. Built on top of Winston with first-class TypeScript support, rotating file transports, timezone mirroring, and an HTTP middleware that outperforms traditional solutions.
Table of Contents
- Features
- Installation
- Quick Start
- API
- Log Output Format
- Scripts
- Testing & Coverage
- Security Notes
- Contributing
- License
- Links
Features
- Multi-target logging (console, per-module files, shared global file, custom transports). Console output is colorized and omits timestamps so only rotating files capture the full timeline.
- Daily rotation with independent retention rules for module and global files
- Guaranteed UTC timestamps plus optional verified IANA timezone mirrors
- Automatic log directory creation (including nested module scopes like
security/failedLogins) - Batteries-included Express middleware with structured HTTP payloads, body redaction, and header filtering
- Graceful fallback for unknown logger methods (warns once, re-routes to
info()) - Environment-aware request logging with built-in presets (
dev-only,prod-only,test-only) and custom rules - Zero-config ESM & CommonJS dual builds with rich IntelliSense documentation
- Comprehensive Jest suite with 100% coverage enforcement
Installation
npm install @hiprax/loggerThe package ships with precompiled dual builds (
.mjsfor ESM,.cjsfor CommonJS). No transpilation is required in consuming projects.
Quick Start
import { createLogger } from "@hiprax/logger";
const securityLogger = createLogger({
moduleName: "security/failedLogins",
extraTimezones: ["Europe/London"],
});
securityLogger.warn(`Failed login attempt\nEmail: ${email}\nIP: ${req.realIp}`);CommonJS usage:
const { createLogger } = require("@hiprax/logger");API
createLogger(options?: LoggerOptions)
Creates a fully configured Winston logger with safe defaults, rotating files, UTC timestamps, and optional timezone mirrors. Instances are cached by moduleName + logDirectory — calling createLogger() with the same configuration from multiple files returns the same logger, preventing duplicate transports, file handles, and console output.
| Option | Type | Default | Description |
| ---------------------- | --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------ |
| moduleName | string | 'global' | Label used in log lines and for module-specific files. Supports nested scopes (security/failedLogins). |
| logDirectory | string | <process.cwd()>/logs | Target directory (auto-created). |
| level | LogLevel | 'info' | Default level for all transports. |
| consoleLevel | LogLevel | level | Console-specific level override. |
| includeConsole | boolean | true | Enables console logging. Console lines are colorized and omit timestamps. |
| includeFile | boolean | true | Enables module-specific rotating file logging. |
| includeGlobalFile | boolean | true | Enables shared rotating file logging. |
| globalModuleName | string | 'all-logs' | Label for the shared log file. |
| extraTimezones | string \| string[] | [] | Additional IANA zones rendered beside UTC. Validity is enforced; invalid zones throw InvalidTimezoneError. |
| rotation | RotationStrategy | 20 MB / 14 days / daily | Rotation config for the module file. |
| globalRotation | RotationStrategy | rotation | Override rotation for the shared file. Falls back to rotation when omitted. |
| additionalTransports | winston.transport[] | [] | Appends custom transports (e.g., HTTP, Kafka, Stream). |
RotationStrategy
interface RotationStrategy {
maxSize?: string; // e.g., '20m', '200k'
maxFiles?: string; // e.g., '14d', '30'
datePattern?: string; // default: 'YYYY-MM-DD'
zippedArchive?: boolean;
}Unknown method fallback: If you call a method that does not exist on the logger (e.g., logger.success("done")), it will log a warning once and route the message through info() instead of throwing.
Instance caching: Logger instances are cached by moduleName + logDirectory. The same call from different files returns the same instance. Use resetLoggerRegistry() to clear the cache (useful for testing or hot-reload scenarios):
import { resetLoggerRegistry } from "@hiprax/logger";
resetLoggerRegistry(); // clears all cached instancesRequest Logging Middleware
import express from "express";
import { createRequestLogger } from "@hiprax/logger";
const app = express();
app.use(express.json());
app.use(
createRequestLogger({
includeRequestBody: true,
includeRequestHeaders: ["authorization"],
includeResponseHeaders: true,
maskBodyKeys: ["password", "token"],
enrich: (req) => ({ tenantId: req.headers["x-tenant-id"] }),
}),
);When includeHttpContext is enabled, the middleware attaches rich structured metadata via info.http while emitting a concise human-readable message. It relies on plain Node events (finish/close) with a guard against double-logging, and does not depend on on-finished.
| Option | Type | Default | Description |
| ------------------------ | --------------------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------- |
| logger | winston.Logger | Scoped http logger | Provide your own logger or use the auto-created scoped one. |
| level | LogLevel \| (status: number) => LogLevel | Auto (info/warn/error) | Override severity. The default maps 5xx to error, 4xx to warn, rest to info. |
| label | string | — | Included in the auto-generated logger name (http/<label>). |
| messageBuilder | (entry) => string | "METHOD URL status latency (event)" | Customize the final message string. |
| skip | (req, res) => boolean | — | Return true to skip logging for specific requests. |
| enrich | (req, res, durationMs) => Record<string, unknown> | — | Inject extra context (e.g., tenant, user). Attached under entry.context. |
| includeRequestHeaders | boolean \| string[] | false | true for all headers, or an array of allowed header names. |
| includeResponseHeaders | boolean \| string[] | false | Same as above for response headers. |
| includeRequestBody | boolean | false | Logs parsed request body (with redaction support). |
| maskBodyKeys | string[] | [] | Keys replaced with [REDACTED]. Applies deeply and case-insensitively, including arrays. |
| maxBodyLength | number | 3000 | Caps serialized body size to prevent log floods. Truncated bodies end with .... |
| includeHttpContext | boolean | false | Attaches the structured RequestLogEntry payload under info.http. |
| loggingEnabled | boolean | true | Hard enable/disable switch. |
| loggingMode | RequestLoggingMode | 'always' | Environment-aware control. See below. |
Environment-Aware Request Logging
The loggingMode option controls when request logging is active, based on environment variables.
Built-in presets:
| Mode | Matched env values (case-insensitive) | Env sources checked (in order) |
| ------------- | ------------------------------------- | ------------------------------ |
| 'always' | Always enabled | — |
| 'never' | Always disabled | — |
| 'dev-only' | dev, development, local | NODE_ENV, APP_ENV, ENV |
| 'prod-only' | prod, production, live | NODE_ENV, APP_ENV, ENV |
| 'test-only' | test, testing, qa, staging | NODE_ENV, APP_ENV, ENV |
Custom configuration:
createRequestLogger({
loggingMode: {
sources: ["DEPLOYMENT_STAGE"], // env vars to check (priority order)
allow: ["staging", "qa"], // values that enable logging
fallback: false, // behavior when no env source is found
},
});Tips:
- Pair
loggingEnabledwith boolean expressions for one-line toggles:loggingEnabled: process.env.FEATURE_LOGS === "on" - The
loggingModedecision is evaluated once at middleware creation time, not per-request - Keep log output lightweight by enabling
includeHttpContextonly when you need the full structured payload
Timezone Handling
- UTC is always logged in file output.
- Additional zones must be valid IANA identifiers (validated by
moment-timezone). Invalid entries throwInvalidTimezoneError. - Provide a single string or an array:
createLogger({
extraTimezones: ["Europe/London", "America/New_York"],
});- Duplicate timezone entries are automatically deduplicated.
Custom Transports
You can disable all built-in transports and use only custom ones:
import { PassThrough } from "node:stream";
import winston from "winston";
import { createLogger } from "@hiprax/logger";
const capture = new PassThrough();
const logger = createLogger({
moduleName: "audit",
includeConsole: false,
includeFile: false,
includeGlobalFile: false,
additionalTransports: [new winston.transports.Stream({ stream: capture })],
});Log Output Format
File output (with timestamps and extra timezones):
UTC: 2025-06-15 14:30:22
Europe/London: 2025-06-15 15:30:22
[WARN] (security/failedLogins)
Failed login attempt
Email: [email protected]
IP: 192.168.1.1Console output (colorized, no timestamps):
[WARN] (security/failedLogins)
Failed login attempt
Email: [email protected]
IP: 192.168.1.1Request middleware default message:
POST /auth/login 201 12.45ms (completed)When includeHttpContext is enabled, the structured RequestLogEntry is attached under info.http:
{
"event": "completed",
"method": "POST",
"url": "/auth/login",
"statusCode": 201,
"responseTimeMs": 12.45,
"contentLength": 256,
"ip": "127.0.0.1",
"userAgent": "Mozilla/5.0...",
"requestId": "req-abc-123"
}Scripts
| Command | Description |
| -------------------- | ------------------------------------------------------ |
| npm run build | Generates dual ESM/CJS bundles plus type declarations. |
| npm test | Runs Jest with 100% coverage enforcement. |
| npm run lint | Runs ESLint across source and test files. |
| npm run type-check | Runs the TypeScript compiler in check-only mode. |
Testing & Coverage
npm testts-jestcompiles TypeScript on the fly with ESM support.- Coverage thresholds are locked at 100% for branches, functions, lines, and statements.
- Tests run in band (
--runInBand) for deterministic behavior.
Security Notes
- All filesystem interactions are sandboxed to the configured log directory.
- Module names are sanitized to prevent path traversal (dangerous characters are replaced with hyphens).
- Timezones are validated against the Moment timezone database before use.
- Sensitive request payload fields can be masked recursively via
maskBodyKeys(case-insensitive, handles nested objects and arrays). - Circular references in request bodies are safely handled (replaced with
[Circular]).
Contributing
- Clone the repo and install dependencies.
- Run
npm testto ensure the suite passes before submitting changes. - Follow the existing TypeScript, linting, and documentation patterns.
License
MIT License - see LICENSE file for details.
