nodejs auto maintain winston log - node.js

I'm trying to integrate winston to a nodeJs Express app. It works but I don't know how could it be auto maintained. I wanted something like when it reach, for example 50000 rows, it delete the first ones before adding the new ones for terms of space. The idea is doing that without something like cron. Is it possible?
Here is my testing code:
//Winston logger
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, label, printf } = format;
const winstonFormat = printf(info => {
return `${info.timestamp} [${info.label}] ${info.level}: ${info.message}`;
});
const logger = createLogger({
level: 'info',
//maxsize:'10000', //It doesn't works for me
format: combine(
label({ label: config.environment }),
timestamp(),
winstonFormat
),
transports: [
new transports.File({ filename: 'error.log', level: 'error' }),
new transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new transports.Console({
format: format.simple()
}));
}

You're looking for log rotation, so googling "winston log rotation" points to https://github.com/winstonjs/winston-daily-rotate-file, which is probably just the ticket for you.

Related

How to force nodejs winston log to file before process exit

I am using winston 3 to log my data. But sometimes it didn't log the error before process exit.
The following process will exit, without log to the logfile.log:
const winston = require('winston');
winston.add(new winston.transports.File({
filename: 'logfile.log'
}));
winston.info('please log me in');
process.exit(1);
One of attempted solution is used callback:
const winston = require('winston');
const logger = new winston.createLogger({
new winston.transports.File({
filename: 'logfile.log'
}));
winston.info('please log me in', () => {
process.exit(1);
});
setTimeout(() => {}, 100000000000); // pause the process until process.exit is call
But Winston 3.x have callback issue, so the above code won't work, the process will not exit.
I am looking for a workable solution? Any help will be greatly appreciated. My OS is Ubuntu 16.04, Node 10.17.
Edit 1:
I also have try Prabhjot Singh Kainth's suggestion use finish event to trigger process exit:
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.File({
filename: 'logfile.log'
})
]
});
logger.info('please log me in');
logger.on('finish', () => {
process.exit();
});
logger.end();
setTimeout(() => {}, 100000000000); // pause the process until process.exit is call
In the above case, the process will exit, but no log file will be created.
How about this one?
logger.info('First message...')
logger.info('Last message', () => process.exit(1))
Finally, I find a workable solution. Instead of listening to logger finish event, it should listen to file._dest finish event. But file._dest is only created after file open event. So it need to wait for file open event in initialization process.
const winston = require('winston');
const file = new winston.transports.File({
filename: 'logfile.log'
});
const logger = winston.createLogger({
transports: [file]
});
file.on('open', () => { // wait until file._dest is ready
logger.info('please log me in');
logger.error('logging error message');
logger.warn('logging warning message');
file._dest.on('finish', () => {
process.exit();
});
logger.end();
});
setTimeout(() => {}, 100000000000); // pause the process until process.exit is call
Each instance of winston.Logger is also a Node.js stream.
A finish event will be raised when all logs have flushed to all transports after the stream has been ended.
Just try using this code :
const transport = new winston.transports.Console();
const logger = winston.createLogger({
transports: [transport]
});
logger.on('finish', function (info) {
// All `info` log messages has now been logged
});
logger.info('CHILL WINSTON!', { seriously: true });
logger.end();
The answer from #WanChap was good, but not when there is multiple file transports. I will post my entire logging solution below, which handles exiting the process correctly, including support for Development and Production.
The only caveat is that it does not work with the exceptions log at the moment, but it will work with every other transport based log. If anyone figures out how to make this work with the exception log as well, please post in the comments.
First, you want to create a folder in the same directory that your file that is doing the logs is in, and call it logger. In this folder, create three .js files, one called dev-logger.js, the other prod-logger.js, and finally index.js
In node, when you require a directory, it will assume you want to load the index.js file.
TheFileYouWantToLogFrom.js
const logger = require('./logger');
logger.info("it works!");
index.js
This file will look at your node process environment state and use the correct logger.
const buildDevLogger = require('./dev-logger');
const buildProdLogger = require('./prod-logger');
let logger = null;
if(process.env.NODE_ENV === 'development'){
logger = buildDevLogger();
}else{
logger = buildProdLogger();
}
module.exports = logger;
You can test this later, by simply calling your node process (in my case cron.js) in your terminal, by saying something like NODE_ENV=production node cron.js or NODE_ENV=development node cron.js
dev-logger.js
const { format, createLogger, transports} = require('winston');
const { timestamp, combine, printf, errors } = format;
var numOpenTransports = 0;
function buildDevLogger(){
const logFormat = printf(({ level, message, timestamp, stack, durationMs }) => {
return `${timestamp} ${level}: ${stack || message}${durationMs ? " | "+durationMs+'ms' : ''}`;
});
//For info on the options go here.
//https://github.com/winstonjs/winston/blob/master/docs/transports.md
var options = {
console: {
handleExceptions: true,
level: 'debug',
format: combine(
format.colorize(),
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
logFormat
),
},
combined: {
filename: 'logs/dev/combined.log',
maxsize: 10000000,
maxFiles: 10,
tailable:true,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
logFormat
),
},
error: {
filename: 'logs/dev/error.log',
level: 'error',
maxsize: 10000000,
maxFiles: 10,
tailable:true ,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
logFormat
),
},
exception : {
filename: 'logs/dev/exceptions.log',
maxsize: 10000000,
maxFiles: 10,
tailable:true,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
logFormat
),
}
};
function fileFinished(){
numOpenTransports--;
if(numOpenTransports==0){
process.exit(0);
}
}
var combinedFile = new transports.File(options.combined);
var errorFile = new transports.File(options.error);
var exceptionFile = new transports.File(options.exception);
combinedFile.on('open', () => { // wait until file._dest is ready
numOpenTransports++;
combinedFile._dest.on('finish', () => {
fileFinished();
});
});
errorFile.on('open', () => { // wait until file._dest is ready
numOpenTransports++;
errorFile._dest.on('finish', () => {
fileFinished();
});
});
return createLogger({
defaultMeta: {service:'cron-dev'},
transports: [
new transports.Console(options.console),
combinedFile,
errorFile,
],
exceptionHandlers: [
exceptionFile
]
});
}
module.exports = buildDevLogger;
prod-logger.js
const { format, createLogger, transports} = require('winston');
const { timestamp, combine, errors, json } = format;
var numOpenTransports = 0;
function buildProdLogger(){
var options = {
console: {
handleExceptions: true,
level: 'debug',
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
json()
),
},
combined: {
filename: 'logs/prod/combined.log',
maxsize: 10000000,
maxFiles: 10,
tailable:true,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
json()
),
},
error: {
filename: 'logs/prod/error.log',
level: 'error',
maxsize: 10000000,
maxFiles: 10,
tailable:true ,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
json()
),
},
exception : {
filename: 'logs/prod/exceptions.log',
maxsize: 10000000,
maxFiles: 10,
tailable:true,
format: combine(
timestamp({format: 'YYYY-MM-DD HH:mm:ss'}),
errors({stack: true}),
json()
),
}
};
function fileFinished(){
numOpenTransports--;
if(numOpenTransports==0){
process.exit(0);
}
}
var combinedFile = new transports.File(options.combined);
var errorFile = new transports.File(options.error);
var exceptionFile = new transports.File(options.exception);
combinedFile.on('open', () => { // wait until file._dest is ready
numOpenTransports++;
combinedFile._dest.on('finish', () => {
fileFinished();
});
});
errorFile.on('open', () => { // wait until file._dest is ready
numOpenTransports++;
errorFile._dest.on('finish', () => {
fileFinished();
});
});
return createLogger({
defaultMeta: {service:'cron-prod'},
transports: [
new transports.Console(options.console),
combinedFile,
errorFile,
],
exceptionHandlers: [
exceptionFile
]
});
}
module.exports = buildProdLogger;
The way the dev and prod logger files work is by using the numOpenTransports variable to keep track of which 'files transports' are currently open, and once they are all closed, then, and only then, exit the process.

winston logging to separate files

I'm new in node.js and have such winson config
const winston = require('winston');
const transports = [];
if (process.env.NODE_ENV !== 'development') {
transports.push(new (winston.transports.DailyRotateFile)({
filename: path.resolve(__dirname, '../logs/%DATE%/source1.log'),
level: 'info',
datePattern: 'YYYY-MM-DD',
}));
transports.push(new (winston.transports.DailyRotateFile)({
filename: path.resolve(__dirname, '../logs/%DATE%/source2.log'),
level: 'info',
datePattern: 'YYYY-MM-DD',
}));
}
let logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports,
exitOnError: true,
});
logger.log({
date: (new Date()).toString(),
level,
message,
source,
});
and I need logging to separate files depend on source (all source1 logs in file ../logs/%DATE%/source1.log' and same with source2)
How can do this?
by GoogleTraduction FR -> EN
Hello !
If you want your logs to be separated into several separate files I can offer you the following solution.
Note: this solution is based on log levels.
Winston: 3.0.0
Nodejs: 9.3.0
./logger/index.js
const winston = require('winston');
module.exports = (dirname) => {
const loggers = {
info: null,
warn: null,
query: null,
errDat: null,
errFun: null,
errInt: null,
};
for (let level of Object.keys(loggers)) {
loggers[level] = winston.createLogger({
levels: { [level]: 0 },
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(info =>
`${info.timestamp}\t[${info.level}]\t${info.message}`
),
),
transports: [
new winston.transports.Console({
level
}),
new winston.transports.File({
dirname,
filename: `${level}.log`,
level,
format: winston.format.json(),
}),
]
})[level];
}
return loggers;
};
Other file
const path = require('path');
const { info, query, errInt, errDat, errFun } = require('./logger')(
path.join(__dirname, 'logs')
);
info(`Test`);
query(`Test`);
errDat(`Test`);
errInt(`Test`);
errFun(`Test`);

How do I change my node winston JSON output to be single line

When I create a nodejs winston console logger and set json:true, it always output JSON logs in multiline format. If I pipe these to a file and try to grep that file, my grep hits only include part of the log line. I want winston to output my log lines in JSON format, but not to pretty print the JSON
Here is my config (coffeescript, apologies):
winston = require 'winston'
logger = new (winston.Logger)(
transports: [
new winston.transports.Console({
json: true
})
]
)
And some sample output:
{
"name": "User4",
"level": "info",
"message": "multi line whyyyyy"
}
winston 3.x (current version)
Default formatter
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.json(),
transports: [
new winston.transports.Console()
]
});
Example
const test = { t: 'test', array: [1, 2, 3] };
logger.info('your message', test);
// logger output:
// {"t":"test","array":[1,2,3],"level":"info","message":"your message"}
Custom formatter
const winston = require('winston');
const { splat, combine, timestamp, printf } = winston.format;
// meta param is ensured by splat()
const myFormat = printf(({ timestamp, level, message, meta }) => {
return `${timestamp};${level};${message};${meta? JSON.stringify(meta) : ''}`;
});
const logger = winston.createLogger({
format: combine(
timestamp(),
splat(),
myFormat
),
transports: [
new winston.transports.Console()
]
});
Example:
const test = { t: 'test', array: [1, 2, 3] };
// NOTE: wrapping object name in `{...}` ensures that JSON.stringify will never
// return an empty string e.g. if `test = 0` you won't get any info if
// you pass `test` instead of `{ test }` to the logger.info(...)
logger.info('your message', { test });
// logger output:
// 2018-09-18T20:21:10.899Z;info;your message;{"test": {"t":"test","array":[1,2,3]}}
winston 2.x (legacy version)
It seems that the accepted answer is outdated. Here is how to do this for current winston version (2.3.1):
var winston = require('winston');
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({
json: true,
stringify: (obj) => JSON.stringify(obj),
})
]
})
Note the parenthesis around winston.transports.Console.
The winston transports provide a way to override the stringify method, so by modifying the config above I got single line JSON output.
New config:
winston = require('winston')
logger = new (winston.Logger)({
transports: [
new winston.transports.Console({
json: true,
stringify: (obj) => JSON.stringify(obj)
})
]
})
"winston": "^3.0.0"
function createAppLogger() {
const { combine, timestamp, printf, colorize } = format;
return createLogger({
level: 'info',
format: combine(
colorize(),
timestamp(),
printf(info => {
return `${info.timestamp} [${info.level}] : ${JSON.stringify(info.message)}`;
})
),
transports: [new transports.Console()]
});
}
Output:
2018-08-11T13:13:37.554Z [info] : {"data":{"hello":"Hello, World"}}
On "winston": "^3.2.1"
This is works great for me
const {createLogger, format} = require('winston');
// instantiate a new Winston Logger with the settings defined above
var logger = createLogger({
format: format.combine(
format.timestamp(),
// format.timestamp({format:'MM/DD/YYYY hh:mm:ss.SSS'}),
format.json(),
format.printf(info => {
return `${info.timestamp} [${info.level}] : ${info.message}`;
})
),
transports: [
new winston.transports.File(options.file),
new winston.transports.Console(options.console)
],
exitOnError: false, // do not exit on handled exceptions
});
winston.format.printf(
info => `${info.timestamp} ${info.level}: ${JSON.stringify(info.message, null, 2)}`))
will pretty print the json object

how to use winston to setup log in a sub directory instead of root directory?

I want to setup logs for my nodejs project inside a directory named logs
as per documentation here
i am doing :
winston.add(winston.transports.File, { filename: 'logs/mylogs.log' });
But it is doing nothing.
How do i achieve the same?
Put the below code in your server file.
var winston = require('winston');
var fs = require( 'fs' );
var path = require('path');
var logDir = 'log'; // directory path you want to set
if ( !fs.existsSync( logDir ) ) {
// Create the directory if it does not exist
fs.mkdirSync( logDir );
}
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({
colorize: 'all'
}),
new (winston.transports.File)({filename: path.join(logDir, '/log.txt')})
]
});
logger.info("Anything you want to write in logfile");
Winston provides option to set dirname when creating log file.
By default Winston creates directory to project root:
new transports.File({
filename: 'errors.log',
dirname: 'logs',
level: 'error',
})
If you want your logs for example inside src directory:
new transports.File({
filename: 'errors.log',
dirname: './src/logs',
level: 'error',
})
Full code example:
import { createLogger, transports, format } from 'winston'
const { timestamp, combine, json, errors } = format
const buildLogger = () => {
return createLogger({
format: combine(
timestamp(),
errors({stack: true}),
json()
),
defaultMeta: {service: "your-service"},
transports: [
new transports.File({
filename: 'events.log',
dirname: 'logs',
level: 'info',
}),
new transports.File({
filename: 'errors.log',
dirname: 'logs',
level: 'error',
}),
]
})
}
export default buildLogger
You should use a relative path (the dot and backslash):
winston.add(winston.transports.File, { filename: './logs/mylogs.log' });

Node.js - How to add timestamp to logs using Winston library?

I want to add timestamp to logs.
What is the best way to achieve this?
Thanks.
Above answers did not work for me. In case you are trying to add timestamp to your logs using the latest version of Winston - 3.0.0-rc1, this worked like charm:
const {transports, createLogger, format} = require('winston');
const logger = createLogger({
format: format.combine(
format.timestamp(),
format.json()
),
transports: [
new transports.Console(),
new transports.File({filename: 'logs/error/error.log', level: 'error'}),
new transports.File({filename: 'logs/activity/activity.log', level:'info'})
]
});
I used 'format.combine()'. Since I needed timestamp on all my transports, I added the formatting option within the createLogger, rather than inside each transport. My output on console and on file (activity.log) are as follows:
{"message":"Connected to mongodb","level":"info","timestamp":"2018-02-01T22:35:27.758Z"}
{"message":"Connected to mongodb","level":"info","timestamp":"2018-02-01T22:35:27.758Z"}
We can add formatting to this timestamp in 'format.combine()' as usual using:
format.timestamp({format:'MM-YY-DD'})
I was dealing with the same issue myself. There are two ways I was able to do this.
When you include Winston, it usually defaults to adding a Console transport. In order to get timestamps to work in this default case, I needed to either:
Remove the console transport and add again with the timestamp option.
Create your own Logger object with the timestamp option set to true.
The first:
var winston = require('winston');
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {'timestamp':true});
The second, and cleaner option:
var winston = require('winston');
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({'timestamp':true})
]
});
Some of the other options for Console transport can be found here:
level: Level of messages that this transport should log (default 'debug').
silent: Boolean flag indicating whether to suppress output (default false).
colorize: Boolean flag indicating if we should colorize output (default false).
timestamp: Boolean flag indicating if we should prepend output with timestamps (default false). If function is specified, its return value will be used instead of timestamps.
We can do like this also
var winston = require('winston');
const { createLogger, format, transports } = require('winston');
var config = require('../configurations/envconfig.js');
var loggerLevel = process.env.LOGGERLEVEL || config.get('LOGGERLEVEL');
var logger = winston.createLogger({
format: format.combine(
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`+(info.splat!==undefined?`${info.splat}`:" "))
),
transports: [
new (winston.transports.Console)({ level: loggerLevel }),
]
});
module.exports = logger;
You can use built-in util and forever to achieve logging with timestap for your nodejs server.
When you start a server add log output as part of the parameter:
forever start -ao log/out.log server.js
And then you can write util in your server.js
server.js
var util = require('util');
util.log("something with timestamp");
The output will look something like this to out.log file:
out.log
15 Mar 15:09:28 - something with timestamp
I took Biswadev's answer and created a stringified JSON object. This way if i need to process the logs later it will be in a well structured format.
const winston = require('winston');
const { createLogger, format, transports } = require('winston');
const dotenv = require('dotenv');
dotenv.config();
var logger = createLogger({
level: 'info',
format: format.combine(
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
format.printf((info) =>
JSON.stringify({
t: info.timestamp,
l: info.level,
m: info.message,
s: info.splat !== undefined ? `${info.splat}` : '',
}) + ','
)
),
});
if (process.env.NODE_ENV !== 'PRODUCTION') {
logger.add(new transports.Console({ format: winston.format.cli() }));
// Turn these on to create logs as if it were production
// logger.add(new transports.File({ filename: 'log/output/error.log', level: 'error' }));
// logger.add(new transports.File({ filename: 'log/output/warn.log', level: 'warn' }));
// logger.add(new transports.File({ filename: 'log/output/info.log', level: 'info' }));
} else {
logger.add(new transports.File({ filename: 'log/output/error.log', level: 'error' }));
logger.add(new transports.File({ filename: 'log/output/warn.log', level: 'warn' }));
logger.add(new transports.File({ filename: 'log/output/info.log', level: 'info' }));
}
module.exports = {
logger,
};
Usage:
app.listen(port, () => logger.info(`app is running on port ${port}`));
Output:
info.log file:
{"t":"2020-08-06 08:02:05","l":"info","m":"app is running on port 3001","s":""},
Console:
info: app is running on port 3001
Although I'm not aware of winston, this is a suggestion. I use log4js for logging & my logs by default look like this
[2012-04-23 16:36:02.965] [INFO] Development - Node Application is running on port 8090
[2012-04-23 16:36:02.966] [FATAL] Development - Connection Terminated to '127.0.0.1' '6379'
Development is the environment of my node process & [INFO|FATAL] is log level
Maintaining different profiles for logging is possible in log4js. I have Development & Production profiles. Also there are logger types like rolling file appender, console appender, etc. As a addon your log files will be colorful based on the log level [Trace, Info, Debug, Error, Fatal] ;)
log4js will override your console.log It is a configurable parameter now in 0.5+
we could use console-stamp to add timestamp and log level to the existing console: require('console-stamp')(console, '[yyyy-mm-dd HH:MM:ss.l]')
See https://github.com/starak/node-console-stamp for the details
Sometimes default timestamp format can be not convenient for you.
You can override it with your implementation.
Instead of
var winston = require('winston');
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({'timestamp':true})
]
});
you can write
var winston = require('winston');
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({
'timestamp': function() {
return <write your custom formatted date here>;
}
})
]
});
See https://github.com/winstonjs/winston#custom-log-format for the details
Another solution is wrapping the logger into a file that exports some functions like logger.info(), logger.error(), etc. then you just pass an extra key to be sent on every message log.
loggerService.js
const logger = winston.createLogger({ ... })
function handleLog(message, level) {
const logData = {
timestamp: Date.now(),
message,
}
return logger[level](logData)
}
function info(message) {
handleLog(message, 'info')
}
function error(message) {
handleLog(message, 'error')
}
function warn(message) {
handleLog(message, 'warn')
}
module.exports = {
info,
error,
warn
}
whatever-file.js
const logger = require('./services/loggerService')
logger.info('Hello World!')
your-log.log
{"timestamp":"2019-08-21 06:42:27","message":"Hello World!","level":"info"}

Resources