@mqnoy/lolog
v1.0.0
Published
Ease to logging your app with wrapping winston to own logger class which called Lolog
Downloads
136
Maintainers
Readme
🍭 lolog
Logging shouldn't be a bottleneck or a chore. lolog is a small, honest wrapper around Pino that handles the boring boilerplate so you don't have to — environment-specific output, secret redaction, and class-based context, all out of the box.
[!CAUTION] v1.0.0 contains breaking changes. If you are upgrading from
v0.0.7, please read the Migration Guide at the bottom of this page.
Why lolog?
Most teams end up writing the same setup code for Pino or Winston on every project: pretty-print in dev, JSON in prod, redact passwords, add a service name... It's always the same 50 lines. lolog does that for you:
- Built for Speed — Uses Pino internally, the fastest Node.js logger.
- Context-Aware — Decorators and child loggers tag your logs automatically.
- Pretty in Dev, Clean in Prod — Switches output automatically based on
NODE_ENV. - Secure by Default —
password,token,authorization, and friends are redacted automatically. - Easy to Configure — One
setup()call at the top of your app is all it takes.
Installation
pnpm add @mqnoy/lolog
# or
npm install @mqnoy/lologQuick Start
The default export is a pre-configured logger instance. It just works.
import logger from '@mqnoy/lolog';
logger.info('Server started');
logger.warn({ diskSpace: '2%' }, 'Storage running low');
try {
connectToDatabase();
} catch (err) {
// Always put the Error object first — it ensures the stack trace is captured
logger.error(err, 'Failed to connect to the database');
}Global Configuration
Call logger.setup() once at the very top of your app entry point (e.g., index.ts). This configures the global logger instance that all decorated classes and child loggers inherit from.
import logger from '@mqnoy/lolog';
logger.setup({
env: 'production', // 'development' | 'production' — defaults to NODE_ENV
serviceName: 'order-api',
logLevel: 'info', // 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
transports: [
{ type: 'console', level: 'info' },
{
type: 'file',
level: 'error',
options: { destination: './logs/error.log' },
},
],
});Transport Options
Each transport in the transports array accepts the following shape:
{
type: 'console' | 'file';
level?: string; // minimum level for this transport
options?: {
destination?: string; // file path, for 'file' type only
mkdir?: boolean; // auto-create the directory, defaults to true
colorize?: boolean; // pino-pretty options, for 'console' type
[key: string]: any; // any other pino-pretty / pino/file option
};
}Note on
level: Pino applies level filtering in two stages. The globallogLevelacts as the first gate — logs below it are dropped immediately. Each transport'slevelthen acts as a second filter. Always set the globallogLevelto the lowest level any transport needs.
Usage with Decorators
The @Logger decorator automatically injects a contextual logger into your class. The context field in your logs will be set to the class name (or whatever string you pass in).
import { Logger, ILolog } from '@mqnoy/lolog';
@Logger('UserService')
class UserService {
private readonly logger!: ILolog;
async findById(id: string) {
this.logger.debug({ userId: id }, 'Fetching user from database');
// → log will include { context: 'UserService', userId: '...' }
}
}Important: Call
logger.setup()before any classes using@Loggerare initialized. Otherwise they'll use the default configuration.
Child Loggers
Child loggers are perfect for request-scoped tracing. Every log from a child inherits its parent's configuration and appends its own fields.
const requestLogger = logger.child({
traceId: req.headers['x-trace-id'],
method: req.method,
path: req.path,
});
requestLogger.info('Request received');
requestLogger.debug({ userId: '42' }, 'Looking up user');
// Every log above will include the traceId, method, and pathMultiple Transports
You can fan out logs to multiple destinations simultaneously. Each transport has its own level filter, so you can, for example, send everything to the console but only errors to a file.
import { Lolog } from '@mqnoy/lolog';
const logger = new Lolog('MyApp', {
env: 'development',
logLevel: 'debug',
transports: [
{ type: 'console', level: 'debug' }, // colorized dev output
{ type: 'file', level: 'error', options: { destination: './logs/error.log' } },
{ type: 'file', level: 'info', options: { destination: './logs/combined.log' } },
],
});
logger.debug('This goes to console only');
logger.info('This goes to console and combined.log');
logger.error({ code: 'ERR_DB' }, 'This goes everywhere');Note for short-lived scripts: Pino transports run in worker threads. If your process exits immediately after logging, add a short delay (e.g.,
setTimeout(() => {}, 1000)) to let the workers flush to disk.
Development vs Production
lolog switches its output format automatically based on NODE_ENV:
| Environment | Output | Details |
| :--- | :--- | :--- |
| Development | Pretty, colorized (pino-pretty) | PID, hostname, and service metadata are hidden to keep the terminal clean |
| Production | Raw NDJSON | Structured JSON on stdout, ready for Datadog, CloudWatch, ELK, or Promtail |
Automatic Redaction
lolog redacts the following fields by default. They'll appear as *** in your logs:
password, token, accessToken, refreshToken, secret, authorization, headers.authorization, req.headers.authorization
You can override this with a custom redact config:
logger.setup({
redact: {
paths: ['user.ssn', 'payment.cvv'],
censor: '[REDACTED]',
},
});Migrating from v0.0.7 to v1.0.0
V1 is a ground-up rewrite. The class is still called Lolog, but almost everything under the hood changed. Here's what you need to update:
What Changed
| Feature | v0.0.7 (old) | v1.0.0 (new) |
| :--- | :--- | :--- |
| Engine | Winston | Pino |
| Error argument order | (message, error) | (error, message) |
| Child logger method | .setChild() | .child() |
| Configuration | Constructor only | logger.setup() + constructor |
| Level labels | Numeric in JSON | String level_label field |
1. Fix Error Logging
Pino serializes Error objects correctly only when they're the first argument.
// Before (v0.x)
logger.error('Database failed', err);
// After (v1.0)
logger.error(err, 'Database failed');2. Fix Child Loggers
// Before (v0.x)
const child = logger.setChild({ requestId: '123' });
// After (v1.0)
const child = logger.child({ requestId: '123' });3. Add Global Setup (Optional but Recommended)
Rather than passing config to every new Lolog(), configure the global instance once:
// index.ts — at the very top, before any other imports that use @Logger
import logger from '@mqnoy/lolog';
logger.setup({
serviceName: 'my-service',
logLevel: 'debug',
});Built with care for the production Node.js ecosystem. Issues and contributions are welcome on GitHub.
