Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Winston logs to all files. (or: Restrict transport to only 1 level) #614

Closed
t0lkman opened this issue Apr 27, 2015 · 25 comments
Closed

Winston logs to all files. (or: Restrict transport to only 1 level) #614

t0lkman opened this issue Apr 27, 2015 · 25 comments
Labels
Feature Request Request for new functionality to support use cases not already covered Needs Investigation

Comments

@t0lkman
Copy link

t0lkman commented Apr 27, 2015

This is my winston config:

var winston = require('winston');
var fs = require('fs');
var _ = require('lodash');

winston.emitErrs = true;

var logDir = 'logs';
if (!fs.existsSync(logDir)) {
    fs.mkdirSync(logDir);
}

//add custom logging levels
var levels = _.clone(winston.config.syslog.levels);
var colors = _.clone(winston.config.syslog.colors);

levels.request = _.max(levels) + 1;
colors.request = 'blue';

var logger = new winston.Logger({
    levels: levels,
    colors: colors,
    exceptionHandlers: [
        new winston.transports.File({filename: 'logs/exceptions.log'})
    ],
    transports: [
        new winston.transports.File({
            name: 'info-file',
            level: 'info',
            filename: 'logs/all-logs.log',
            json: true,
            maxsize: 5242880, //5MB
            maxFiles: 5,
            colorize: false
        }),
        new winston.transports.File({
            name: 'error-file',
            level: 'error',
            filename: 'logs/error-logs.log',
            json: true,
            maxsize: 5242880, //5MB
            maxFiles: 5,
            colorize: false
        }),
        new winston.transports.File({
            name: 'request-file',
            level: 'request',
            filename: 'logs/requests.log',
            json: true,
            maxsize: 5242880, //5MB
            maxFiles: 5,
            colorize: false
        })
    ],
    exitOnError: false
});

module.exports = logger;

I'm trying to log, errors, http-requests, and exceptions to their own files.
However, I see, for example, in error-logs.log (http)requests logs.

Can anybody explain to me what I'm doing wrong?

@t0lkman
Copy link
Author

t0lkman commented Apr 27, 2015

This is how I'm logging the requests

// Log all requests
app.use(function(req, res, next) {
    var ip = req.headers['x-real-ip'] || req.connection.remoteAddress;
    logger.log('request', req.method, req.url, ip);
    next();
});

But see the output in the all 3 files:
filename: 'logs/all-logs.log',
filename: 'logs/error-logs.log',
filename: 'logs/requests.log'

All I want is that "request" custom level will output only to its own dedicated file.

@indexzero
Copy link
Member

@t0lkman I believe that you need to call setLevel explicilty on your Logger instance, but this may be a bug in setting custom levels. We will investigate.

@t0lkman
Copy link
Author

t0lkman commented May 27, 2015

Ideally, what I want to accomplish is, to have different log files for different stuff.. e.g. all http requests goes to one place, all mongo errors to another etc...

Currently there is some hierarchy logging system... where depends on the order things being logged..

@JosephScript
Copy link

I'm actually having a very similar issue, using the built in levels.

My Winston config:

logger = new winston.Logger({
        emitErrs: true,
        transports: [
            new winston.transports.Console({
                level:'debug',
                name: 'console',
                handleExceptions: true,
                prettyPrint: true,
                silent:false,
                timestamp: true,
                colorize: true,
                json: false
            }),
            new winston.transports.DailyRotateFile({
                level:'info',
                name: 'info',
                filename: accessLogsPath,
                handleExceptions: true,
                prettyPrint: true,
                silent:false,
                timestamp: true,
                json: true,
                colorize: true,
                maxsize: 20000,
                tailable: true
            }),
            new winston.transports.DailyRotateFile({
                level:'error',
                name: 'error',
                filename: errorLogsPath,
                handleExceptions: true,
                prettyPrint: true,
                silent: false,
                timestamp: true,
                json: true,
                colorize: false,
                maxsize: 20000,
                tailable: true
            })
        ],
        exitOnError: false 
    });

Then when I run moduleName.logger.error(err); it outputs to all three (I expected that out of debug, but not info). I changed level:'error', to level:'debug', and just log errors to debug only as a work around.

@masom
Copy link

masom commented Jun 22, 2015

@NukaPunk Isn't this the expected behaviour of a logger?

Usually a logger instance will log to all transports that match the minimum level of the message. Ex: debug -> info -> warning -> error. A transport that handles info will not log debug messages but will log info, warning and error.

This is usually solved by creating multiple logger instances, each handling a specific case.

@JosephScript
Copy link

Thanks for the reply @masom.

I guess it's counter intuitive to have your errors logged in your info log to me, but now that I better understand the way the levels work I'll take your advice and create different instances.

Thanks again.

@masom
Copy link

masom commented Jun 22, 2015

@NukaPunk yeah it is really counter-intuitive at first. Same happened with other language loggers and some of them let you specify an exclusive log level for a specific transport.

@JosephScript
Copy link

@masom so not a bug, but a feature ;-)

The readme file doesn't really explain the concept of lower level logging, so I assumed levels were exclusive. I'd recommend adding some information about that to the readme to avoid confusion, if possible.

Thank again, great work on the logging transport!

@t0lkman
Copy link
Author

t0lkman commented Jun 22, 2015

Is there any example, how I do multiple logger instances? All I need is to have totally separated log files.
I don't want errors appear in info etc

@JosephScript
Copy link

@t0lkman My guess would be something like this:

module.exports = {
    errorLog: new winston.Logger({
        levels: levels,
        colors: colors,
        exceptionHandlers: [
            new winston.transports.File({filename: 'logs/exceptions.log'})
        ],
        transports: [

            new winston.transports.File({
                name: 'error-file',
                level: 'error',
                filename: 'logs/error-logs.log',
                json: true,
                maxsize: 5242880, //5MB
                maxFiles: 5,
                colorize: false
            })
        ],
        exitOnError: false
    }),
    requestLog: new winston.Logger({
            levels: levels,
            colors: colors,
            exceptionHandlers: [
                new winston.transports.File({filename: 'logs/exceptions.log'})
            ],
            transports: [
                new winston.transports.File({
                    name: 'request-file',
                    level: 'request',
                    filename: 'logs/requests.log',
                    json: true,
                    maxsize: 5242880, //5MB
                    maxFiles: 5,
                    colorize: false
                })
            ],
            exitOnError: false
        }
    ),

    infoLog: new winston.Logger({
        levels: levels,
        colors: colors,
        exceptionHandlers: [
            new winston.transports.File({filename: 'logs/exceptions.log'})
        ],
        transports: [
            new winston.transports.File({
                name: 'info-file',
                level: 'info',
                filename: 'logs/all-logs.log',
                json: true,
                maxsize: 5242880, //5MB
                maxFiles: 5,
                colorize: false
            })
        ],
        exitOnError: false
    })
};

And then you could use it like this:

var logger = require(/path/to/file);

logger.errorLog('This is an error!');
logger.infoLog('This is info!');
logger.request('request', req.method, req.url, ip);

Alternatively you can use Morgan for request logging pretty easily:

infoLogger.stream = {
    write: function(message, encoding){
        logger.info(message);
    }
};
app.use(require("morgan")("combined", { "stream":  infoLogger.stream }));

I hope that helps!

@t0lkman
Copy link
Author

t0lkman commented Jun 23, 2015

Tried, didn't work :(

new winston.Logger( returns an object, but expected a function

@JosephScript
Copy link

@t0lkman You're right. I tried it out and it doesn't work (that's what I get for writing it in my browser). Try this instead:

var errorLog = require('./models/winston').errorLog;
errorLog.error('test');

@indexzero indexzero changed the title Winston logs to all files. Winston logs to all files. (or: Restrict transport to only 1 level) Oct 29, 2015
@indexzero indexzero added the Feature Request Request for new functionality to support use cases not already covered label Oct 29, 2015
@davidfeldi
Copy link

any updates on this one?

@odegraciajr
Copy link

@davidfeldi Maybe this may help, I've encountered this issue before and manage to create my own wrapper. This separates error logs for each levels. https://github.com/odegraciajr/winston-logger

@j-holub
Copy link

j-holub commented Jun 15, 2017

This issue has now been around for two years. I myself thought it was kinda a pity that I can't make an error-only log file along with an info-log with one instance.
But if this is the desired / expected behaviour you should close this issue in good old won't fix matter and inform the people in the readme, how to seperate the log files, by using two instances, one for the error logs and one for the infos.

@jesprider
Copy link

jesprider commented Aug 11, 2017

Hey guys, here is my solution for the problem (also in gist: winston-logger.js)

const winston = require('winston');
const path = require('path');

const getLogger = (module, type) => {
    const modulePath = module.filename.split('/').slice(-2).join('/');
    const logger = new winston.Logger({
        transports: [
            new (winston.transports.Console)({
                colorize: true,
                level: (process.env.NODE_ENV === 'development') ? 'debug' : 'error',
                label: modulePath
            })
        ]
    });

    switch (type) {
        case 'error':
            logger.add(winston.transports.File, {
                name: 'error-file',
                filename: path.join(__dirname, '../../logs/error_default.log'),
                level: 'error'
            });
            return logger;
        case 'info':
            logger.add(winston.transports.File, {
                name: 'info-file',
                filename: path.join(__dirname, '../../logs/info_default.log'),
                level: 'info'
            });
            return logger;
        default:
            return logger;
    }
};

module.exports = module => ({
    error(err) {
        getLogger(module, 'error').error(err);
    },
    info(err) {
        getLogger(module, 'info').info(err);
    },
    debug(err) {
        getLogger(module, 'default').debug(err);
    }
});

Usage:

const logger = require('path/to/logger')(module);
logger.info('Logging with winston');

@eyalzoref
Copy link

It works! only if the minimum log level should be the same in all defined transports,
e.g.
new(winston.Logger)({
transports: [
new(winston.transports.Console)({ level: 'error' }),
new(winston.transports.File)({
name: 'error-file',
level: 'error',
filename: 'filename.log',
json: true,
maxsize: 5242880, //5MB
maxFiles: 5,
colorize: false
})
],
exitOnError: false
});

Personally, I think that each transport should log according it its defined level

@WangHansen
Copy link

WangHansen commented Mar 21, 2018

Any update on this, the latest rc3 doesn't work

@madal3x
Copy link

madal3x commented Jun 19, 2018

You could achieve this with a custom format that checks for the desired level:

    `new transports.File({
            filename: logFileInfo,
            level: 'info',
            json: false,
            format: combine(
                timestamp(),
                printf(i => i.level === 'info' ? `${i.level.toUpperCase()}: ${i.timestamp} ${i.message}` : '')
            )
        })`

@khmelevskii
Copy link

+1

@dlong500
Copy link

@madal3x That will still log empty lines to the file whenever the level doesn't match. I've tried returning null or even returning nothing at all and it still logs null/undefined. Unless something has changed in the 3.x release of which I'm not aware.

It's a pity there doesn't seem to be a way to abort a particular line from being logged or your custom format example might be a viable workaround. Seems like it would be a simply fix to not log the undefined value at all, so returning undefined from the format/formatter function would not log an empty line. Note that this should not affect the ability to log undefined variables as long as the total message being logged is more than just a single undefined value.

A more robust solution might be to add some type of intercept function to a transport that would allow returning true to log a message or false to ignore it.

@ngthienlinh
Copy link

I'm using the 3.x winston, after trials and errors I find out that we can filter log by level to log them to separate files. Here is the code I used:

const customFormat = winston.format.printf(i => {
	return `${i.level.toUpperCase()}: ${i.timestamp} ${i.message}`;
});

// Log unhandled exceptions to separate file
var exceptionHandlers = [
	new (winston.transports.DailyRotateFile)({
		name: 'Error Logs',
		filename: 'server/logs/errlogs/exceptions-%DATE%.log',
		datePattern: 'YYYY-MM-DD',
		zippedArchive: true,
		maxSize: '128m',
		maxFiles: '14d'
	})
]

const infoAndWarnFilter = winston.format((info, opts) => { 
	return info.level === 'info' || info.level === 'warn' ? info : false
})

const errorFilter = winston.format((info, opts) => { 
	return info.level === 'error' ? info : false 
})

// Separate warn/error 
var transports = [
	new (winston.transports.DailyRotateFile)({
		name: 'Error Logs',
		filename: 'server/logs/errlogs/application-%DATE%.log',
		datePattern: 'YYYY-MM-DD',
		zippedArchive: true,
		maxSize: '128m',
		maxFiles: '14d',
		level: 'warn',
		json: true,
		colorize: false,
		format: winston.format.combine(
			errorFilter(),
			winston.format.timestamp(),
			customFormat
		)
	}),
	new (winston.transports.DailyRotateFile)({
		name: 'INFO logs',
		filename: 'server/logs/infologs/application-%DATE%.log',
		datePattern: 'YYYY-MM-DD',
		zippedArchive: true,
		maxSize: '128m',
		maxFiles: '14d',
		json: true,
		colorize: false,
		level: 'info',
		format: winston.format.combine(
			infoAndWarnFilter(),
			winston.format.timestamp(),
			customFormat
		)
	}),
	new (winston.transports.Console)({		
		level: config.debugMode ? 'debug' : 'warn', // log warn level to console only
		handleExceptions: true,
		json: false,
		colorize: true,
		format: winston.format.combine(
			winston.format.colorize(),
			winston.format.simple()
		)
	})
]

var logger = winston.createLogger({
	transports: transports,
	exceptionHandlers: exceptionHandlers,
	level: config.debugMode ? 'debug' : 'info',
	exitOnError: false,
	// Default format
	format: winston.format.combine(
		winston.format.timestamp(),
		customFormat
	)
})

@dlong500
Copy link

@ngthienlinh You're right, that does work, and this is even detailed in the Filtering info Objects section of the documentation for version 3.x. Not sure how I missed that before. This works pretty well and basically turns the format feature into a robust middleware architecture.

I think this issue can be closed now given that it has been clearly indicated that there is no more work being done on the winston 2.x codebase.

@gtzinos
Copy link

gtzinos commented Nov 2, 2022

Hello everyone, this worked for me.

const winston = require('winston');

const infoWinston = winston.createLogger({
    level: 'info',
    defaultMeta: { service: 'log-service' },
    transports: [
        new (winston.transports.File)({ name: "info", filename: './logs/info.log', level: 'info' })
    ],
});

const errorWinston = winston.createLogger({
    level: 'info',
    defaultMeta: { service: 'log-service' },
    transports: [
        new (winston.transports.File)({ name: "error", filename: './logs/error.log', level: 'error' })
    ],
});

const logger = {
    info: (params) => {        
        return infoWinston.info(params);
    },
    error: (params) => {
        return errorWinston.error(params);
    }
};

logger.info("info");
logger.error("error");

@wbt
Copy link
Contributor

wbt commented Nov 4, 2022

Thanks!
Closing per penultimate comment above.

@wbt wbt closed this as completed Nov 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature Request Request for new functionality to support use cases not already covered Needs Investigation
Projects
None yet
Development

No branches or pull requests