Source: utils/log.js

/**
 * This file is part of Sesatheque.
 *   Copyright 2014-2015, Association Sésamath
 *
 * Sesatheque is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation.
 *
 * Sesatheque is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Sesatheque (LICENCE.txt).
 * @see http://www.gnu.org/licenses/agpl.txt
 *
 *
 * Ce fichier fait partie de l'application Sésathèque, créée par l'association Sésamath.
 *
 * Sésathèque est un logiciel libre ; vous pouvez le redistribuer ou le modifier suivant
 * les termes de la GNU Affero General Public License version 3 telle que publiée par la
 * Free Software Foundation.
 * Sésathèque est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE
 * sans même la garantie tacite de QUALITÉ MARCHANDE ou d'ADÉQUATION à UN BUT PARTICULIER.
 * Consultez la GNU Affero General Public License pour plus de détails.
 * Vous devez avoir reçu une copie de la GNU General Public License en même temps que Sésathèque
 * (cf LICENCE.txt et http://vvlibri.org/fr/Analyse/gnu-affero-general-public-license-v3-analyse
 * pour une explication en français)
 */
'use strict'

var dump = require('./dump')
var tools = require('../index')
var formatDate = tools.formatDate
var hasProp = tools.hasProp

// attention, on duplique plus bas pour l'export
var levels = {
  debug: 0,
  info: 1,
  notice: 1,
  warn: 2,
  warning: 2,
  error: 3,
  alert: 4,
  critical: 4
}
var prefixes = [
  '[debug]',
  '[info]',
  '[warning]',
  '[error]',
  '[CRITICAL]'
]
var loggers = [
  console.info || console.log, // 0 => debug
  console.log, // 1 => info|notice
  console.warn || console.error || console.log, // 2 => warning
  console.error || console.log, // 3 => error
  console.error || console.log // 4 => critical|alert
]

// valeurs par défaut
/**
 * Niveau minimum requis pour affichage (un niveau 2 affiche warning, error et critical)
 * @private
 * @type {number}
 */
var logLevel = levels.info
// on peut tourner sous node comme dans un navigateur
var isNode = typeof process !== 'undefined' && process.env
var isCluster = isNode && hasProp(process.env, 'NODE_APP_INSTANCE')
/**
 * Flag pour savoir s'il faut ajouter la date en préfixe (false par défaut dans un navigateur et true ailleurs)
 * @private
 * @type {boolean}
 */
var hasDatePrefix = isNode && !isCluster // en mode cluster c'est pm2 qui met le préfixe de date
var datePrefix = '[%O]'

// idem pour le process
var hasProcessPrefix = isCluster
// NODE_APP_INSTANCE est une string, donc '0' ne sera pas falsy
var processPrefix = (isCluster && process.env.NODE_APP_INSTANCE) || 'N/A'

/**
 * Écrit en console en ajoutant les préfixes
 * @param {Array} args arguments d'une autre fct (pseudo Array)
 * @param {number} level
 * @private
 */
function _log (args, level) {
  try {
    var logger = loggers[level] || console.log
    var prefix = ''
    // date en 1er
    if (hasDatePrefix) prefix = formatDate(datePrefix)
    // puis processId
    if (hasProcessPrefix) {
      if (prefix) prefix += ' '
      prefix += '[' + processPrefix + ']'
    }
    // puis logLevel
    if (prefix) prefix += ' '
    prefix += prefixes[level]
    // avec le 1er argument sur la même ligne, mais on râle si y’en a pas (pour avoir la trace et retrouver l'appelant)
    if (args.length < 1) args = [Error('fonction de log appelée sans contenu')]
    logger(prefix, args[0])
    // puis les autres sans préfixe
    if (args.length > 1) for (var i = 1; i < args.length; i++) logger(args[i])
  } catch (e) {
    // rien, fallait un environnement décent avec console...
  }
}

/**
 * Un console.log qui plante pas sur les anciens IE (ou d'autres navigateurs qui n'auraient pas de console.log)
 * @param {...*} arguments Nombre variable d'arguments, chacun sera passé à console.log ou console.error si c'est une erreur
 * @service sesajstools/utils/log
 */
function log () {
  if (logLevel < levels.warning) {
    _log(arguments, levels.info)
  }
}
// les logger des ≠ niveaux
log.debug = function debug () {
  if (logLevel < levels.info) {
    _log(arguments, levels.debug)
  }
}
log.info = function info () {
  if (logLevel < levels.warning) {
    _log(arguments, levels.info)
  }
}
log.warn = function warn () {
  if (logLevel < levels.error) {
    _log(arguments, levels.warning)
  }
}
log.ifError = function error () {
  if (error && logLevel < levels.critical) {
    _log(arguments, levels.error)
  }
}
log.error = function error () {
  if (logLevel < levels.critical) {
    _log(arguments, levels.error)
  }
}
log.critical = function critical () {
  _log(arguments, levels.critical)
}

/**
 * Passe logLevel au niveau error (désactive donc seulement les infos et warnings…)
 * Pour comptatibilité ascendante, à remplacer par log.setLevel(log.levels.error)
 * @deprecated
 */
log.disable = function disable () {
  logLevel = levels.error
}
/**
 * Supprime le préfixe de date
 */
log.disableDatePrefix = function disableDatePrefix () {
  hasDatePrefix = false
}
/**
 * Supprime le préfixe de process
 */
log.disableProcessPrefix = function disableProcessPrefix () {
  hasProcessPrefix = false
}
/**
 * Dump un objet en console si le niveau de log est <= à celui indiqué
 * @param {string} message affiché avant le dump
 * @param {*} objToDump peut être n'importe quoi
 * @param {object} [options]
 * @param {string|number} [options.max=1000] le nb max de caractères affichés du dump
 * @param {string|number} [options.level=debug] le niveau d'affichage souhaité
 */
log.dump = function (message, objToDump, options) {
  if (typeof options !== 'object') options = {}
  var level = options.level || levels.debug
  if (typeof level === 'string') level = levels[level] || levels.debug
  var dumped = dump(objToDump)
  var max = options.max || 1000
  if (dumped.length > max) dumped = dumped.substr(0, max) + '…'
  if (logLevel <= level) _log([message, dumped], level)
}
/**
 * Passe logLevel au niveau info (qui le rend plus bavard)
 * Pour comptatibilité ascendante, à remplacer par log.setLevel(log.levels.info)
 * @deprecated
 */
log.enable = function enable () {
  logLevel = levels.info
}
/**
 * Active le préfixe de date
 */
log.enableDatePrefix = function enableDatePrefix () {
  hasDatePrefix = true
}
/**
 * Active le préfixe de process
 */
log.enableProcessPrefix = function enableProcessPrefix () {
  if (typeof process === 'undefined') console.error('process not available')
  else hasProcessPrefix = true
}
/**
 * Modifie le préfixe de date (cf formats gérés par formatDate)
 * @param prefix
 */
log.setDatePrefix = function setDatePrefix (prefix) {
  if (typeof prefix === 'string') datePrefix = prefix
  else log.error(new Error('Le préfixe de log doit être une string'))
}
/**
 * Modifie le niveau de log
 * @param {number|string} level
 */
log.setLogLevel = function setLogLevel (level) {
  switch (level) {
    case 0:
    case '0':
    case 'debug':
      logLevel = levels.debug
      break
    case 1:
    case '1':
    case 'info':
    case 'notice':
      logLevel = levels.notice
      break
    case 2:
    case '2':
    case 'warn':
    case 'warning':
      logLevel = levels.warning
      break
    case 3:
    case '3':
    case 'err':
    case 'error':
      logLevel = levels.error
      break
    case 4:
    case '4':
    case 'alert':
    case 'crit':
    case 'critical':
      logLevel = levels.critical
      break
    default:
      log.error('niveau d’erreur ' + level)
  }
}
// et on ajoute les levels, mais sans ref pour empêcher de modifier nos constantes locale de l'extérieur
log.levels = {
  debug: 0,
  info: 1,
  notice: 1,
  warn: 2,
  warning: 2,
  error: 3,
  alert: 4,
  critical: 4
}
// ce serait plus élégant de cloner (quoi que, en es5 ça reste moche) mais ça empêche l'autocompletion

module.exports = log