
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
lean-logger
Advanced tools
Lean-logger is a nodejs logger, doing only logging, only json to only console, very lean with no dependencies.
There are 2 main reasons:
trace to error) isn't enough for any non-trivial application. What if your database layer has bugs and needs debug or trace to diagnose them, while the rest of the app only needs warn? Typically, you set something like LOG=trace and drown in gigabytes of logs. Even worse, what if different modules constantly need different verbosity levels? We all know the usual answer: crank logging to the max and put up with tons of noise - and wasted thousands in log storage.The solution is simple: this library introduces logging "channels" - like a separate loggers for different parts of your application, each with its own verbosity level, independently configured via config in code or via environment dynamically.
import {
createLogger,
type Channel,
type Logger,
type LoggerConfig,
// type LogLevel,
// type LoggerData,
} from 'lean-logger';
// LoggerConfig is optional parameter, there is always one default channel, always on, working traditional way - { default: 'info' }
const logger: Logger = createLogger({
// set separate leve for every channel
default: 'warn',
db: 'debug',
auth: 'trace',
});
// ...
logger.info('Service started');
logger.error('Failed to start service, fatal error', error.toString());
// somewhere in DAL module
const dbLog: Channel = logger.getChannel('db');
dbLog.warn('Database query took longer than expected', { duration });
dbLog.info('Database connection established');
dbLog.trace('Query executed:', q);
// somewhere in payments module
const pmtsLog: Channel = logger.getChannel('payments');
pmtsLog.info('Payment processed successfully', response);
pmtsLog.debug('Payment data', paymentData);
pmtsLog.fatal('Payment processing failed', pmtError);
// somewhere in auth module
const authLog: Channel = logger.getChannel('auth');
authLog.info('User authenticated', uid);
authLog.debug('User auth data', userData);
authLog.warn('User data has invalid signature', userData, signature);
...which prints:
{"channel":"DEFAULT","level":"INFO","time":"2025-07-12T11:56:10.979Z","messages":["Service started"]}
{"channel":"DEFAULT","level":"ERROR","time":"2025-07-12T11:56:10.980Z","messages":["Failed to start service, fatal error","Error: Shit happens"]}
{"channel":"DB","level":"WARN","time":"2025-07-12T11:56:10.980Z","messages":["Database query took longer than expected",{"duration":"890ms"}]}
{"channel":"DB","level":"INFO","time":"2025-07-12T11:56:10.980Z","messages":["Database connection established"]}
{"channel":"AUTH","level":"INFO","time":"2025-07-12T11:56:10.980Z","messages":["User authenticated","user123"]}
{"channel":"AUTH","level":"DEBUG","time":"2025-07-12T11:56:10.980Z","messages":["User auth data",{"name":"John Doe","email":"john@doe.com"}]}
{"channel":"AUTH","level":"WARN","time":"2025-07-12T11:56:10.980Z","messages":["User data has invalid signature",{"name":"John Doe","email":"john@doe.com"},"abc123signature"]}
2 points from the above:
logger already contains default channel, so you don't need to get it separately;payments channel is not declared in the config, so logger.getChannel('payments') returns a silent channel by default. It can be enabled at runtime via LOG=payments:info without changing code.A convenient pattern is to cast the logger and attach channels directly as named properties, giving you a single object to pass around:
const logger = createLogger({ default: 'warn', dal: 'debug', http: 'info', aws: 'warn' }) as Logger & Record<string, Channel>
logger.dal = logger.getChannel('dal')
logger.http = logger.getChannel('http')
logger.aws = logger.getChannel('aws')
// then anywhere in your code:
logger.aws.warn('S3 bucket not found', { bucket });
logger.dal.debug('Query executed', { sql, duration });
logger.http.info('Request received', { method, path });
logger.error('Unhandled exception', err); // default channel still on the root
The cast as Logger & Record<string, Channel> is needed because TypeScript doesn't know about the dynamically added properties — the runtime behavior is plain object assignment.
It allows you to dynamically change the verbosity level for any channel - or toggle channels on and off entirely - by setting the LOG (or LOGGER) environment variable:
LOG=<channel>:<level>,<channel>:<level>,...
The default channel name can be omitted or set as an empty string or *, so the below lines mean the same:
LOG=info,db:debug,auth:trace
LOG=:info,db:debug,auth:trace
LOG=*:info,db:debug,auth:trace
The environment configuration takes precedence, so if you set:
LOG=info,db:silent,auth:trace,payments
then the db channel will be silenced despite debug level in the code, and the payments channels will get info level and will start to print.
const logger: Logger = createLogger({ default: 'warn', ... },
// mixin parameter, static
{
pid: process.pid,
service: 'LAX-AIR-MON-12'
});
... which adds to output:
{"channel":"AUTH","level":"WARN","time":"2025-07-12T12:53:36.268Z","pid":252753,"service":"LAX-AIR-MON-12","messages":["User data has invalid signature",{"name":"John Doe","email":"john@doe.com"},"abc123signature"]}
Same way you can use function for mixin parameter:
const logger: Logger = createLogger({ default: 'warn', ... },
// mixin parameter, function
(data) => ({
...data, // spread original data to preserve fields
pid: process.pid,
service: 'LAX-AIR-MON-12',
messages: [...data.messages, 'Bender was here']
})
);
... which updates output:
{"channel":"AUTH","level":"WARN","time":"2025-07-12T13:01:47.094Z","messages":["User data has invalid signature",{"name":"John Doe","email":"john@doe.com"},"abc123signature","Bender was here"],"pid":253077,"service":"LAX-AIR-MON-12"}
The lib uses process.stdout|stderr internally, so logging is asynchronous.
In the case of mistyped level name - default channel gets info level, any other - silent.
Log calls at error and fatal levels output to stderr; everything else goes to stdout.
For colored printing, pls install jq then run your app like
node dist/server.js 2>&1 | jq -c -R 'fromjson?'
That 2>&1 part combines stdout and stderr, and the ... -R 'fromjson?' lets jq to ignore non-json output in case you have one.
FAQs
dead simple, fast, env configurable, multi-channel node.js json logging
The npm package lean-logger receives a total of 114 weekly downloads. As such, lean-logger popularity was classified as not popular.
We found that lean-logger demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.