2
\$\begingroup\$

I provide a class to use but for convenience I want the class to implement some instance methods as static methods. This was fast and concise but it feels smelly, probably because I'm modifying the class after I create an instance of it. Is this ok?

  const logLevel    = !args['v'] || args['v'] === true ? 0 : parseInt(args['v']);
  const chalk       = new Chalk.constructor({enabled: useColor});
  const nameLength  = args['format-name-length'] || 25;

  class Logger{
    constructor(name='', _stdout=stdout, _stderr=stderr){
      this._name = this._getPaddedName(name);
      this._stdout = _stdout;
      this._stderr = _stderr;
      this.error.bind(this);
      this.info.bind(this);
      this.warn.bind(this);
    }
    error(level, ...args){
      if(level <= logLevel){
        this._print(this._stderr, args, 'red', level);
      }
    }
    info(level, ...args){
      if(level <= logLevel){
        this._print(this._stdout, args, 'blue', level);
      }
    }
    warn(level, ...args){
      if(level <= logLevel){
        this._print(this._stderr, args, 'yellow', level);
      }
    }
    _getLogId(color, level){
      return chalk[color](
        moment().format("YYYY:MM:DD h:mm:ss")
        + ` ${this._name}`
        + ` [${level}]: `
      );
    }
    _getPaddedName(name){
      let nameArray = name.split('')
        .slice(0, nameLength);
      if(nameLength > name.length){
        nameArray = nameArray
          .concat(new Array(nameLength - name.length).fill(null));
      }
      let ellipsis = nameArray.slice(nameLength - 3)
        .reduce((prev, next)=>!!(prev || next), false);
      return nameArray
        .slice(0, nameLength - 3)
        .map(char=>char || '-')
        .concat(ellipsis ? ['.', '.', '.'] : ['-', '-', '-'])
        .join('');
    }
    _format(arg, color, level){
      let logId = this._getLogId(color, level);
      return util.format(arg)
        .replace(/^/, logId)
        .replace(/(\r?\n)/g, `\n${logId}`)
        .replace(/((?:\r?\n)?$)/, '\n');
    }
    _print(stream, args, color, level){
      args.forEach(arg=>{
        if(arg !== ''){
          stream.write(this._format(arg, color, level));
        }
      });
    }
  }

  let defaultLogger = new Logger('logger');

  Logger.error = defaultLogger.error.bind(defaultLogger);
  Logger.info = defaultLogger.info.bind(defaultLogger);
  Logger.warn = defaultLogger.warn.bind(defaultLogger);

  return Logger;
\$\endgroup\$
3
  • 1
    \$\begingroup\$ I agree this isn't a clean approach. To give a good answer, though, can you show some examples of client code. That is, it's not entirely clear how you intend to use this logger and what it's responsibilities should be. Also, what features, eg, was console.log missing that inspired you to write this? \$\endgroup\$ – Jonah Jan 21 '17 at 18:46
  • \$\begingroup\$ I need to filter the log output by level. Also, I need to prefix every line of multi line strings with an identifier which console doesn't do. \$\endgroup\$ – Craig Lafferty Jan 21 '17 at 19:56
  • \$\begingroup\$ Our site standard, as stated in the How to Ask guidelines, is to state the purpose of the code in the title, rather than your concern about the code. I've rolled back the title you set in Rev 3. \$\endgroup\$ – 200_success Jan 22 '17 at 6:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.