Structured logging for Node.js that makes Go's log/slog look basic. Features file rotation, buffering, sampling, filtering, async handlers, middleware, and 20+ features Go slog doesn't have!
npm install @omdxp/jslogA structured logging library for Node.js that makes Go's log/slog look basic.
- Everything Go's slog has + variadic parameters (NEW in v1.7.0!)
- Plus 20+ features Go slog doesn't have
- File logging with auto-rotation
- Buffering, sampling, filtering
- Async handlers
- Middleware pattern
- Metrics collection
- Rate limiting & deduplication
- Colorful output
- And way more...
See full comparison in SUPERIORITY.md
``bash`
npm install @omdxp/jslog
`typescript
import { info, warn, error, String, Int } from '@omdxp/jslog';
// Go slog-style variadic parameters (NEW in v1.7.0!)
info('Application started', 'env', 'production', 'port', 3000);
warn('High memory usage', 'percentage', 85);
error('Failed to connect', 'host', 'localhost');
// Traditional style (still works)
info('Application started', String('env', 'production'), Int('port', 3000));
// Mix both styles!
info('Mixed', String('typed', 'value'), 'key', 'value');
`
`typescript
import { Logger, TextHandler, Level, String, Int } from '@omdxp/jslog';
const logger = new Logger(new TextHandler({ level: Level.DEBUG }));
// Go slog-style (NEW in v1.7.0!)
logger.info('User logged in', 'user', 'alice', 'ip', '192.168.1.1');
logger.warn('Rate limit exceeded', 'requests', 1000);
logger.error('Database connection failed', 'error', 'timeout');
// Traditional style
logger.info('User logged in', String('user', 'alice'), String('ip', '192.168.1.1'));
logger.warn('Rate limit exceeded', Int('requests', 1000));
logger.error('Database connection failed', String('error', 'timeout'));
`
`typescript
import { String, Int, Int64, Float64, Bool, Time, Duration, Any, Group } from '@omdxp/jslog';
// Go slog-style variadic (automatically inferred types)
logger.info('Event',
'name', 'user.created',
'userId', 42,
'score', 98.5,
'verified', true,
'metadata', { plan: 'pro' }
);
// Traditional typed style (explicit type control)
logger.info('Event',
String('name', 'user.created'),
Int('userId', 42),
Int64('bigNumber', 9007199254740991),
Float64('score', 98.5),
Bool('verified', true),
Time('createdAt', new Date()),
Duration('elapsed', 1500), // in milliseconds
Any('metadata', { plan: 'pro' }),
Group('address',
String('city', 'NYC'),
String('country', 'USA')
)
);
`
`typescript
import { JSONHandler, New } from '@omdxp/jslog';
const logger = New(new JSONHandler());
logger.info('Request processed', String('method', 'GET'), Int('status', 200));
// Output: {"time":"2024-01-01T00:00:00.000Z","level":"INFO","msg":"Request processed","method":"GET","status":200}
`
`typescript
// Add persistent attributes
const requestLogger = logger.with(String('request_id', '123-456'));
requestLogger.info('Processing request');
requestLogger.info('Request completed');
// Group related attributes
const dbLogger = logger.withGroup('database');
dbLogger.info('Connected', String('host', 'localhost'));
// Output: time=... level=INFO msg="Connected" database.host="localhost"
`
`typescript
import { String, Int, Bool, Any, Error as ErrorAttr } from '@omdxp/jslog';
logger.info('Event',
String('name', 'user.created'),
Int('userId', 42),
Bool('verified', true),
Any('metadata', { plan: 'pro' })
);
try {
throw new Error('Something went wrong');
} catch (err) {
logger.error('Operation failed', ErrorAttr(err as Error));
}
`
`typescript
import { LevelVar, TextHandler } from '@omdxp/jslog';
const levelVar = new LevelVar(Level.INFO);
const logger = new Logger(new TextHandler({ level: levelVar }));
logger.debug('Not visible'); // Won't show
logger.info('Visible'); // Will show
// Change level at runtime
levelVar.set(Level.DEBUG);
logger.debug('Now visible!'); // Will show
`
`typescript
import { MultiHandler, TextHandler, JSONHandler } from '@omdxp/jslog';
const textHandler = new TextHandler({ level: Level.INFO });
const jsonHandler = new JSONHandler({ level: Level.WARN });
const multiHandler = new MultiHandler([textHandler, jsonHandler]);
const logger = new Logger(multiHandler);
logger.info('Only in text'); // TextHandler only
logger.warn('In both formats'); // Both handlers
logger.error('In both formats'); // Both handlers
`
`typescript
import { TextHandler } from '@omdxp/jslog';
const logger = new Logger(new TextHandler({
level: Level.INFO,
replaceAttr: (groups, attr) => {
// Redact sensitive fields
if (attr.key === 'password' || attr.key === 'token') {
return { key: attr.key, value: 'REDACTED' };
}
// Transform time format
if (attr.key === 'time' && attr.value instanceof Date) {
return { key: attr.key, value: attr.value.toLocaleString() };
}
return attr;
}
}));
logger.info('Login', String('user', 'alice'), String('password', 'secret'));
// Output: ... user="alice" password="REDACTED"
`
`typescript
import { DiscardHandler } from '@omdxp/jslog';
// Useful for benchmarking or disabling logging
const logger = new Logger(new DiscardHandler());
logger.info('This will be discarded'); // No output
`
Track where log messages originate in your code with zero runtime dependencies:
`typescript
import { Logger, TextHandler, JSONHandler, Level, String } from '@omdxp/jslog';
// Enable source tracking with addSource option
const logger = new Logger(new TextHandler({
level: Level.INFO,
addSource: true
}));
logger.info('User action', String('action', 'login'));
// Output: time=... level=INFO source=app.ts:8 msg="User action" action="login"
// Works with JSON output too
const jsonLogger = new Logger(new JSONHandler({ addSource: true }));
jsonLogger.warn('High memory', String('usage', '85%'));
// Output: {"time":"...","level":"WARN","source":{"function":"checkMemory","file":"app.ts","line":15},...}
// Source tracking in nested functions
function processOrder(orderId: number) {
logger.info('Processing order', String('id', orderId));
// Shows the actual line where this log call is made
}
// Works in class methods too
class UserService {
private logger = new Logger(new JSONHandler({ addSource: true }));
createUser(name: string) {
this.logger.info('Creating user', String('name', name));
// Source shows: UserService.createUser at line X
}
}
`
Features:
- Zero Dependencies: No runtime dependencies, pure stack trace parsing
- TypeScript Support: Shows .ts files during development with tsx/ts-node.js
- Production Ready: Shows compiled files in production
- Smart Filtering: Automatically skips jslog internal frames to show your code
- Function Names: Captures function/method names when available
- Relative Paths: Shows paths relative to cwd for cleaner output
When to use:
- Development and debugging
- Troubleshooting production issues
- Audit logging where source location matters
- Understanding code flow in complex applications
Note: Source tracking adds minimal overhead (one stack trace capture per log call). Only enable it when needed using the addSource handler option.
- Default() - Get the default loggerSetDefault(logger)
- - Set the default loggerNew(handler)
- - Create a new logger with a handler
`typescript`
enum Level {
DEBUG = -4,
INFO = 0,
WARN = 4,
ERROR = 8,
}
- TextHandler - Human-readable text formatJSONHandler
- - Structured JSON formatMultiHandler
- - Send to multiple handlersDiscardHandler
- - Discard all logs (for testing/benchmarking)
`typescript`
interface HandlerOptions {
level?: Level | LevelVar; // Minimum log level
addSource?: boolean; // Add source location
replaceAttr?: (groups, attr) => Attr; // Transform attributes
}
- log(level, msg, ...attrs) - Log at specific levellogAttrs(level, msg, ...attrs)
- - Efficient logging variantdebug(msg, ...attrs)
- - Log at DEBUG leveldebugContext(msg, ...attrs)
- - Debug with context (future)info(msg, ...attrs)
- - Log at INFO levelinfoContext(msg, ...attrs)
- - Info with context (future)warn(msg, ...attrs)
- - Log at WARN levelwarnContext(msg, ...attrs)
- - Warn with context (future)error(msg, ...attrs)
- - Log at ERROR levelerrorContext(msg, ...attrs)
- - Error with context (future)with(...attrs)
- - Create logger with persistent attributeswithGroup(name)
- - Create logger with attribute groupenabled(level)
- - Check if level is enabled
- String(key, value) - String attributeInt(key, value)
- - Integer attributeInt64(key, value)
- - 64-bit integer attributeUint64(key, value)
- - Unsigned 64-bit integer attributeFloat64(key, value)
- - Float attributeBool(key, value)
- - Boolean attributeTime(key, date)
- - Date/time attributeDuration(key, ms)
- - Duration in millisecondsAny(key, value)
- - Any value attributeGroup(key, ...attrs)
- - Group attributesError(err)
- - Error attribute with stack traceattr(key, value)
- - Generic attribute constructor
- debug(msg, ...attrs) - Log at DEBUG leveldebugContext(msg, ...attrs)
- - Debug with contextinfo(msg, ...attrs)
- - Log at INFO levelinfoContext(msg, ...attrs)
- - Info with contextwarn(msg, ...attrs)
- - Log at WARN levelwarnContext(msg, ...attrs)
- - Warn with contexterror(msg, ...attrs)
- - Log at ERROR levelerrorContext(msg, ...attrs)
- - Error with contextlog(level, msg, ...attrs)
- - Log at specific levellogAttrs(level, msg, ...attrs)
- - Efficient variantwith_(...attrs)
- - Get default logger with attributeswithGroup(name)
- - Get default logger with group
`bashInstall dependencies
npm install
$3
The test suite validates that all examples work correctly:
`bash
Run all tests with assertions
npm testExpected output:
Starting test suite
Running: Basic Tests
PASSED: All 15 expectations met
Running: Advanced Tests
PASSED: All 22 expectations met
Running: Beast Mode Tests
PASSED: All 22 expectations met
All tests passed!
`Tests validate:
- Basic logging (TextHandler, JSONHandler, attributes, groups, levels)
- Advanced features (LevelVar, all attribute types, nested groups, replaceAttr, etc.)
- Beast mode features (20+ advanced handlers and utilities)
$3
When using handlers with resources (files, timers, async operations), always call
close():`typescript
import { New, FileHandler, BufferedHandler, AsyncHandler } from '@omdxp/jslog';const fileHandler = new FileHandler({ filepath: './logs/app.log' });
const logger = New(fileHandler);
// On shutdown
process.on('SIGTERM', () => {
// FileHandler.close() is synchronous
fileHandler.close();
process.exit(0);
});
// For async handlers (BufferedHandler, AsyncHandler, etc.)
const asyncHandler = new AsyncHandler({ handler: new JSONHandler() });
process.on('SIGTERM', async () => {
// These handlers need await
await asyncHandler.close();
process.exit(0);
});
`Handlers that need closing:
-
FileHandler - Closes file stream (sync: fileHandler.close())
- BufferedHandler - Flushes buffer and clears timer (async: await handler.close())
- AsyncHandler - Waits for pending operations (async: await handler.close())
- MultiHandler - Cascades close to all wrapped handlers (async: await handler.close())
- MiddlewareHandler - Delegates close to wrapped handler (async: await handler.close())Comparison with Go's log/slog
This library closely mirrors Go's
log/slog API:
| Go slog | jslog | Status |
| ------------------- | -------------------- | ------------- |
|
slog.Debug() | debug() | Implemented |
| slog.Info() | info() | Implemented |
| slog.Warn() | warn() | Implemented |
| slog.Error() | error() | Implemented |
| slog.New() | New() | Implemented |
| slog.Default() | Default() | Implemented |
| slog.SetDefault() | SetDefault() | Implemented |
| slog.With() | logger.with() | Implemented |
| slog.WithGroup() | logger.withGroup() | Implemented |
| slog.String() | String() | Implemented |
| slog.Int() | Int() | Implemented |
| slog.Bool() | Bool() | Implemented |
| slog.Time() | Time() | Implemented |
| slog.Duration() | Duration() | Implemented |
| slog.Group() | Group() | Implemented |
| slog.Any() | Any() | Implemented |
| slog.TextHandler | TextHandler | Implemented |
| slog.JSONHandler | JSONHandler | Implemented |
| slog.Level | Level | Implemented |
| slog.LevelVar | LevelVar | Implemented |
| Handler interface | Handler | Implemented |
| ReplaceAttr | replaceAttr option | Implemented |Inspiration
log/slog` package, bringing structured logging patterns to the Node.js ecosystem.