Node.JS redirect console stdout stderr

Recently i was facing a problem, this usual code :

process.on("uncaughtException", function(err){
	console.log("Uncaught exception raised : " + err);
});

was working only in the main module (in fact the place where i write it down). Inside some (not all) module, the system was not raising this event, the result was the usual usual crash. Which was something i don’t want.

On a test server or production server, usually it’s a better idea to keep a full log about what happen to system. So instead of « simply » put a upstart command, or a « Forever » with ugly linux redirect command line, i decide to go deeper with only Node.JS : I want it to be able to manage himself the stdout and stderr, including redirecting them to file and catch global unhandled error.

For this, i use the previous Singleton structure I post few days ago here. I will reuse this code to make a full Node.JS logger in few lines, here is the result :

Update 2013-04-06 : you will find on this link an updated version which is able to chunk log into small files if logs becomes too big… Prefer this one to the current posted one (which easier to understand) :
Update 2013-06-07 : you will find on this link an updated version which allow system to post on console & files, allowing both, debug and production console log. The system log both (once in console.log, once inside file) for every function. Also the « singleton » has been removed (while it’s not usefull at all).

//DO NOT PUT "use strict" HERE OR THE LOGGER WILL NOT BE ABLE TO LOG ERROR

var windows_error_log_root = __dirname + "/../",
	unix_error_log_root    = "/var/log/node/";

var fs   = require('fs'),
	util = require("util");

var consoleLogger = function consoleLogger(){
	//defining a var instead of this (works for variable & function) will create a private definition
	var stdout = null,
		stderr = null;

	if(process.platform === 'win32'){
		//Test or create the directory, we do it synchronously to never loose any log message
		try{
			if(!fs.statSync(windows_error_log_root).isDirectory()){
				fs.mkdirSync(windows_error_log_root, 0600);
			}
		}catch(e){
			fs.mkdirSync(windows_error_log_root, 0600);
		}
		stdout = fs.createWriteStream(windows_error_log_root + "access.log", { flags: 'a' });
		stderr = fs.createWriteStream(windows_error_log_root + "error.log", { flags: 'a' });
	}else{
		//Test or create the directory, we do it synchronously to never loose any log message
		try{
			if(!fs.statSync(unix_error_log_root).isDirectory()){
				fs.mkdirSync(unix_error_log_root, 0600);
			}
		}catch(e){
			fs.mkdirSync(unix_error_log_root, 0600);
		}
		stdout = fs.createWriteStream(unix_error_log_root + "access.log", { flags: 'a' });
		stderr = fs.createWriteStream(unix_error_log_root + "error.log", { flags: 'a' });
	}

	//Now we log the system
	process.__defineGetter__("stdout", function(){
		return stdout;
	});
	process.__defineGetter__("stderr", function(){
		return stderr;
	});

	//Catching all system error
	process.on("uncaughtException", function(err){
		this.error("Uncaught exception : " + err);
	}.bind(this));

	//Return a beautiful date printed, add 0 if variable is less to 10
	var beautifulDate = function(variable){
		if(variable < 10){
			return "0" + variable;
		}
		return variable;
	};

	//Return a formated date string
	var printDate = function(){
		var d = new Date();
		return ("" + d.getFullYear() + "-" + beautifulDate(d.getMonth()+1) + "-" + beautifulDate(d.getDate()) + "   " + beautifulDate(d.getHours()) + 
			":" + beautifulDate(d.getMinutes()) + ":" + beautifulDate(d.getSeconds()) + "   (UTC : " + (d.getTimezoneOffset()/60) + "h) => ");
	};

	//This function replace the usual console.log, it just automatically add date
	this.log = function(){
		if(stdout.writable===true){
			stdout.write(printDate() + util.format.apply(this, arguments) + "\n");
		}
	};

	//This function replace the usual console.dir, it just automatically add date
	this.dir = function(object){
		if(stdout.writable===true){
			stdout.write(printDate() + util.inspect(object) + "\n");
		}
	};

	//This function replace usual console.error, it just automatically add date
	this.error = function(){
		if(stderr.writable===true){
			stderr.write(printDate() + util.format.apply(this, arguments) + "\n");
		}
	};

	this.err = this.error;

	//Remove instanciate possibility for other module
	if(consoleLogger.caller != consoleLogger.getInstance){
		throw new Error("This object cannot be instanciated");
	}
}

/* ************************************************************************
CONSOLE LOGGER SINGLETON CLASS DEFINITION
************************************************************************ */
consoleLogger.instance = null;

/**
* consoleLogger getInstance definition
* @return consoleLogger class
*/
consoleLogger.getInstance = function(){
	if(this.instance === null){
		this.instance = new consoleLogger();
	}
	return this.instance;
}

module.exports = consoleLogger.getInstance();

Be careful to NEVER use « use strict » option in this script, or you will loose all trace if an error is reported. You must include this script at every file you use.

The « process.__defineGetter__ » is here to define the behaviour of usual console.log/dir if you still you it in your code.

After that, the whole Node.JS never crash anymore when an uncaught error is raised. I also add normal console option, just adding the date object on every log.
Be careful also with the behaviour, which is different between windows and other OS (i’m using usual /var/log for log into unix like).

If you got better improve/way, let me know !

Publicités

3 Commentaires

  1. thanks ! very helpful, however, i’m wondering if there is a way to send the stdout and stderr still on the console but send it to a file as well. Have two output, type of thing, one being the console and the other one being the file system !!!

  2. deisss

    Not sure but did you try using console.log inside this.log for example ? it may work directly doing so…

    Another way (more « usual ») would use directly tail -f inside any unix console (may be better for memory overflow management)

  3. deisss

    Updated the article with a new version handling both console and write to file !

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :