Reputation: 3453
I was looking at top Node logging systems: npmlog
, log4js
, bunyan
and winston
and decided to use winston
for having the most npm
monthly downloads.
What I want to set up is custom logger which I will be able to use on development environment with logger.debug(...)
which won't log anything on production environment. This will help me so when I'm on development environment, I won't need to write anything since I'll see all the outputs.
This is what I have now:
var level = 'debug';
if (process.env.NODE_ENV !== 'development'){
level = 'production'; // this will never be logged!
}
var logger = new winston.Logger({
transports: [
// some other loggings
new winston.transports.Console({
name: 'debug-console',
level: level,
prettyPrint: true,
handleExceptions: true,
json: false,
colorize: true
})
],
exitOnError: false // don't crush no error
});
Problem occurs when I'm trying to log JavaScript Object
or Javascript Array
. With Object
, I need to do toJSON()
, and for Array
I need first JSON.stringify()
and then JSON.parse()
.
It's not nice to write all the time this methods, just to log something that I want. Furthermore, it's not even resource-friendly, because those formatting methods need to be executed before logger.debug()
realises that it's on production and that it shouldn't log it in the first place (basically, it's evaluating arguments before function call). I just like how old-fashined console.log()
logs JavaScript objects and arrays.
Now, as I'm writing this question, I found that there is a way of describing custom format for every winston transports
object. Is that the way of doing it, or is there some other way?
Upvotes: 38
Views: 33853
Reputation: 1
The below code worked well for me:
import { Request } from 'express';
import util from 'util';
import winston, { Logger } from 'winston';
const splatSymbol = Symbol.for('splat');
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.colorize(),
winston.format.timestamp(),
winston.format.printf(({ timestamp, level, message, [splatSymbol]: splatArgs = [] }) => {
const formattedMessage = [message, ...splatArgs].map((value) => {
try {
if (typeof value === 'object' || Array.isArray(value)) {
return util.inspect(value, { depth: 5, showHidden: false, showProxy: false, maxArrayLength: null });
}
if (typeof value === 'function') {
return util.inspect(value, { depth: 5, showHidden: false, showProxy: false, compact: true });
}
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return value;
}
if (typeof value === 'symbol' || typeof value === 'bigint') {
return value.toString();
}
return String(value);
} catch (error) {
console.error(`Error formatting value: ${error}`);
return value;
}
}).join(' ');
return `${level}: ${timestamp} - ${formattedMessage}`;
})
),
transports: [new winston.transports.Console()],
});
export interface RequestWithLogger extends Request {
logger: Logger;
}
basically I used util to deal with objects and arrays, also I checked other edge cases. Here a log and an output example:
app.listen(process.env.PORT, () => {
logger.info('Server Started', {
server: `http://localhost:${process.env.PORT}`,
swagger: `http://localhost:${process.env.PORT}/api-docs`,
bullDashboard: `http://localhost:${process.env.PORT}/admin/queues`,
}, null, undefined, new Error('Server Started'), { a: { b: { c: { d: { e: { f: 1 }}}}}},
test,
() => {});
});
queuenode-app-1 | info: 2024-10-04T23:29:29.757Z - Server Started {
queuenode-app-1 | server: 'http://localhost:3333',
queuenode-app-1 | swagger: 'http://localhost:3333/api-docs',
queuenode-app-1 | bullDashboard: 'http://localhost:3333/admin/queues'
queuenode-app-1 | } null undefined Error: Server Started
queuenode-app-1 | at Server.<anonymous> (/app/src/server.ts:60:25)
queuenode-app-1 | at Object.onceWrapper (node:events:633:28)
queuenode-app-1 | at Server.emit (node:events:531:35)
queuenode-app-1 | at Server.incomingRequest (/app/node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@opentelemetry/instrumentation-http/src/http.ts:446:25)
queuenode-app-1 | at emitListeningNT (node:net:1947:10)
queuenode-app-1 | at processTicksAndRejections (node:internal/process/task_queues:81:21) {
queuenode-app-1 | a: {
queuenode-app-1 | b: {
queuenode-app-1 | c: { d: { e: { f: 1 } } }
queuenode-app-1 | }
queuenode-app-1 | }
queuenode-app-1 | } [Function: test] [Function (anonymous)]
Upvotes: 0
Reputation: 525
Try using util.inspect for objects. It handles circular references correctly as well. @radon-rosborough has already given this answer but I thought of adding an example. Please see below
const customTransports = [
new winston.transports.Console({
format: combine(
timestamp({
format: 'DD-MMM-YYYY HH:MM:SS'
}),
label({
label: file
}),
prettyPrint(),
format.splat(),
simple(),
printf( (msg)=> {
let message = msg.message;
return colorize().colorize(msg.level, `${ msg.level } : ${msg.timestamp} : ${ msg.label } : \n`) + `${ util.inspect(message,{
depth: 2,
compact:true,
colors: true,
} )}`;
})
)
})
]
Upvotes: 1
Reputation: 337
In Winston > 3 you can use
logger.log('%o', { lol: 123 }')
Anyway... Couldn't accept that I have to use %o always and made this simple solution:
const prettyJson = format.printf(info => {
if (info.message.constructor === Object) {
info.message = JSON.stringify(info.message, null, 4)
}
return `${info.level}: ${info.message}`
})
const logger = createLogger({
level: 'info',
format: format.combine(
format.colorize(),
format.prettyPrint(),
format.splat(),
format.simple(),
prettyJson,
),
transports: [
new transports.Console({})
],
})
So this logger....
logger.info({ hi: 123 })
...transforms to this in the console
info: {
"hi": 123
}
Upvotes: 9
Reputation: 1445
Instead of doing
prettyPrint: function ( object ){
return JSON.stringify(object)
}
it's better go with utils-deep-clone package
// initialize package on the top
const { toJSON } = require('utils-deep-clone')
// and now in your `prettyPrint` parameter do this
prettyPrint: function ( object ){
return toJSON(object)
}
if you'll go with JSON.stringify
you won't be able to print error
console.log(JSON.stringify(new Error('some error')))
// output will '{}'
Upvotes: 0
Reputation: 53
As Leo already pointed out in his answer, Winston makes use of String Interpolation provided by util.format:
const winston = require("winston"); const logger = new winston.Logger({ transports: [ // some other loggings new winston.transports.Console({ name: "debug-console", level: process.env.LOGLEVEL || "info", prettyPrint: true, handleExceptions: true, json: false, colorize: true }) ], exitOnError: false // don't crush no error }); const nestedObj = { foo: { bar: { baz: "example" } } }; const myString = "foo"; logger.log("info", "my nested object: %j. My string: %s", nestedObj, myString);
When calling logger.log
, you can define placeholders which will be appropriately replaced. %j
will be replaced by the equivalent of JSON.stringify(nestedObj)
Upvotes: 1
Reputation: 1120
logger.log("info", "Starting up with config %j", config);
Winstons uses the built-in utils.format library. https://nodejs.org/dist/latest/docs/api/util.html#util_util_format_format_args
Upvotes: 31
Reputation: 4957
Use the built-in Node.js function util.format
to convert your objects to strings in the same way that console.log
does.
Upvotes: 4
Reputation: 1003
try changing prettyPrint parameter to
prettyPrint: function ( object ){
return JSON.stringify(object);
}
Upvotes: 16
Reputation: 136
My recommendation is to write your own abstraction on top of winston that has a convenience method for printing your objects for debugging.
You may also look at this response for a hint of how the method could be developed.
https://stackoverflow.com/a/12620543/2211743
Upvotes: 2