14

I'm working on an node.js application with several dozen modules and using bunyan for logging (JSON output, multiple configurable streams). I've been looking for good examples of how to implement a instance across all the modules, but haven't seen what appears to be a really clean example I can learn from.

Below illustrates an approach that works, but seems quite inelegant (ugly) to me. I'm new to node & commonjs javascript in general, so looking for recommendations on how to improve it.

module: ./lib/logger

// load config file (would like this to be passed in to the constructor)
nconf.file({ file: fileConfig});
var logSetting = nconf.get('log');

// instantiate the logger
var Bunyan = require('bunyan');
var log = new Bunyan({
    name: logSetting.name,
streams : [
        { stream  : process.stdout, 
        level : logSetting.stdoutLevel},
        {    path : logSetting.logfile, 
            level : logSetting.logfileLevel}
    ],
serializers : Bunyan.stdSerializers
});

function Logger() {
};

Logger.prototype.info = function info(e) { log.info(e) };
Logger.prototype.debug = function debug(e) { log.debug(e) };
Logger.prototype.trace = function trace(e) { log.trace(e) };
Logger.prototype.error = function error(e) { log.error(e) };
Logger.prototype.warn = function warn(e) {  log.warn(e) };

module.exports = Logger;

module: main app

// create the logger
var logger = require('./lib/logger)
var log = new logger();

// note: would like to pass in options -->  new logger(options)


module: any project module using logger
// open the logger (new, rely on singleton...)
var logger = require('./lib/logger');
var log = new logger();

or view the gist

any recommendations?

EDIT:

I've modified the constructor, making the singleton pattern explicit (rather than implicit as part of the 'require' behaviour.

var log = null;
function Logger(option) {

// make the singleton pattern explicit
if (!Logger.log) {
    Logger.log = this;
}
    return Logger.log;
};  

and then changed the initialization to take an options parameter

// initialize the logger 
Logger.prototype.init = function init(options) {
log = new Bunyan({
    name: options.name,
    streams : [
        { stream  : process.stdout, 
            level : options.stdoutLevel},
        {    path : options.logfile, 
            level : options.logfileLevel}
    ],
    serializers : Bunyan.stdSerializers     
    });
};
Gal Margalit
  • 5,525
  • 6
  • 52
  • 56
dewd
  • 141
  • 1
  • 5
  • What you have looks like a basic singleton built using require's functionality. You could implement your own singleton, but it wouldn't necessarily be better. Dependency injection could be done, maybe? – mtsr Dec 07 '12 at 16:06
  • Thanks for the reply -- yes, it is a basic singleton. A couple of things bother me about it (a) the singleton behaviour is implied, rather than explicit, (b) I haven't found a clean way for the constructor to take arguments (say an options JSON) without then having to determine if they have already been loaded by a previous module, and (c) the alternative of passing a reference (as an argument) to every single module seems messy / verbose. – dewd Dec 08 '12 at 02:37
  • Thanks for the reply! Yes, it is a basic singleton, though it seems an implied behaviour rather than a defined one. I've looked at [bad practice?] (http://stackoverflow.com/questions/9733201/is-it-a-bad-practice-to-use-the-requirejs-module-as-a-singleton), [singleton pattern] (http://jasonwyatt.tumblr.com/post/8087061585/singleton-pattern-with-requirejs) plus [using require for singletons] (http://stackoverflow.com/questions/5608685/using-requirejs-how-do-i-pass-in-global-objects-or-singletons-around) and they seem to address the constructor behaviour better than the above. – dewd Dec 08 '12 at 02:48

2 Answers2

23

Singleton pattern in nodejs - is it needed? Actually, singleton is perhaps not needed in Node's environment. All you need to do is to create a logger in a separate file say, logger.js:

var bunyan = require("bunyan"); // Bunyan dependency
var logger = bunyan.createLogger({name: "myLogger"});

module.exports = logger;

Then, retrieve this logger from another module:

var logger = require("./logger");
logger.info("Anything you like");
Community
  • 1
  • 1
lixiang
  • 1,941
  • 3
  • 20
  • 25
  • I like this approach, but how to configure logger before its first use? I would like to use it this way, but I want to set output path. I tried to include "init" function in logger.js and set "module.exports" in it, but it isn't nice. – Garret Raziel Oct 19 '14 at 18:28
  • @lixiang Isn't this going to create a new instance of logger each time it is required? – Z2VvZ3Vp Jan 30 '15 at 22:06
  • 1
    @PixMach, No, The require module will cache the object instance – lixiang Sep 09 '15 at 02:35
  • Oh, that means that the require("./logger") effectively is a singleton, doesn't it? – mogsie Sep 27 '16 at 13:47
  • 1
    @mogsie, **Yes**, if we export an **object** its singleton. If we export a **function**, its not a singleton. – Sangeeth Nov 19 '18 at 09:06
1

if you are using express with node.js then you can try this. By default, logging is disabled in Express. You have to do certain stuff to get logs working for your app. For access logs, we need to use the Logger middleware; for error logs we will use Forever.Hope it will help you.. Here is a good example How to Logging Access and Errors in node.js

Damodaran
  • 10,882
  • 10
  • 60
  • 81
  • 1
    thanks, this was a helpful reference. I had already used a similar pattern for express logging middleware using Bunyan, where I was struggling was in creating a clean wrapper class which allowed me to use the same instance across all the modules, with an initialization method that allowed options to be passed in. Will post what I've come up with... – dewd Dec 10 '12 at 15:26