I have a project that I need log files for, which is why I want to use winston. But over its runtime it may crash at some point, so I was doing some testing:
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'combined.log' })
]
});
let i=100000;
while(i-->0){
logger.info('Hello world');
}
throw new Error('Error');
This basically just prints hello world 100000 times and than errors out. The problem I have is that combined.log is only written to if the program doesn't crash (presumably at the very end). So why is winston only writing to the actual log once its done running. And how can I make it write to the file even if there may be an exception?
EDIT:
interestingly, it works if you add a 1 second delay between the error
const winston = require('winston');
const fsp = require('fs').promises;
let a=async ()=>{
try{
await fsp.unlink('combined.log');
}catch(e){
}
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'combined.log' })
]
});
let i=100000;
while(i-->0){
logger.info('Hello world');
}
//wait 1 seconds
await new Promise((resolve)=>setTimeout(resolve,1000));
// await new Promise((resolve,reject)=>{resolve()});
throw new Error('Error');
}
a()
Use handleExceptions: true
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.Console({handleExceptions: true}),
new winston.transports.File({ filename: 'combined.log',handleExceptions: true})
]
});
let i=100000;
while(i-->0){
logger.info('Hello world');
}
throw new Error('Error');
Although, really, you should always catch any exceptions etc
i need to add file name to pino-pretty line output,
now i'm using:
const pino = require('pino');
const logger = pino({
prettyPrint: {
colorize: true,
translateTime: 'yyyy-mm-dd HH:MM:ss',
ignore: 'pid,hostname'
}
})
and have this output:
[2020-05-14 16:25:45] INFO : Network is private
but i want something like this:
[2020-05-14 16:25:45] INFO myFile.js: Network is private
i.e. i want see filename in line witch was launch, i try play with customPrettifiers option but can't get hoped result,
for example i try this:
const pino = require('pino');
const path = require('path');
const logger = pino({
prettyPrint: {
colorize: true,
translateTime: 'yyyy-mm-dd HH:MM:ss',
ignore: 'pid,hostname',
customPrettifiers: {
filename: path.basename(__filename)
}
}
})
I think the closest you can get is as follows:
const path = require('path');
const pino = require('pino');
const logger = pino({
prettyPrint: {
// Adds the filename property to the message
messageFormat: '{filename}: {msg}',
// need to ignore 'filename' otherwise it appears beneath each log
ignore: 'pid,hostname,filename',
},
}).child({ filename: path.basename(__filename) });
Note that you can't style the filename differently to the message, but hopefully that's good enough.
It's probably also better to have a separate logger.js file where the default pino options are passed e.g.:
// logger.js
const logger = require('pino')({
prettyPrint: {
messageFormat: '{filename}: {msg}',
ignore: 'pid,hostname,filename',
},
});
module.exports = logger;
// file_with_logging.js
const parentLogger = require('./logger.js');
const logger = parentLogger.child({ filename: path.basename(__filename) });
Is it possible to get the line number and file for each log output ?
For example:
var winston = require('winston');
winston.log('info', 'some message!'); // this is at line 4 of myfile.js
should specify in log file that 'some message' came from myFile.js line 4.
You can pass the file name as label and you can get the file name from callingModule.
Create logger.js file and code like
var winston = require('winston');
var getLabel = function (callingModule) {
var parts = callingModule.filename.split('/');
return parts[parts.length - 2] + '/' + parts.pop();
};
module.exports = function (callingModule) {
return new winston.Logger({
transports: [
new winston.transports.Console({
label: getLabel(callingModule),
json: false,
timestamp: true,
depth:true,
colorize:true
})
]
});
};
Now Here your test file
var logger = require('./logger')(module);
function test() {
logger.info('test logger');
}
test();
and if you run test file than the output looks like
2017-07-08T07:15:20.671Z - info: [utils/test.js] test logger
winston didn't have the plan to do that due to performance concerns. Please check here for detailed information.
I tried https://github.com/baryon/tracer but it isn't good, e.g. line number is incorrect from time to time.
I found this code somewhere, yeah but It is working. Use it in a new winston.js and then requires that in any file.
var winston = require('winston')
var path = require('path')
var PROJECT_ROOT = path.join(__dirname, '..')
var appRoot = require('app-root-path');
const options = {
file: {
level: 'info',
filename: `${appRoot}/logs/app.log`,
handleExceptions: true,
json: true,
maxsize: 5242880, // 5MB
maxFiles: 5,
colorize: false,
timestamp: true
},
console: {
level: 'debug',
handleExceptions: true,
json: true,
colorize: true,
timestamp: true
}
};
var logger = new winston.Logger({
transports: [
new winston.transports.File(options.file),
new winston.transports.Console(options.console)
],
exitOnError: false // do not exit on handled exceptions
});
logger.stream = {
write: function (message) {
logger.info(message)
}
}
// A custom logger interface that wraps winston, making it easy to instrument
// code and still possible to replace winston in the future.
module.exports.debug = module.exports.log = function () {
logger.debug.apply(logger, formatLogArguments(arguments))
}
module.exports.info = function () {
logger.info.apply(logger, formatLogArguments(arguments))
}
module.exports.warn = function () {
logger.warn.apply(logger, formatLogArguments(arguments))
}
module.exports.error = function () {
logger.error.apply(logger, formatLogArguments(arguments))
}
module.exports.stream = logger.stream
/**
* Attempts to add file and line number info to the given log arguments.
*/
function formatLogArguments (args) {
args = Array.prototype.slice.call(args)
var stackInfo = getStackInfo(1)
if (stackInfo) {
// get file path relative to project root
var calleeStr = '(' + stackInfo.relativePath + ':' + stackInfo.line + ')'
if (typeof (args[0]) === 'string') {
args[0] = calleeStr + ' ' + args[0]
} else {
args.unshift(calleeStr)
}
}
return args
}
/**
* Parses and returns info about the call stack at the given index.
*/
function getStackInfo (stackIndex) {
// get call stack, and analyze it
// get all file, method, and line numbers
var stacklist = (new Error()).stack.split('\n').slice(3)
// stack trace format:
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
// do not remove the regex expresses to outside of this method (due to a BUG in node.js)
var stackReg = /at\s+(.*)\s+\((.*):(\d*):(\d*)\)/gi
var stackReg2 = /at\s+()(.*):(\d*):(\d*)/gi
var s = stacklist[stackIndex] || stacklist[0]
var sp = stackReg.exec(s) || stackReg2.exec(s)
if (sp && sp.length === 5) {
return {
method: sp[1],
relativePath: path.relative(PROJECT_ROOT, sp[2]),
line: sp[3],
pos: sp[4],
file: path.basename(sp[2]),
stack: stacklist.join('\n')
}
}
}
Source: https://gist.github.com/transitive-bullshit/39a7edc77c422cbf8a18
Update (for Winston 3.x)
I also created a gist for the following code:
const { format } = require('winston');
const { combine, colorize, timestamp, printf } = format;
/**
* /**
* Use CallSite to extract filename and number, for more info read: https://v8.dev/docs/stack-trace-api#customizing-stack-traces
* #param numberOfLinesToFetch - optional, when we want more than one line back from the stacktrace
* #returns {string|null} filename and line number separated by a colon, if numberOfLinesToFetch > 1 we'll return a string
* that represents multiple CallSites (representing the latest calls in the stacktrace)
*
*/
const getFileNameAndLineNumber = function getFileNameAndLineNumber (numberOfLinesToFetch = 1) {
const oldStackTrace = Error.prepareStackTrace;
const boilerplateLines = line => line &&
line.getFileName() &&
(line.getFileName().indexOf('<My Module Name>') &&
(line.getFileName().indexOf('/node_modules/') < 0));
try {
// eslint-disable-next-line handle-callback-err
Error.prepareStackTrace = (err, structuredStackTrace) => structuredStackTrace;
Error.captureStackTrace(this);
// we need to "peel" the first CallSites (frames) in order to get to the caller we're looking for
// in our case we're removing frames that come from logger module or from winston
const callSites = this.stack.filter(boilerplateLines);
if (callSites.length === 0) {
// bail gracefully: even though we shouldn't get here, we don't want to crash for a log print!
return null;
}
const results = [];
for (let i = 0; i < numberOfLinesToFetch; i++) {
const callSite = callSites[i];
let fileName = callSite.getFileName();
fileName = fileName.includes(BASE_DIR_NAME) ? fileName.substring(BASE_DIR_NAME.length + 1) : fileName;
results.push(fileName + ':' + callSite.getLineNumber());
}
return results.join('\n');
} finally {
Error.prepareStackTrace = oldStackTrace;
}
};
function humanReadableFormatter ({ level, message, ...metadata }) {
const filename = getFileNameAndLineNumber();
return `[${level}] [${filename}] ${message} ${JSON.stringify(metadata)}`;
}
const logger = winston.createLogger({
transports: [
new winston.transports.Console({
level: 'info',
handleExceptions: true,
humanReadableUnhandledException: true,
json: false,
colorize: { all: true },
stderrLevels: ['error', 'alert', 'critical', 'bizAlert'],
format: combine(
colorize(),
timestamp(),
humanReadableFormatter,
),
})
]
});
Original Answer (for Winston 2.x)
I'm using winston 2.x (but the same solution will work for winston 3.x) and that's the way I'm logging the filename and linenumber of the caller:
IMPORTANT: pay attention to the embedded code comments!
/**
* Use CallSite to extract filename and number
* #returns {string} filename and line number separated by a colon
*/
const getFileNameAndLineNumber = () => {
const oldStackTrace = Error.prepareStackTrace;
try {
// eslint-disable-next-line handle-callback-err
Error.prepareStackTrace = (err, structuredStackTrace) => structuredStackTrace;
Error.captureStackTrace(this);
// in this example I needed to "peel" the first 10 CallSites in order to get to the caller we're looking for, hence the magic number 11
// in your code, the number of stacks depends on the levels of abstractions you're using, which mainly depends on winston version!
// so I advise you to put a breakpoint here and see if you need to adjust the number!
return this.stack[11].getFileName() + ':' + this.stack[11].getLineNumber();
} finally {
Error.prepareStackTrace = oldStackTrace;
}
};
And (a simplified version of) the formatter function:
function humanReadableFormatter ({level, message}) {
const filename = getFileNameAndLineNumber();
return `[${level}] ${filename} ${message}`;
}
Then declare the transport to use the formatter:
new winston.transports.Console({
level: 'info',
handleExceptions: true,
humanReadableUnhandledException: true,
json: false,
colorize: 'level',
stderrLevels: ['warn', 'error', 'alert'],
formatter: humanReadableFormatter,
})
To read more about prepareStackTrace read: https://v8.dev/docs/stack-trace-api#customizing-stack-traces
You can do string operations on
console.log(new Error().stack.split('\n')[1].slice(7));
to get line Number and file path too along with function name.
The output would look like
AutomationFramework._this.parsingLogic (/Users/user_name/Desktop/something/some-file.js:49:25)
here is how I go around it
import winston from 'winston'
const {format } = winston
const { combine, timestamp, printf } = format;
const myFormat = printf(({ level, message,timestamp , at }) => {
let on = at.stack.split('\n')[1].slice(7).split('/').pop() ;
let file = on.split(':')[0]
let line = on.split(':')[1]
let data = Date(timestamp).toString().split(' GMT')[0]
return `${level}: [${data}] Hello <yourName>, there is a ${level} message on file:${file} line ${line}
${message}`;
});
const logger = winston.createLogger({
level: 'info',
format: combine(
timestamp(),
myFormat ,
),
transports: [new winston.transports.Console()],
});
on loggin the output will be like
logger.info('wrong file type' , {at : new Error})
//logs
warn: [Thu Aug 18 2022 15:06:28] Hello <yourName>, there is a warn message on file:winston.js line 7
>>wrong file type
I want to log just the data and not log level, timestamp etc. to a file.
var logger = new (winston.Logger)({
transports: [
new (winston.transports.File)({
filename: '/tmp/data.log',
json : false,
timestamp : function() {
return '';
}
})
]
});
logger.log('info', "a")
It removes the timestamp from the line but log level still appears. Currently, file contains "info: a". I want it to log just "a". Is it possible to specify output format in winston?
Unfortunately, that formatting is sort of hardcoded into winston; you can see the logic for it in the log function of common.js, which is used by most of the default transports.
The way around this would be to write your own custom transport which doesn't rely on common.log().
An aside: you can just provide a timestamp: false option to disable timestamp logging in the default transports.
You can define the custom log format like this
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({
timestamp: function() {
return Date.now();
},
formatter: function(options) {
// Return string will be passed to logger.
return options.timestamp() +' '+ options.level.toUpperCase() +' '+ (options.message ? options.message : '') + (options.meta && Object.keys(options.meta).length ? '\n\t'+ JSON.stringify(options.meta) : '' );
}
})
]
});
logger.info('Data to log.');
Hi i have a requirement from our production team, I need to create the logs hourly, I know that winston support daily, but this doesn't help me.
It is possible to do this?
You can rotate Winston logs hourly. You need to provide hour (HH) in date pattern.
Please check the sample code below:
var winston = require ('winston');
var path = require ('path');
var transports = [];
transports.push(new winston.transports.DailyRotateFile({
name: 'file',
datePattern: '.yyyy-MM-ddTHH',
filename: path.join("some_path", "log_file_name.log")
}));
var logger = new winston.Logger({transports: transports});
// ... and logging
logger.info("some info log ...", {extraData: 'abc'});
File names will be as follows: log_file_name.log.2013-12-17T16, log_file_name.log.2013-12-17T17 etc.
I hope that will help.
UPDATED
As #Tom has mentioned rotating Logs has moved out of winston and loaded if required
npm install winston-daily-rotate-file
Code sample
const winston = require('winston')
require('winston-daily-rotate-file');
const path = require('path');
let transports = [];
const { createLogger } = winston;
transports.push(
new winston.transports.DailyRotateFile({
name: 'file',
datePattern: 'YYYY-MM-DD-THH-mm',
filename: path.join(__dirname, 'rotate_logs', 'log_file.log')
})
)
var logger = createLogger({ transports: transports })
Full Example if want to test the above code
dataLog(0)
function dataLog(secondsPassed){
setTimeout(function(){
let dateNow = new Date();
logger.info(`seconds passed ${secondsPassed} and Time is ${dateNow}`);
console.log(`${secondsPassed}`);
if(dataLog != 130){ //when reaches 130 seconds stops logging
dataLog(++secondsPassed);
}
},1000);
}
The result files mentioned in the attached image
EXTRA: I have created winston examples with different use cases, Might be helpful
https://github.com/shivashanmugam/node-lab/blob/master/winston/index.js