Article contents
Getting started with Winston
Let's first create a new project folder so we can take Winston for a test drive. I recommend that you choose a directory name such as winston-test
rather than winston
to ensure that npm does not yield an error and refuse to install a package as a dependency of itself.
Next, create a blank package.json
file that automatically accepts all the defaults without prompting you. (We are, after all, just creating a quick test project.)
$ npm init -y
We are now positioned to install Winston and save it as a dependency in our package.json
file:
$ npm install --save winston
Create a file called index.js
and add the following contents:
'use strict';
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
level: 'debug',
format: format.simple(),
// You can also comment out the line above and uncomment the line below for JSON format
// format: format.json(),
transports: [new transports.Console()]
});
logger.info('Hello world');
logger.debug('Debugging info');
This enables us to log messages to the console by defining a "transport" (in Winston parlance) to specify where we want to output our messages. We use require
to load the Winston module and we can then start logging messages to the console.
Next, run the program you just created from the console:
$ node index.js
You should see the following output:
info: Hello world
debug: Debugging info
Success - you are logging messages to the console!
As noted in the program comments above, we can also change the format of the log output messages and use JSON rather than the simple format. We'll talk about this more later in the article.
Winston logging levels
As described in greater detail in the documentation, Winston provides different logging levels with associated integer values. In our example above, we utilized the "info" and "debug" logging levels. By default, Winston uses logging levels utilized by npm:
{ error: 0, warn: 1, info: 2, verbose: 3, debug: 4, silly: 5 }
Logging levels benefit us since we can choose logging level thresholds to determine what logging messages will be displayed. For example, you might use a different logging level threshold for logging to the console versus logging to a file, or you might choose to temporarily increase the threshold level of logging messages to aid in troubleshooting.
Let's take our previous example and log a message using the silly
logging threshold:
'use strict';
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
level: 'debug',
format: format.simple(),
// You can also comment out the line above and uncomment the line below for JSON format
// format: format.json(),
transports: [new transports.Console()]
});
logger.info('Hello world');
logger.debug('Debugging info');
logger.silly('Very verbose silly message');
When we invoke this code, we don't see the silly logger level message. ☹️ What's going on here?
We must also change the level
property in our createLogger
options parameter to increase our logging threshold from debug
to silly
and enable the silly
logging level to be logged to the output. Let's do that now:
'use strict';
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
// Change the level on the next line from 'debug' to 'silly' to enable messages logged
// with the silly logging threshold to be logged to the output.
level: 'silly',
format: format.simple(),
// You can also comment out the line above and uncomment the line below for JSON format
// format: format.json(),
transports: [new transports.Console()]
});
logger.info('Hello world');
logger.debug('Debugging info');
logger.silly('Very verbose silly message');
Excellent - now we can see that silly logging message! 😺
Logging levels can be very helpful to us. We could, for example, choose to dial back the level
property to a different logging threshold such as warn
if we only wanted to emit logging messages that are warn
or lower (which would include error
) to view a smaller subset of logging messages.
Winston provides other types of logging levels such as syslog
levels, and you can even create your own custom levels. We will use the default npm levels in this tutorial, but, rest assured, other options are available if you need them.
Colorize Winston console log output
Why not colorize our console log output to add an additional dimension of fun 🎉to our projects? Here's how it's done:
'use strict';
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
level: 'debug',
format: format.combine(format.colorize(), format.simple()),
transports: [new transports.Console()]
});
logger.info('Hello world');
logger.debug('Debugging info');
In this example, we modify the Winston "transport" for the console to add an additional format.colorize()
function. To utilize multiple formats, Winston requires that we wrap the format functions inside a format.combine
function as shown above. Run this example, and you will see colorized output in your console that varies by the logging level of the message.
Add timestamps to the log entries
Adding a timestamp to each log entry will prove to be very useful for IoT applications—or any application for that matter. Here's the code needed to bring timestamps to life:
'use strict';
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
level: 'debug',
format: format.combine(
format.colorize(),
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
),
transports: [new transports.Console()]
});
logger.info('Hello world');
logger.debug('Debugging info');
To enable timestamps to appear in the log entry, we change our format from format.simple()
to format.printf
. We also take it up a notch by specifying a timestamp format to gain precise control over the format of the timestamp.
Log to a file in addition to the console
We now begin to see the power of Winston transports in action as we add a second transport to log to a file in addition to logging to the console:
'use strict';
const { createLogger, format, transports } = require('winston');
const fs = require('fs');
const path = require('path');
const env = process.env.NODE_ENV || 'development';
const logDir = 'log';
// Create the log directory if it does not exist
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
const filename = path.join(logDir, 'results.log');
const logger = createLogger({
// change level if in dev environment versus production
level: env === 'development' ? 'debug' : 'info',
format: format.combine(
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
),
transports: [
new transports.Console({
level: 'info',
format: format.combine(
format.colorize(),
format.printf(
info => `${info.timestamp} ${info.level}: ${info.message}`
)
)
}),
new transports.File({ filename })
]
});
logger.info('Hello world');
logger.warn('Warning message');
logger.debug('Debugging info');
As shown above, we create a log directory if it does not exist. We also add the second transport for a file. Notice also that we can specify different levels (thresholds) for our transports. In this context, if we are running in a development environment, we use a level of debug
and thus send more messages to the log file than we send to the console which is configured with a level of info
.
When you run this code, you should see a log file get created before your eyes. Feel free to experiment with the levels when writing log entries and see how the log output varies between the console and the log file.
Log to console in standard text format and log to file in JSON format
We can also tailor the individual transports to log to the console using standard text format and log to a file using JSON format. The JSON file format might be handy if you had some tools for filtering the data using JSON format, for example.
'use strict';
const { createLogger, format, transports } = require('winston');
const fs = require('fs');
const path = require('path');
const env = process.env.NODE_ENV || 'development';
const logDir = 'log';
// Create the log directory if it does not exist
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
const filename = path.join(logDir, 'results.log');
const logger = createLogger({
// change level if in dev environment versus production
level: env === 'development' ? 'debug' : 'info',
format: format.combine(
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
format.json()
),
transports: [
new transports.Console({
level: 'info',
format: format.combine(
format.colorize(),
format.printf(
info => `${info.timestamp} ${info.level}: ${info.message}`
)
)
}),
new transports.File({ filename })
]
});
logger.info('Hello world');
logger.warn('Warning message');
logger.debug('Debugging info');
In this case, we change the global value for log format to format.json()
and use format.printf
to specify a different format in the transport for the console.
Log to a file that rotates daily
As a final example, we will add an npm module to automatically create a new log file every day. This same functionality can be accomplished other ways including through the use of the logrotate command in the Linux world; however, we will demonstrate a way to make this happen here in the context of Winston.
We're going to leverage the winston-daily-rotate-file npm module to make this happen. We will first install the winston-daily-rotate-file
package from npm using the following command:
$ npm install --save winston-daily-rotate-file
After the npm install is complete, we are ready to implement the code for the daily log file:
'use strict';
const { createLogger, format, transports } = require('winston');
require('winston-daily-rotate-file');
const fs = require('fs');
const path = require('path');
const env = process.env.NODE_ENV || 'development';
const logDir = 'log';
// Create the log directory if it does not exist
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
const dailyRotateFileTransport = new transports.DailyRotateFile({
filename: `${logDir}/%DATE%-results.log`,
datePattern: 'YYYY-MM-DD'
});
const logger = createLogger({
// change level if in dev environment versus production
level: env === 'development' ? 'verbose' : 'info',
format: format.combine(
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
),
transports: [
new transports.Console({
level: 'info',
format: format.combine(
format.colorize(),
format.printf(
info => `${info.timestamp} ${info.level}: ${info.message}`
)
)
}),
dailyRotateFileTransport
]
});
logger.debug('Debugging info');
logger.verbose('Verbose info');
logger.info('Hello world');
logger.warn('Warning message');
logger.error('Error info');
In this code example, we change our file transport to use the winston-daily-rotate-file
transport that we installed above. When instantiating the dailyRotateFileTransport
we are also able to supply options to control the format and location of our log file. Great stuff!
You will also notice in this example that I added some additional log messages at various log levels and changed the file transport to use a logging level of verbose
if the machine is in a development environment. You can experiment with these to solidify your understanding of Winston logging levels and observe how the logging messages appear (or don’t appear) on the console and in the log file.
You will find more Winston usage examples in the examples directory on the Winston GitHub repo.
Bonus - Add custom text to log entries for name of file calling Winston logger
One of my article readers (Bob) asked for help in the comments about how to include the name of the file calling the logger. Let's help Bob get a victory and expand our knowledge of Winston too!
We'll start with logging just to the console and expand to file logging in a minute. First, we'll create a Node module by adding a file named logger.js
with the following contents:
'use strict';
const { createLogger, format, transports } = require('winston');
const path = require('path');
const logger = createLogger({
level: 'debug',
format: format.combine(
format.label({ label: path.basename(process.mainModule.filename) }),
format.colorize(),
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.printf(
info => `${info.timestamp} ${info.level} [${info.label}]: ${info.message}`
)
),
transports: [new transports.Console()]
});
module.exports = logger;
We have now encapsulated the Winston logger functionality in a Node module that we can call from other files. As part of this code, we also introduce a Winston function we haven't covered called format.label
:
format.label({ label: path.basename(process.mainModule.filename) }),
This label function provides some additional text for Winston to display in the log entry. Inside this function, we include an expression that provides the name of the file calling our logger.js
module with the help of process.mainModule.filename
. This piece of code has been through a couple of iterations. Thanks to Sree Divya Akula and Filippo for providing comments below to notify me of issues. Hopefully, all is good now!
A couple of lines down, we include ${info.label}
in our position of choice to render our custom label contents in every log entry.
format.printf(
// We display the label text between square brackets using ${info.label} on the next line
info => `${info.timestamp} ${info.level} [${info.label}]: ${info.message}`
)
Let's see if the module works!
Create a file called index.js
in the same directory as logger.js
with the following contents:
const logger = require('./logger');
logger.info('Hello world');
logger.debug('Debugging info');
Next, invoke index.js
.
$ node index.js
2018-12-06 19:55:37 info [index.js]: Hello world
2018-12-06 19:55:37 debug [index.js]: Debugging info
Bam! Our custom text of [index.js]
, the name of the calling file, is now included with every log entry.
For the sake of completeness, I will also show you an example that logs to both the console and to a file. Replace logger.js
with the following contents:
'use strict';
const { createLogger, format, transports } = require('winston');
const fs = require('fs');
const path = require('path');
const env = process.env.NODE_ENV || 'development';
const logDir = 'log';
// Create the log directory if it does not exist
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
const filename = path.join(logDir, 'results.log');
const 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' })
),
transports: [
new transports.Console({
format: format.combine(
format.colorize(),
format.printf(
info =>
`${info.timestamp} ${info.level} [${info.label}]: ${info.message}`
)
)
}),
new transports.File({
filename,
format: format.combine(
format.printf(
info =>
`${info.timestamp} ${info.level} [${info.label}]: ${info.message}`
)
)
})
]
});
module.exports = logger;
I'll point out just a couple of items here. We are able to include the format.label
function near the top of the createLogger
function and share it among both the Console
and File
transports. Since the Console
transport uses format.colorize
, we must declare a separate format.printf
function in each Transport so we can omit format.colorize
in the File transport and avoid rendering funky ANSI escape codes in logs that are saved to files.
Finally, create a file called index2.js
with the following contents:
const logger = require('./logger');
logger.info('Hello world');
logger.debug('Debugging info');
Invoke index2.js
....
$ node index2.js
2018-12-06 20:14:21 info [index2.js]: Hello world
2018-12-06 20:14:21 debug [index2.js]: Debugging info
...and lo and behold! We should see output logged to both the console and to a file with today's date—including the name of our calling program (index2.js
).
Bob, I hope this answers your question. Thanks for taking the time to ask it as you have ultimately helped all of us deepen our Winston knowledge!
Conclusion
We’ve only scratched the surface of the many features in Winston. I hope this guide has made you a little smarter and equipped you to use Winston for your Node.js file logging endeavors!
Follow @thisDaveJ (Dave Johnson) on Twitter to stay up to date with the latest tutorials and tech articles.
Additional articles
Guide to Installing Node.js on a Raspberry Pi Making Interactive Node.js Console Apps That Listen for Keypress Events How to Watch for Files Changes in Node.js How to Count Unique Items in JavaScript Arrays
Last updated Jun 25 2019
Thanks for your amazing and clear tutorial!
You are very welcome. I appreciate your positive feedback!
Great tutorial. Very easy to follow along. Thanks!
Crystal clear Definition. Thanks a lot!!
great tutorial
Got me started in just 10 minutes. Thanks for this awesome tutorial.
Thanks a lot sir
Grate tutorial for starting using Winston and nodejs logging. Thank you.
Thank you, it was helpful!
Very helpful and well written. Thank you
You are very welcome, Alexandros. Thanks for taking the time to provide the positive feedback!
Thanks a ton 🙂
Its Nice. Very Helpful
Hi, it is recommended to use node-file-logger.
Its light, easy to use and has good documentation.
Install it by using
npm install node-file-logger –save
Here is the documentation.
https://www.npmjs.com/package/node-file-logger
any idea why my log file ends up having this name format? -results.log.yyyy-10-Mo
Please try again, Kieran. I updated the tutorial to ensure everything works with Winston 3.x. This should resolve the issue you were experiencing with the log file naming format. Let me know if you continue to have issues.
This is a great help but it is unfortunately outdated with Winston 3.x.
Thanks for the heads up, Louis! I updated this tutorial to ensure everything works with Winston 3.x.
Great tutorial. Easy to follow and really usefull. Thank you very much for writing.
Thanks a lot. It is more clearly than documentation.
Thanks Dear
very clear and good understanding.
Thanks
Great. very helpful
Thanks for the great tutorial. It is very helpful. Currently, I’m facing with a problem. I’m trying to include the path of the file that the logger is being called. Do you have any idea? Is there any option in Winston that I can use to include the path?
Thanks,
Hi Bob – great question! I created a bonus section in the tutorial to answer it. See here.
That is great! Thanks a lot.
Thanks, clear concise and up to date! You should include how to use it with express and combine it with Morgan logging, since I feel like that’s a really common use case. But I really like how you gradually introduce features instead of throwing in a lot of features and other npm modules. Super helpful
Seems like there is an error in displaying the file name of the caller function in the logger. Only one file name gets displayed (The first file name say x.js) always irrespective of whichever file calls the logger.
Sree, thanks for pointing out this bug! I updated the code in the “Bonus – Add custom text to log entries for name of file calling Winston logger” section to resolve the issue.
Very well explained tutorial, Thanks a lot.
Can we also add line no in the logs ?
Some recommendation or update to use with typescript? Thanks!
Anyway to hook this up with morgan or does winston have a way to log the request, response, and related data?
Travis, you can follow the instructions from this article to integrate winston with morgan. As another option, you can use express-winston in lieu of morgan to provide the middleware for the request and error logging of your express.js application.
Thanks Dave. I am able to create a logging utility in 15 mins using your article. I am able log levels – info, error and warning. But I am not able to log debug, verbose and silly levels. I am not getting any exceptions, so could not find the root cause of this issue. Can you help. I am using winston v3.2.1.
Justin, excellent question. You must also change the level property in the
createLogger
options parameter to increase the logging threshold from “debug” to “silly” and enable the silly logging level to be logged to the output. I added a code example and explanatory text under the Winston logging levels section above because this subtlety is likely to confuse other people too – including me. 🙂you really write a great article but you can add some other useful things which we can do with Winston like saving log into database or sending emails on errors or send logs to aws
Thanks for this tutorial, it helps me a lot! I was looking how to print the filename of the caller to the log and your example works.
But I found an issue doing this way: any call to the “createLogger” (1 per file usually) will instantiate a new logger that add an EventEmitter listener, bringing the application into the “MaxListenersExceededWarning”.
Can you confirm this or it’s just my personal implementation? Thx
Filippo, thanks for taking the time to point out this issue! You are exactly right. I updated the code and I’m expecting the newest version above will no longer cause max listeners exceeded warnings. Let me know if you experience otherwise.
Thanks for Tutorial . It really help me as Node.js beginner to implement logging in the application. But I faced issue here, it prints same file name as a label even logger called from different file. Where I will get the latest code?
Thanks for the feedback. I’m not sure why it does not work for you. I just now tested the code in my tutorial listed under the Bonus section and it works. Be sure you use the Logger.js code listed in the Bonus section and then create a couple .js files that reference the Logger.js (const logger = require(‘./logger’) as described in the tutorial to test. You may want to copy and paste the Logger.js code to make sure you have included the full syntax.
This is one of the best articles on the web, I found so far.
When running `const logger = require(‘./logger’)(__filename);` I got “require(…) is not a function”.
It seems you need to wrap the exported logger into a function like:
// logger.js
const logger = function(callingFile) {
return createLogger({…});
}
module.exports = logger;
Also, it would be great to have the logger to print stack trace.
Thanks for the amazing article!
Thanks for pointing out this issue! The use of the
__filename
parameter was a remnant from a previous version of this tutorial. Change the line fromconst logger = require(‘./logger’)(__filename);
toconst logger = require(‘./logger’);
and all should be good.I have multiple js files laid out across dirs in my project where function from /file1.js calls functions in /file2.js which in turn calls functions in /file3.js, in such scenario label with process.mainModule.filename always returns file1.js from where node application was started and thus logs coming out from file2.js and file3.js are labelled with the same name ‘file1.js’ which is not undesired. I would appreciate any suggestions to handle this scenario. By the way, logger.js in this case is in /logger.js (next to file1.js), thus file2.js and file3.js are acquiring the logger using ‘const logger = require(‘../logger’);’ statement.
Project Structure:
app-root: file1.js, logger.js
app-root/dir1: file2.js
app-root/dir3:file3.js
Thank you for the bonus section, It saved my day.
Nice tutorial well written with good explanations, no assumed knowledge
An outstanding tutorial about Winston logging. I can’t ask better than this. Thank you.
A very nice and clear explanation. Thanks
best tuturoial i seen for this.