Reputation: 2471
I use Winston for my backend logging I cannot log the object without using JSON.stringify
which is annoying
logger.debug(`Register ${JSON.stringify(req.body)}`)
const logger: Logger = createLogger({
// change level if in dev environment versus production
level: env === 'production' ? 'info' : 'debug',
format: format.combine(
format.label({label: path.basename(process.mainModule.filename)}),
format.timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
format.prettyPrint()
),
transports: [
new transports.Console({
format: format.combine(format.colorize(), logFormat),
}),
new transports.File({
filename,
format: format.combine(format.json()),
}),
],
exitOnError: false,
})
Could you show me the way to log object with Winston. I am using version 3.2.1
Upvotes: 34
Views: 58158
Reputation: 189
const transports: (
| winston.transports.ConsoleTransportInstance
| winston.transports.HttpTransportInstance
)[] = [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
winston.format.printf(
({ context, level, message, timestamp }) => {
const ns = cls.getNamespace("ctx");
const reqId: any = ns?.get("reqId");
return JSON.stringify({
level: level,
context: context,
time: timestamp,
message: message,
});
}
),
winston.format.prettyPrint()
),
}),
];
Upvotes: 0
Reputation: 1694
You are trying to insert a JSON object directly into the string, so it will print [Object Object]
without the JSON.stringify
.
This is not fixable by configuring Winston, as this problem happens while the string is generated (before the logger.debug
function actually reads it), so a console.log
call would print the same thing.
The first parameter of the logger.*
functions is the message (string), then you can pass a metadata object (JSON).
To use the metadata in your logFormat
function, update your Logger instantiation as follow:
const winston = require('winston')
const { format, transports } = winston
const path = require('path')
const logFormat = format.printf(info => `${info.timestamp} ${info.level} [${info.label}]: ${info.message}`)
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
format: format.combine(
format.label({ label: path.basename(process.mainModule.filename) }),
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
// Format the metadata object
format.metadata({ fillExcept: ['message', 'level', 'timestamp', 'label'] })
),
transports: [
new transports.Console({
format: format.combine(
format.colorize(),
logFormat
)
}),
new transports.File({
filename: 'logs/combined.log',
format: format.combine(
// Render in one line in your log file.
// If you use prettyPrint() here it will be really
// difficult to exploit your logs files afterwards.
format.json()
)
})
],
exitOnError: false
})
Usage:
const req = {
body: {
name: 'Daniel Duuch',
email: '[email protected]',
password: 'myGreatPassword'
}
}
logger.debug(`Register ${req.body.name} with email ${req.body.email}`, { ...req.body, action: 'register' })
Console output:
2019-05-11 17:05:45 debug [index.js]: Register Daniel Duuch with email [email protected]
Logfile output (prettified by hand, see comment in the transport file format):
{
message: 'Register Daniel Duuch with email [email protected]',
level: 'debug',
timestamp: '2019-05-11 17:05:45',
label: 'index.js',
metadata: {
name: 'Daniel Duuch',
email: '[email protected]',
password: 'myGreatPassword',
action: 'register'
}
}
Hope this solves your issue.
Important note: as noted by @Xetera in the comments, "you should make sure you're not actually logging people's passwords anywhere"
Upvotes: 61
Reputation: 1
winston-pretty will solve this issue in a robust and versatile way. It's a standalone program that takes winston json log as input and outputs human readable log lines. It also pretty prints json and errors.
npm install -g winston-pretty
npm run <your-project> | winston-pretty
https://www.npmjs.com/package/winston-pretty
Upvotes: 0
Reputation: 3232
If you want the object to be logged to the console and to the file, here is what you can do:
1.Initialize 2 formats. One for file and the other for the console. Notice the JSON.stringify
method used in the consoleFormat
const winston = require("winston");
const { format, transports, createLogger } = winston;
const path = require("path");
const consoleloggerLevel = process.env.WINSTON_LOGGER_LEVEL || "info";
const consoleFormat = format.combine(
format.colorize(),
format.timestamp(),
format.align(),
format.printf((info) => {
return `${info.timestamp} - ${info.level}: [${info.label}]: ${
info.message
} ${JSON.stringify(info.metadata)}`;
})
);
const fileFormat = format.combine(
format.timestamp(),
format.label({ label: path.basename(process.mainModule.filename) }),
format.metadata({ fillExcept: ["message", "level", "timestamp", "label"] }),
format.json()
);
2.Now, create the logger.
const logger = createLogger({
level: "info",
defaultMeta: { service: "some-random-service" },
format: fileFormat,
transports: [
new transports.File({
filename: path.join(__dirname, "../logs/error.log"),
level: "error",
}),
new transports.File({
filename: path.join(__dirname, "../logs/activity.log"),
maxsize: 5242880, //5MB
maxFiles: 5 // just in case
}),
],
});
3.Enable console logging only in non-prod environments:
if (process.env.NODE_ENV !== "production") {
logger.add(
new transports.Console({
level: consoleloggerLevel,
format: consoleFormat,
})
);
}
4.Export it as the default logger
module.exports = logger;
On logger.info("Server started listening", { port: 9000 } );
This will print,
On the console:
2021-06-22T07:47:25.988Z - info: [index.js]: Server started listening {"service":"some-random-service", "port": 9000}
In the file:
{"message":"Server started listening","level":"info","timestamp":"2021-06-22T07:47:25.988Z","label":"index.js","metadata":{"service":"some-random-service", "port": 9000}}
Upvotes: 3
Reputation: 3556
My solution was to use this kind of formatter:
const { format } = winston
const consoleFormat = format.combine(
format.prettyPrint(),
format.splat(),
format.printf((info) => {
if (typeof info.message === 'object') {
info.message = JSON.stringify(info.message, null, 3)
}
return info.message
})
)
now all those options works as expected:
logger.info('plain text')
logger.info('plain text with object %o', { a:1, b: 2} )
logger.info({ a:1, b: 2 })
Upvotes: 18
Reputation: 3458
Or you just use the
printf
function in conjunction with JSON.stringify
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple(),
winston.format.printf(context => {
const msgstr = JSON.stringify(context.message, null, '\t')
return `[${context.level}]${msgstr}`
}),
),
})
Upvotes: 2
Reputation: 641
I had to combine the solution provided by @SherloxFR and @Anton.
const Winston = require('winston');
const { format } = Winston;
const options = {
file: {
....
format: format.combine(
format.splat(),
format.json()
),
...
},
console: {
...
format: format.combine(
format.splat(),
format.json()
),
...
}
};
You can see that I added both format.splat()
and format.json()
to the options config in the above code.
const logger = new Winston.createLogger({
transports: [
new Winston.transports.File(options.file),
new Winston.transports.Console(options.console)
],
exitOnError: false // do not exit on handled exceptions
});
That is how I used the options config object. You can actually write the format code inside the transports array but I don't like it that way. It's your choice anyway.
After the configuration like that, the is how I used it in my code
let myObj = {
name: "StackOverflow",
};
logger.info('Content: %o', myObj);
You can also spread it like this if you want
logger.info('Content: %o', {...myObj});
That's all. Winston should log your object with this set up.
Upvotes: 7
Reputation: 628
You can use format.splat()
in your logger config:
const logger = createLogger({
format: combine(
...
format.splat(), // <--
...
),
...
});
...and log object using string interpolation:
let myObj = { /* ... */ };
logger.info('This message will include a complete object: %O', myObj);
Upvotes: 25