How to use Winston in several modules? - node.js

I have several modules - let's say server.js, module1.js,...,moduleN.js.
I would like define the log file in my server.js:
winston.add(winston.transports.File, { filename: 'mylogfile.log' });
and then use it in all my modules.
What is the best way to do that? I could exports.winston=winston; in each module and then set it in the server.js, but is there any better solution?
Thank you in advance!

The default logger concept handles this nicely.
Winston defines a default logger that any straight require (and subsequent require) to winston will retrieve. Thus you simply configure this default logger once, and it's available for subsequent module use via vanilla require('winston') in its glorious tweaked multi-transport mode.
e.g. here is my complete logging setup that defines 3 transports. I swap Loggly for MongoDB sometimes.
server.js
var logger=require('./log.js');
// requires winston and configures transports for winstons default logger- see code below.
all other .js files
var logger=require('winston'); // this retrieves default logger which was configured in log.js
logger.info("the default logger with my tricked out transports is rockin this module");
log.js - this is a one time configuration of the DEFAULT logger
var logger = require('winston');
var Loggly = require('winston-loggly').Loggly;
var loggly_options={ subdomain: "mysubdomain", inputToken: "efake000-000d-000e-a000-xfakee000a00" }
logger.add(Loggly, loggly_options);
logger.add(winston.transports.File, { filename: "../logs/production.log" });
logger.info('Chill Winston, the logs are being captured 3 ways- console, file, and Loggly');
module.exports=logger;
Alternatively for more complex scenarios you can use winston containers and retrieve the logger from a named container in other modules. I haven't used this.
My only issue with this was a missing logs directories on my deployment host which was easily fixed.

What I do ( which may not be the best way ) is use a 'global' module where I export all the stuff that I use through my applications.
For instance:
//Define your winston instance
winston.add(winston.transports.File, { filename: 'mylogfile.log' });
exports.logger = winston;
exports.otherGlobals = ....
Now just require this globally used module from your other modules
var Global = require(/path/to/global.js);
Because the file is cached after the first time it is loaded (which you can verify by including a log statement in your global; it will only log once), there's very little overhead in including it again. Putting it all into one file is also easier than requiring ALL your globally used modules on every page.

I wanted to use custom colours and levels.
So I removed the default console-transport and set a colorized one
here is my logger.js
var logger = require('winston');
logger.setLevels({
debug:0,
info: 1,
silly:2,
warn: 3,
error:4,
});
logger.addColors({
debug: 'green',
info: 'cyan',
silly: 'magenta',
warn: 'yellow',
error: 'red'
});
logger.remove(logger.transports.Console);
logger.add(logger.transports.Console, { level: 'debug', colorize:true });
module.exports = logger;
Loading from app.js:
var logger = require('./lib/log.js');
Loading from other modules:
var logger = require('winston');

Slightly off topic (as the OP asks about Winston), but I like the 'child-logger' approach by Bunyan:
var bunyan = require('bunyan');
var log = bunyan.createLogger({name: 'myapp'});
app.use(function(req, res, next) {
req.log = log.child({reqId: uuid()});
next();
});
app.get('/', function(req, res) {
req.log.info({user: ...});
});
It solves the OP's problem as the logger is available through the req object (hence no need for 'require(log)' in each module). Additionally, all log entries belonging to a particular request will have a unique ID that connects them together.
{"name":"myapp","hostname":"pwony-2","pid":14837,"level":30,"reqId":"XXXX-XX-XXXX","user":"...#gmail.com","time":"2014-05-26T18:27:43.530Z","v":0}
I'm not sure if Winston supports this as well.

I am working on Winston 3.0.0 right now.
And it seems the way to configure the default logger has changed a little bit.
The way that works for me is folloing:
log.js// the setting for global logger
const winston= require('winston');
winston.configure({
level:"debug",
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
transports: [
new winston.transports.Console()
]
});
The other part is the same.
In the beginning of you application, require('log.js'), and also require ('winston'),
While in all other files, simply require('winston')
.

I'm creating a new Winston logger.
log.js
'use strict';
const winston = require('winston');
module.exports = new(winston.Logger)({
transports: [
new(winston.transports.Console)({
level: 'info'
})
]
});
a.js
const log = require('./log');
log.info("from a.js");
b.js
const log = require('./log');
log.info("from b.js");

Here is my logger configuration with winston version is 3.2.1.
It storing logs in application.log file and for error stack trace I am using errors({ stack: true }) and small trick in printf function to print stack trace in error case.
Configuration
const {format, transports} = require('winston');
const { timestamp, colorize, printf, errors } = format;
const { Console, File } = transports;
LoggerConfig = {
level: process.env.LOGGER_LEVEL || 'debug',
transports: [
new Console(),
new File({filename: 'application.log'})
],
format: format.combine(
errors({ stack: true }),
timestamp(),
colorize(),
printf(({ level, message, timestamp, stack }) => {
if (stack) {
// print log trace
return `${timestamp} ${level}: ${message} - ${stack}`;
}
return `${timestamp} ${level}: ${message}`;
}),
),
expressFormat: true, // Use the default Express/morgan request formatting
colorize: false, // Color the text and status code, using the Express/morgan color palette (text: gray, status: default green, 3XX cyan, 4XX yellow, 5XX red).
ignoreRoute: function (req, res) {
return false;
} // optional: allows to skip some log messages based on request and/or response
}
Declare
I am using this same configuration in express-winston and for general log also. I declared __logger object globally so that you don't need to import every time in every file. Generally in node js all the global variable prefix with 2 time underscore(__) so it will be good to follow this.
Server.js
const winston = require('winston');
const expressWinston = require('express-winston');
/**
* winston.Logger
* logger for specified log message like console.log
*/
global.__logger = winston.createLogger(LoggerConfig);
/**
* logger for every HTTP request comes to app
*/
app.use(expressWinston.logger(LoggerConfig));
Use
__logger is global so you can use it any place, for example:
blog.controller.js
function save(req, res) {
try {
__logger.debug('Blog add operation');
.
.
return res.send(blog);
} catch (error) {
__logger.error(error);
return res.status(500).send(error);
}
}
Hope this will help !

I use a factory function and pass in the module name so it can be added to the meta data:
logger-factory.js
const path = require('path');
const { createLogger, format, transports } = require('winston');
const { combine, errors, timestamp } = format;
const baseFormat = combine(
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
errors({ stack: true }),
format((info) => {
info.level = info.level.toUpperCase();
return info;
})(),
);
const splunkFormat = combine(
baseFormat,
format.json(),
);
const prettyFormat = combine(
baseFormat,
format.prettyPrint(),
);
const createCustomLogger = (moduleName) => createLogger({
level: process.env.LOG_LEVEL,
format: process.env.PRETTY_LOGS ? prettyFormat : splunkFormat,
defaultMeta: { module: path.basename(moduleName) },
transports: [
new transports.Console(),
],
});
module.exports = createCustomLogger;
app-harness.js (so I can run the exported index module)
const index = require('./index');
// https://docs.aws.amazon.com/lambda/latest/dg/with-s3.html
const sampleEvent = {
"Records": [
{
"eventVersion": "2.1",
"eventSource": "aws:s3",
"awsRegion": "us-east-2",
"eventTime": "2019-09-03T19:37:27.192Z",
"eventName": "ObjectCreated:Put",
"userIdentity": {
"principalId": "AWS:AIDAINPONIXQXHT3IKHL2"
},
"requestParameters": {
"sourceIPAddress": "205.255.255.255"
},
"responseElements": {
"x-amz-request-id": "D82B88E5F771F645",
"x-amz-id-2": "vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo="
},
"s3": {
"s3SchemaVersion": "1.0",
"configurationId": "828aa6fc-f7b5-4305-8584-487c791949c1",
"bucket": {
"name": "lambda-artifacts-deafc19498e3f2df",
"ownerIdentity": {
"principalId": "A3I5XTEXAMAI3E"
},
"arn": "arn:aws:s3:::lambda-artifacts-deafc19498e3f2df"
},
"object": {
"key": "b21b84d653bb07b05b1e6b33684dc11b",
"size": 1305107,
"eTag": "b21b84d653bb07b05b1e6b33684dc11b",
"sequencer": "0C0F6F405D6ED209E1"
}
}
}
]
};
index.handler(sampleEvent)
.then(() => console.log('SUCCESS'))
.catch((_) => console.log('FAILURE'));
index.js
const logger = require('./logger-factory')(__filename);
const app = require('./app');
exports.handler = async function (event) {
try {
logger.debug('lambda triggered with event', { event });
await app.run(event);
logger.debug(`lambda finished`);
} catch(error) {
logger.error('lambda failed: ', error);
// rethrow the error up to AWS
throw error;
}
}
app.js
const logger = require('./logger-factory')(__filename);
const run = async (event) => {
logger.info('processing S3 event', event);
try {
logger.info('reading s3 file')
// throws because I used "Record" instead of "Records"
const s3 = event.Record[0].s3;
// use s3 to read the file
} catch (error) {
logger.error('failed to read from S3: ', error);
throw error;
}
};
module.exports = { run };
when I run the application locally at WARN level:
~/repos/ghe/lambda-logging (master * u=)> LOG_LEVEL=warn node -r dotenv/config ./src/app-harness.js
{
module: 'app.js',
level: 'ERROR',
message: "failed to read from S3: Cannot read property '0' of undefined",
stack: "TypeError: Cannot read property '0' of undefined\n" +
' at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
' at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
' at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
' at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
' at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
' at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
' at internal/main/run_main_module.js:18:47',
timestamp: '2020-05-11 17:34:06'
}
{
module: 'index.js',
level: 'ERROR',
message: "lambda failed: Cannot read property '0' of undefined",
stack: "TypeError: Cannot read property '0' of undefined\n" +
' at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
' at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
' at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
' at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
' at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
' at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
' at internal/main/run_main_module.js:18:47',
timestamp: '2020-05-11 17:34:06'
}
when I run at DEBUG level:
~/repos/ghe/lambda-logging (master * u=)> LOG_LEVEL=debug node -r dotenv/config ./src/test-harness.js
{
module: 'index.js',
event: {
Records: [
{
eventVersion: '2.1',
eventSource: 'aws:s3',
awsRegion: 'us-east-2',
eventTime: '2019-09-03T19:37:27.192Z',
eventName: 'ObjectCreated:Put',
userIdentity: { principalId: 'AWS:AIDAINPONIXQXHT3IKHL2' },
requestParameters: { sourceIPAddress: '205.255.255.255' },
responseElements: {
'x-amz-request-id': 'D82B88E5F771F645',
'x-amz-id-2': 'vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo='
},
s3: {
s3SchemaVersion: '1.0',
configurationId: '828aa6fc-f7b5-4305-8584-487c791949c1',
bucket: {
name: 'lambda-artifacts-deafc19498e3f2df',
ownerIdentity: { principalId: 'A3I5XTEXAMAI3E' },
arn: 'arn:aws:s3:::lambda-artifacts-deafc19498e3f2df'
},
object: {
key: 'b21b84d653bb07b05b1e6b33684dc11b',
size: 1305107,
eTag: 'b21b84d653bb07b05b1e6b33684dc11b',
sequencer: '0C0F6F405D6ED209E1'
}
}
}
]
},
level: 'DEBUG',
message: 'lambda triggered with event',
timestamp: '2020-05-11 17:38:21'
}
{
module: 'app.js',
Records: [
{
eventVersion: '2.1',
eventSource: 'aws:s3',
awsRegion: 'us-east-2',
eventTime: '2019-09-03T19:37:27.192Z',
eventName: 'ObjectCreated:Put',
userIdentity: { principalId: 'AWS:AIDAINPONIXQXHT3IKHL2' },
requestParameters: { sourceIPAddress: '205.255.255.255' },
responseElements: {
'x-amz-request-id': 'D82B88E5F771F645',
'x-amz-id-2': 'vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo='
},
s3: {
s3SchemaVersion: '1.0',
configurationId: '828aa6fc-f7b5-4305-8584-487c791949c1',
bucket: {
name: 'lambda-artifacts-deafc19498e3f2df',
ownerIdentity: { principalId: 'A3I5XTEXAMAI3E' },
arn: 'arn:aws:s3:::lambda-artifacts-deafc19498e3f2df'
},
object: {
key: 'b21b84d653bb07b05b1e6b33684dc11b',
size: 1305107,
eTag: 'b21b84d653bb07b05b1e6b33684dc11b',
sequencer: '0C0F6F405D6ED209E1'
}
}
}
],
level: 'INFO',
message: 'processing S3 event',
timestamp: '2020-05-11 17:38:21'
}
{
message: 'reading s3 file',
level: 'INFO',
module: 'app.js',
timestamp: '2020-05-11 17:38:21'
}
{
module: 'app.js',
level: 'ERROR',
message: "failed to read from S3: Cannot read property '0' of undefined",
stack: "TypeError: Cannot read property '0' of undefined\n" +
' at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
' at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
' at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
' at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
' at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
' at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
' at internal/main/run_main_module.js:18:47',
timestamp: '2020-05-11 17:38:21'
}
{
module: 'index.js',
level: 'ERROR',
message: "lambda failed: Cannot read property '0' of undefined",
stack: "TypeError: Cannot read property '0' of undefined\n" +
' at Object.run (/Users/jason.berk/repos/ghe/lambda-logging/src/app.js:8:28)\n' +
' at Object.exports.handler (/Users/jason.berk/repos/ghe/lambda-logging/src/index.js:7:15)\n' +
' at Object.<anonymous> (/Users/jason.berk/repos/ghe/lambda-logging/src/test-harness.js:44:7)\n' +
' at Module._compile (internal/modules/cjs/loader.js:1158:30)\n' +
' at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)\n' +
' at Module.load (internal/modules/cjs/loader.js:1002:32)\n' +
' at Function.Module._load (internal/modules/cjs/loader.js:901:14)\n' +
' at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:74:12)\n' +
' at internal/main/run_main_module.js:18:47',
timestamp: '2020-05-11 17:38:21'
}

if you want to make the logger a global variable- you have to do specifically by assign it to the global variable like so
logger.js
var winston = require('winston')
var winston = winston.createLogger({
transports: [
new (winston.transports.Console)(),
new (winston.transports.File)({
filename: './logs/logger.log'
})
]
});
module.exports=winston;
app.js
let logger = require('./logger')
global.__logger = logger
someController.js
__logger.info('created log successfully')
Note: it's good practice to assign a prefix for every global variable so you will know that is a global one. i'm using __ as prefix (double low dash)

Just create logger.js and put
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
transports: [
new winston.transports.Console()
]
});
module.exports = logger
Then you can require and use it anywhere, since logger is now singleton.
const logger = require('./utils/logger');
logger.info('Hello!');
This even gives you an option to swap logging library if needed. The accepted answer is totally wrong and one step closer to spaghetti code.

In my team we have created a private npm package with all default configs (as you've shown in previous answers)
I've just one question: would it be a good practice to declare the logger object as a global in order to avoid import in each and every module?

Related

Meta is null in Winston-mongodb

I am using winston-mongodb modlue to log the request response.
Logger.service.ts
const options = {
console: {
db: DB_URL,
level: 'info',
format: format.combine(format.timestamp(), format.json()),
collection: 'logs',
},
error: {
db: DB_URL,
level: 'error',
format: format.combine(format.timestamp(), format.json()),
collection: 'logs',
}
};
const logger = createLogger({
transports: [
new MongoDB(options.console),
new MongoDB(options.error)
],
});
Server.ts
server.listen(port, () => {
logger.info({message:`Server is up and running on port ${port}`, meta: {
myProp: 'foo'
}});
);
Result:
{
"_id" : ObjectId("5ed8bd2c726d273004b082ca"),
"timestamp" : ISODate("2020-06-04T09:21:48.835Z"),
"level" : "info",
"message" : "Server is up and running on port 4000 HI",
**"meta" : null**
}
I am trying to add meta data to logger. But it is adding null to the result.
To get err object stored in the meta (with all its javascript properties), you need to specify :
format.metadata()
In the createLogger call.
Here is a working sample code:
// Declare winston
const winston = require("winston");
// Import all needed using Object Destructuring
const { createLogger, format, transports } = require("winston");
const { combine, timestamp, printf } = format;
// Export the module
module.exports = function (err, req, res, next) {
const logger = createLogger({
level: "error",
format: combine(
format.errors({ stack: true }), // log the full stack
timestamp(), // get the time stamp part of the full log message
printf(({ level, message, timestamp, stack }) => {
// formating the log outcome to show/store
return `${timestamp} ${level}: ${message} - ${stack}`;
}),
format.metadata() // >>>> ADD THIS LINE TO STORE the ERR OBJECT IN META field
),
transports: [
new transports.Console(), // show the full stack error on the console
new winston.transports.File({
// log full stack error on the file
filename: "logfile.log",
format: format.combine(
format.colorize({
all: false,
})
),
}),
new winston.transports.MongoDB({
db: "mongodb://localhost/logdb",
// collection: "log",
level: "error",
storeHost: true,
capped: true,
// metaKey: 'meta'
}),
],
});
logger.log({
level: "error",
message: err,
});
// Response sent to client but nothing related to winston
res.status(500).json(err.message);
};
try this
// create new meta object to store in database
let logMeta={
stack: err.stack, // error stack
otherInfo: otherInfoData // any other info you want to store
}
// use metadata propery for meta
logger.error(err.message, {metadata: logMeta });
OR another way, if you are exporting the function as error middleware (using nodejs, express):
module.exports = function (err, req, res, next) {
logger.log({
level: "error",
message: err.message,
stack: err.stack,
metadata: err.stack, // Put what you like as meta
});
res.status(500).send("Uncaught error caught here.");
};
Specify metadata object explicitly:
logger.error(err.message, {metadata: err.stack});
apparently i had the same problem where the meta property in mongoDB transport was null: This is how i fixed it....
const winston = require("winston")
const { format } = require("winstone")
winstone.add(new
winstone.transports.MongoDB({
format:format.metadata()})

How to write a log file with winston on NodeJS

I'm building on my NodeJS API a custom logger using Winston.
I'm trying to log into a file but is it not working and cannot understand the issue.
I used some online resources to find out the solution but anything worked.
I used the transports.File() and added to my push but nothing happens.
My goal is to add the same logs I'm getting using the logger with the console into the file.
Same format and way of it.
My code
/* eslint-disable object-curly-newline */
/* eslint-disable arrow-parens */
/* eslint-disable comma-dangle */
// Logger
// This logger is used to show error/info messages about the status of the API
import winston from 'winston';
import moment from 'moment';
// import logSymbols from 'log-symbols';
import fs from 'fs-extra';
import { logger, NODE_ENV } from '../config';
const transports = [];
const dir = './logs';
// Creating the logs dir if does not exist
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
// For development in prod need to check for dev env
// in dev we want more info error tracking
// in prod essential info error message
if (NODE_ENV.env !== 'development') {
transports.push(
new winston.transports.Console({
format: winston.format.combine(
winston.format.cli(),
winston.format.splat()
),
}),
new winston.transports.File({
level: 'error',
filename: `${dir}/logs.log`,
})
);
} else {
transports.push(new winston.transports.Console());
}
// Parse meta keys
const parser = string => {
if (!string) {
return '';
}
if (typeof string === 'string') {
return string;
}
return Object.keys(string).length ? JSON.stringify(string, undefined, 2) : '';
};
// Logger instance
const LoggerInstance = winston.createLogger({
level: logger.level,
levels: winston.config.npm.levels,
format: winston.format.combine(
winston.format.colorize(),
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.printf(info => {
const { timestamp, level, message, meta } = info;
const ts = moment(timestamp)
.local()
.format('YYYY-MM-DD HH:MM:ss');
const metaMsg = meta ? `: ${parser(meta)}` : '';
// const symbol = level === 'error' ? logSymbols.error : logSymbols.success;
return `${ts} [${level}] ${parser(message)} ${metaMsg}`;
})
),
transports,
});
export default LoggerInstance;
While you set log level explicitly to 'error' in your winston transporter, you can not see any messages in debug level in the file. Please change your configuration so that your file. transporter accepts debug level messages too.
new winston.transports.File({
level: 'debug',
filename: `${dir}/logs.log`,
})

Why Winston 3.0.0 is not working with Sails 0.12?

I have these settings in the file config/log.js
let {transports: transp, createLogger} = require('winston');
let commonSettings = {
colorize: false,
maxsize: 10000000,
maxfiles: 10,
timestamp: true
};
let settings = [{ filename: 'logs/warn.log', level: 'warn' }];
let logger = createLogger({
transports: settings.map(s => new transp.File({...s, ...commonSettings}))
});
module.exports.log = {
custom: logger,
inspect: false
};
When I'm trying to start the Sails server using this command sails lift, I'm getting the following error:
Uncaught TypeError: this.write is not a function
at Function.DerivedLogger.(anonymous function) (node_modules/winston/lib/winston/create-logger.js:45:14)
at Function._writeLogToConsole [as error] (node_modules/captains-log/lib/write.js:78:16)
at Sails.runBootstrap (node_modules/sails/lib/app/private/bootstrap.js:56:17)
at Sails.bound [as runBootstrap] (node_modules/sails/node_modules/lodash/dist/lodash.js:729:21)
at Sails.initialize (node_modules/sails/lib/app/private/initialize.js:48:9)
at bound (node_modules/sails/node_modules/lodash/dist/lodash.js:729:21)
at /Users/egomezr/Documents/stack/backend/node_modules/sails/node_modules/async/lib/async.js:607:21
at /Users/egomezr/Documents/stack/backend/node_modules/sails/node_modules/async/lib/async.js:246:17
at iterate (node_modules/sails/node_modules/async/lib/async.js:146:13)
at /Users/egomezr/Documents/stack/backend/node_modules/sails/node_modules/async/lib/async.js:157:25
at /Users/egomezr/Documents/stack/backend/node_modules/sails/node_modules/async/lib/async.js:248:21
at /Users/egomezr/Documents/stack/backend/node_modules/sails/node_modules/async/lib/async.js:612:34
at /Users/egomezr/Documents/stack/backend/node_modules/sails/lib/app/load.js:201:13
at /Users/egomezr/Documents/stack/backend/node_modules/sails/node_modules/async/lib/async.js:451:17
at /Users/egomezr/Documents/stack/backend/node_modules/sails/node_modules/async/lib/async.js:441:17
at _each (node_modules/sails/node_modules/async/lib/async.js:46:13)
at Immediate.taskComplete (node_modules/sails/node_modules/async/lib/async.js:440:13)
at process.topLevelDomainCallback (domain.js:101:23)
What could be the problem?
It looks Sails makes a sort of binding into the Winston logger, therefore, the logger loses some functionalities.
Open issue: issues/4337
I found a workaround to get it work:
let commonSettings = {
colorize: false,
maxsize: 10000000,
maxfiles: 10,
json: false
};
let settings = [
{filename: `${__dirname}/../logs/warn.log`, level: 'warn'},
{filename: `${__dirname}/../logs/error.log`, level: 'error'},
{filename: `${__dirname}/../logs/debug.log`, level: 'debug'},
{filename: `${__dirname}/../logs/info.log`, level: 'info'}
];
let {transports, createLogger, format} = require('winston');
let loggerSettings = {
transports: [...settings.map(s => new transports.File({...s, ...commonSettings})), new transports.Console({
format: format.simple()
})],
exitOnError: false
};
// This is the workaround
let winstonLogger = createLogger(loggerSettings);
let logger = {
'info': function () {
winstonLogger.info(...arguments);
},
'debug': function () {
winstonLogger.debug(...arguments);
},
'error': function () {
winstonLogger.error(...arguments);
},
'warn': function () {
winstonLogger.warn(...arguments);
},
'log': function () {
winstonLogger.log(...arguments);
}
};
// End of workaround
module.exports.log = {
custom: logger,
inspect: false
};
For what it's worth, it looks like his was reported in Sails issues but was inadvertently closed in https://github.com/balderdashy/sails/issues/4209.

How to add session id to each log in winston logger

In my node application I'm using winston module to store my application logs. I am getting the log as:
2017-11-22T07:16:38.632Z - info: asset type is attached successfully
Now I want to add sessionID after timestamp. I want my log as:
2017-11-22T07:16:38.632Z -**sessionId here**- info: asset type is attached successfully.
My code which I used for winston logging is:
var winston = require('winston');
require('winston-daily-rotate-file');
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6,
trace: 7
};
var transport = new (winston.transports.DailyRotateFile)({
filename: 'logs/./log',
datePattern: 'yyyy-MM-dd.',
prepend: true,
json: false,
level: process.env.ENV === 'development' ? 'debug' : 'info'
});
var logger = new (winston.Logger)({
levels: levels,
transports: [
transport
]
});
module.exports = logger;
You have to custom the log format https://github.com/winstonjs/winston/tree/2.x#custom-log-format
First update your transport :
var transport = new (winston...
...
level: process.env.ENV === 'development' ? 'debug' : 'info',
timestamp: () => {
let today = new Date();
return today.toISOString();
},
formatter: options => `${options.timestamp()} -${options.meta.sessionId}- ${options.level}: ${options.message}`
});
Then, just pass the session ID to your logger meta feature :
logger.info('asset type is attached successfully', {sessionId: 'mySessionID'});
and you get
2017-11-22T14:13:17.697Z -mySessionID- info: asset type is attached successfully
Edit : instead of exporting only the winston.logger object, we export an object which requires a sessionId as a parameter, and contains the winston.logger. We also update the transport, so we customize its formatter property in the new Logger object. In the formatter property, we replace the meta declaration with the new this.sessionId variable, so we don't use the meta property anymore.
logger.js :
var transport = new (winston...
...
level: process.env.ENV === 'development' ? 'debug' : 'info',
timestamp: () => {
let today = new Date();
return today.toISOString();
}
});
class Logger {
constructor(session) {
this.sessionId = session;
this.transport = transport;
this.transport.formatter = options => `${options.timestamp()} -${this.sessionId}- ${options.level}: ${options.message}`;
this.logger = new (winston.Logger)({
levels: levels,
transports: [this.transport]
});
}
}
module.exports = Logger;
server.js :
const Logger = require('./logger.js');
let logman = new Logger('my_session_id');
let logger = logman.logger;
logger.info('asset type is attached successfully');
2017-11-23T13:13:08.769Z -my_session_id- info: asset type is attached successfully
I had the same request and wasn't wild about the solution... I liked the way the standard logger was formatting logs and didn't want to reinvent that wheel. Also, I was hoping for something more compact and I think I found it. I'm not a javascript programmer, so it might not be good coding practice, but it seems to work well for me...
server.js
//Initiate winston logging please
const logger = require('winston');
const common = require('winston/lib/winston/common');
function myFormat(options) {
options.formatter = null
options.label = helpers.getSessionId(logger.req)
return common.log(options);
}
var consoleLoggingConfig = {
timestamp: true,
level: process.env.LOG_LEVEL ? process.env.LOG_LEVEL : "info",
handleExceptions: true,
humanReadableUnhandledException: true,
formatter: myFormat
}
logger.remove(logger.transports.Console);
logger.add(logger.transports.Console, consoleLoggingConfig)
logger.info('Winston logging initiated', consoleLoggingConfig)
//Helper to ensure that logger has access to the request object to obtain the session id
app.use(helpers.attachReqToLogger)
module.exports=logger
helpers.js
// Helper to ensure that the winston logger has the request object on it to obtain the sessionId
helpers.attachReqToLogger = function(req, res, next) {
logger.req = req
next();
}
import winston from "winston";
import { v4 as uuidv4 } from "uuid";
const { format } = winston;
const commonFormat = [format.timestamp(), format.json()];
const withId = [
format.printf(({ message, id, ...rest }) => {
return JSON.stringify(
{
// if frontend privide id, use it, else create one
id: id ?? uuidv4(),
message,
...rest,
},
null,
// prettyPrint seems will overload printf's output
// so i mannually do this
4,
);
}),
];
export const logger = winston.createLogger({
level: "debug",
format: format.combine(...commonFormat, ...withId),
// ...

Add module name in winston log entries

Is there a possibility to save the current module name in order to be printed automatically in winston log entries when they are called later?
Currently, when I want to print the module name in logs, I have to add it manually:
var logHeader = 'mymodule'
log.info(logHeader + 'Hello')
For example, with debug, you can do (ignore the log format feature %s for now):
var debug = require('debug')('http')
, name = 'My App'
debug('booting %s', name);
This will prin http prefix before the log:
http booting My App
Can this be done in winston? I have searched in the documentation but I couldn't find anything relevant.
I found a better way to do this.
I added an additional layer over the winston logger, in this case a function, that keeps the module name for each module that needs the logger. So when a module requires my new logger, it actually calls the exported function with the module name, in this case __filename.
log.js
var winston = require('winston')
var winstonLogger = new (winston.Logger)({
transports: [
new (winston.transports.File) ({
filename: 'MyLogs.txt',
handleExceptions: true,
humanReadableUnhandledException: true,
level: 'info',
timestamp: true,
json: false
}),
new (winston.transports.Console) ({
level: 'info',
prettyPrint: true,
colorize: true,
timestamp: true
})
]
})
module.exports = function(fileName) {
var myLogger = {
error: function(text) {
winstonLogger.error(fileName + ': ' + text)
},
info: function(text) {
winstonLogger.info(fileName + ': ' + text)
}
}
return myLogger
}
module1.js
var log = require('log')(__filename)
log.info('Info log example')
info: C:\Users\user1\project\module1.js: Info log example
module2.js
var log = require('log')(__filename)
log.error('Error log example')
error: C:\Users\user1\project\module2.js: Error log example
Also, this way, I didn't need to change anywhere the way I submit a text log; log.info('text') stays exactly like before.
This is what Child Loggers are for. They might not have been available when this question was asked, but for future reference, you can create your main logger instance and then export a function that creates a child logger with some default options.
// logging.js
const winston = require('winston')
const logger = winston.createLogger({
transports: [
new winston.transports.Console({
format: winston.format.printf(options => {
// you can pass any custom variable in options by calling
// logger.log({level: 'debug', message: 'hi', moduleName: 'my_module' })
return `[${options.moduleName}] ${options.level}: ${options.message}$`;
})
})
]
});
module.exports = function(name) {
// set the default moduleName of the child
return logger.child({moduleName: name});
}
Then at the top of each module, import the child using:
// my_module.js
const logger = require('./logging.js')('my_module_name');
logger.error('computer says no');
// output:
// [my_module_name] error: computer says no
You can specify a custom log format with Winston -
var moduleName = 'myModule';
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({
formatter: function(options) {
// Return string will be passed to logger.
return moduleName + ' - ' + (options.message ? options.message : '')
}
})
]
});
logger.info('This is a log message');
This will print -
myModule - This is a log message
So your module name will be appended to every log messsage.
With winston v3.3.3 you can do this by using winston.format.label and a customization of winston.format.printf:
const winston = require('winston');
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.label({label: 'mymodule'}),
winston.format.printf(({label, message}) => {
return `${label}: ${message}`;
})
),
transports: [
new winston.transports.Console(),
],
});
logger.info('Hello, World!'); // prints "mymodule: Hello, World!"

Resources