Правильная оболочка для console.log с правильным номером строки?

Теперь я разрабатываю приложение и размещаю глобальный переключатель isDebug. Я хотел бы обернуть console.log для более удобного использования.

//isDebug controls the entire site.
var isDebug = true;

//debug.js
function debug(msg, level){
    var Global = this;
    if(!(Global.isDebug && Global.console && Global.console.log)){
        return;
    }
    level = level||'info';
    Global.console.log(level + ': '+ msg);
}

//main.js
debug('Here is a msg.');

Затем я получаю этот результат в консоли Firefox.

info: Here is a msg.                       debug.js (line 8)

Что делать, если я хочу зарегистрировать номер строки, где вызывается debug(), например info: Here is a msg. main.js (line 2)?

Ответ 1

Это старый вопрос, и все предоставленные ответы чересчур хакки, имеют ОСНОВНЫЕ проблемы с перекрестным браузером и не дают ничего сверх полезного. Это решение работает в каждом браузере и сообщает все данные консоли точно так, как должно. Никаких необходимых хаков и одной строки кода Проверьте код.

var debug = console.log.bind(window.console)

Создайте переключатель следующим образом:

isDebug = true // toggle this to turn on / off for global controll

if (isDebug) var debug = console.log.bind(window.console)
else var debug = function(){}

Затем просто вызовите следующее:

debug('This is happening.')

Вы даже можете взять консоль .log с помощью такого переключателя:

if (!isDebug) console.log = function(){}

Если вы хотите сделать что-то полезное с этим. Вы можете добавить все консольные методы и обернуть его в функцию многократного использования, которая дает не только глобальный контроль, но и уровень класса:

var Debugger = function(gState, klass) {

  this.debug = {}

  if (gState && klass.isDebug) {
    for (var m in console)
      if (typeof console[m] == 'function')
        this.debug[m] = console[m].bind(window.console, klass.toString()+": ")
  }else{
    for (var m in console)
      if (typeof console[m] == 'function')
        this.debug[m] = function(){}
  }
  return this.debug
}

isDebug = true //global debug state

debug = Debugger(isDebug, this)

debug.log('Hello log!')
debug.trace('Hello trace!')

Теперь вы можете добавить его в свои классы:

var MyClass = function() {
  this.isDebug = true //local state
  this.debug = Debugger(isDebug, this)
  this.debug.warn('It works in classses')
}

Ответ 2

Мне понравился ответ @fredrik, поэтому я свел его с другим ответом, который разделяет трассировку стека Webkit, и объединил его с безопасной оболочкой @PaulIrish console.log. "Стандартизирует" filename:line к "специальному объекту", чтобы оно выделялось и выглядело практически одинаково в FF и Chrome.

Тестирование в скрипке: http://jsfiddle.net/drzaus/pWe6W/

_log = (function (undefined) {
    var Log = Error; // does this do anything?  proper inheritance...?
    Log.prototype.write = function (args) {
        /// <summary>
        /// Paulirish-like console.log wrapper.  Includes stack trace via @fredrik SO suggestion (see remarks for sources).
        /// </summary>
        /// <param name="args" type="Array">list of details to log, as provided by 'arguments'</param>
        /// <remarks>Includes line numbers by calling Error object -- see
        /// * http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
        /// * https://stackoverflow.com/questions/13815640/a-proper-wrapper-for-console-log-with-correct-line-number
        /// * /questions/62469/how-to-get-javascript-caller-function-line-number-how-to-get-javascript-caller-source-url/430635#430635
        /// </remarks>

        // via @fredrik SO trace suggestion; wrapping in special construct so it stands out
        var suffix = {
            "@": (this.lineNumber
                    ? this.fileName + ':' + this.lineNumber + ":1" // add arbitrary column value for chrome linking
                    : extractLineNumberFromStack(this.stack)
            )
        };

        args = args.concat([suffix]);
        // via @paulirish console wrapper
        if (console && console.log) {
            if (console.log.apply) { console.log.apply(console, args); } else { console.log(args); } // nicer display in some browsers
        }
    };
    var extractLineNumberFromStack = function (stack) {
        /// <summary>
        /// Get the line/filename detail from a Webkit stack trace.  See /questions/62469/how-to-get-javascript-caller-function-line-number-how-to-get-javascript-caller-source-url/430635#430635
        /// </summary>
        /// <param name="stack" type="String">the stack string</param>

        if(!stack) return '?'; // fix undefined issue reported by @sigod

        // correct line number according to how Log().write implemented
        var line = stack.split('\n')[2];
        // fix for various display text
        line = (line.indexOf(' (') >= 0
            ? line.split(' (')[1].substring(0, line.length - 1)
            : line.split('at ')[1]
            );
        return line;
    };

    return function (params) {
        /// <summary>
        /// Paulirish-like console.log wrapper
        /// </summary>
        /// <param name="params" type="[...]">list your logging parameters</param>

        // only if explicitly true somewhere
        if (typeof DEBUGMODE === typeof undefined || !DEBUGMODE) return;

        // call handler extension which provides stack trace
        Log().write(Array.prototype.slice.call(arguments, 0)); // turn into proper array
    };//--  fn  returned

})();//--- _log

Это также работает в узле, и вы можете проверить это с:

// no debug mode
_log('this should not appear');

// turn it on
DEBUGMODE = true;

_log('you should', 'see this', {a:1, b:2, c:3});
console.log('--- regular log ---');
_log('you should', 'also see this', {a:4, b:8, c:16});

// turn it off
DEBUGMODE = false;

_log('disabled, should not appear');
console.log('--- regular log2 ---');

Ответ 3

Вы можете поддерживать номера строк и для вывода уровня журнала с помощью умного использования Function.prototype.bind:

function setDebug(isDebug) {
  if (window.isDebug) {
    window.debug = window.console.log.bind(window.console, '%s: %s');
  } else {
    window.debug = function() {};
  }
}

setDebug(true);

// ...

debug('level', 'This is my message.'); // --> level: This is my message. (line X)

Сделав еще один шаг, вы можете использовать console ошибки/предупреждения/информацию и все еще иметь пользовательские уровни. Попробуйте!

function setDebug(isDebug) {
  if (isDebug) {
    window.debug = {
      log: window.console.log.bind(window.console, '%s: %s'),
      error: window.console.error.bind(window.console, 'error: %s'),
      info: window.console.info.bind(window.console, 'info: %s'),
      warn: window.console.warn.bind(window.console, 'warn: %s')
    };
  } else {
    var __no_op = function() {};

    window.debug = {
      log: __no_op,
      error: __no_op,
      warn: __no_op,
      info: __no_op
    }
  }
}

setDebug(true);

// ...

debug.log('wat', 'Yay custom levels.'); // -> wat: Yay custom levels.    (line X)
debug.info('This is info.');            // -> info: This is info.        (line Y)
debug.error('Bad stuff happened.');     // -> error: Bad stuff happened. (line Z)

Ответ 4

От: Как получить номер строки номера вызывающего абонента JavaScript? Как получить URL-адрес исходного кода JavaScript-звонителя? объект Error имеет свойство номера строки (в FF). Итак, что-то вроде этого должно работать:

var err = new Error();
Global.console.log(level + ': '+ msg + 'file: ' + err.fileName + ' line:' + err.lineNumber);

В браузере Webkit у вас есть err.stack, который представляет собой строку, представляющую текущий стек вызовов. Он отобразит текущий номер строки и дополнительную информацию.

UPDATE

Чтобы получить правильное имя linenumber, вам нужно вызвать ошибку в этой строке. Что-то вроде:

var Log = Error;
Log.prototype.write = function () {
    var args = Array.prototype.slice.call(arguments, 0),
        suffix = this.lineNumber ? 'line: '  + this.lineNumber : 'stack: ' + this.stack;

    console.log.apply(console, args.concat([suffix]));
};

var a = Log().write('monkey' + 1, 'test: ' + 2);

var b = Log().write('hello' + 3, 'test: ' + 4);

Ответ 5

Способ сохранить номер строки здесь: https://gist.github.com/bgrins/5108712. Это более или менее сводится к следующему:

if (Function.prototype.bind) {
    window.log = Function.prototype.bind.call(console.log, console);
}
else {
    window.log = function() { 
        Function.prototype.apply.call(console.log, console, arguments);
    };
}

Вы можете обернуть это с помощью isDebug и установить window.log в function() { }, если вы не отлаживаете.

Ответ 6

Вы можете передать номер строки вашему методу отладки, например:

//main.js
debug('Here is a msg.', (new Error).lineNumber);

Здесь (new Error).lineNumber предоставит вам текущий номер строки в вашем коде javascript.

Ответ 7

Я нашел простое решение, чтобы объединить принятый ответ (привязка к console.log/error/etc) с некоторой внешней логикой, чтобы отфильтровать то, что фактически зарегистрировано.

// or window.log = {...}
var log = {
  ASSERT: 1, ERROR: 2, WARN: 3, INFO: 4, DEBUG: 5, VERBOSE: 6,
  set level(level) {
    if (level >= this.ASSERT) this.a = console.assert.bind(window.console);
    else this.a = function() {};
    if (level >= this.ERROR) this.e = console.error.bind(window.console);
    else this.e = function() {};
    if (level >= this.WARN) this.w = console.warn.bind(window.console);
    else this.w = function() {};
    if (level >= this.INFO) this.i = console.info.bind(window.console);
    else this.i = function() {};
    if (level >= this.DEBUG) this.d = console.debug.bind(window.console);
    else this.d = function() {};
    if (level >= this.VERBOSE) this.v = console.log.bind(window.console);
    else this.v = function() {};
    this.loggingLevel = level;
  },
  get level() { return this.loggingLevel; }
};
log.level = log.DEBUG;

Использование:

log.e('Error doing the thing!', e); // console.error
log.w('Bonus feature failed to load.'); // console.warn
log.i('Signed in.'); // console.info
log.d('Is this working as expected?'); // console.debug
log.v('Old debug messages, output dominating messages'); // console.log; ignored because 'log.level' is set to 'DEBUG'
log.a(someVar == 2) // console.assert
  • Обратите внимание, что console.assert использует условное ведение журнала.
  • Убедитесь, что ваш инструмент разработки браузера показывает все уровни сообщений!

Ответ 8

Chrome Devtools позволяет вам достичь этого с помощью Blackboxing. Вы можете создать оболочку console.log, которая может иметь побочные эффекты, вызывать другие функции и т.д., И при этом сохранить номер строки, вызвавшей функцию оболочки.

Просто поместите небольшую оболочку console.log в отдельный файл, например

(function() {
    var consolelog = console.log
    console.log = function() {
        // you may do something with side effects here.
        // log to a remote server, whatever you want. here
        // for example we append the log message to the DOM
        var p = document.createElement('p')
        var args = Array.prototype.slice.apply(arguments)
        p.innerText = JSON.stringify(args)
        document.body.appendChild(p)

        // call the original console.log function
        consolelog.apply(console,arguments)
    }
})()

Назовите это что-то вроде log-blackbox.js

Затем перейдите в настройки Chrome Devtools и найдите раздел "Blackboxing", добавьте шаблон для имени файла, который вы хотите добавить в черный ящик, в данном случае log-blackbox.js

Ответ 9

Если вы просто хотите контролировать, используется ли debug и имеет правильный номер строки, вы можете сделать это вместо:

if(isDebug && window.console && console.log && console.warn && console.error){
    window.debug = {
        'log': window.console.log,
        'warn': window.console.warn,
        'error': window.console.error
    };
}else{
    window.debug = {
        'log': function(){},
        'warn': function(){},
        'error': function(){}
    };
}

Когда вам нужен доступ к отладке, вы можете сделать это:

debug.log("log");
debug.warn("warn");
debug.error("error");

Если isDebug == true, номера строк и имена файлов, показанные на консоли, будут правильными, потому что debug.log и т.д. фактически является псевдонимом console.log и т.д.

Если isDebug == false, отладочные сообщения не отображаются, потому что debug.log и т.д. просто ничего не делает (пустая функция).

Как вы уже знаете, функция-оболочка испортит номера строк и имена файлов, поэтому рекомендуется предотвратить использование функций-оболочек.

Ответ 10

Решения трассировки стека отображают номер строки, но не позволяют щелкнуть, чтобы перейти к источнику, что является серьезной проблемой. Единственное решение сохранить это поведение - bind в исходную функцию.

Связывание предотвращает включение промежуточной логики, потому что эта логика будет беспорядочна с номерами строк. Однако, переопределяя связанные функции и играя с консольной заменой строк, возможно некоторое дополнительное поведение.

Этот метод показывает минималистичную структуру ведения журнала, которая предлагает модули, уровни журналов, форматирование и правильные номера строк с возможностью обращения к ним в 34 строках. Используйте его в качестве основы или вдохновения для своих нужд.

var log = Logger.get("module").level(Logger.WARN);
log.error("An error has occured", errorObject);
log("Always show this.");

Ответ 11

Идея с bind Function.prototype.bind блестящая. Вы также можете использовать библиотеку строк-журнала npm. Он показывает исходные файлы:

Создайте регистратор кто-нибудь один раз в вашем проекте:

var LoggerFactory = require('lines-logger').LoggerFactory;
var loggerFactory = new LoggerFactory();
var logger = loggerFactory.getLoggerColor('global', '#753e01');

Печать журналов:

logger.log('Hello world!')();

enter image description here

Ответ 12

Здесь вы можете сохранить существующие console протоколирующие операторы при добавлении имени файла и номера строки или другой информации трассировки стека на вывод:

(function () {
  'use strict';
  var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
  var isChrome = !!window.chrome && !!window.chrome.webstore;
  var isIE = /*@[email protected]*/false || !!document.documentMode;
  var isEdge = !isIE && !!window.StyleMedia;
  var isPhantom = (/PhantomJS/).test(navigator.userAgent);
  Object.defineProperties(console, ['log', 'info', 'warn', 'error'].reduce(function (props, method) {
    var _consoleMethod = console[method].bind(console);
    props[method] = {
      value: function MyError () {
        var stackPos = isOpera || isChrome ? 2 : 1;
        var err = new Error();
        if (isIE || isEdge || isPhantom) { // Untested in Edge
          try { // Stack not yet defined until thrown per https://docs.microsoft.com/en-us/scripting/javascript/reference/stack-property-error-javascript
            throw err;
          } catch (e) {
            err = e;
          }
          stackPos = isPhantom ? 1 : 2;
        }

        var a = arguments;
        if (err.stack) {
          var st = err.stack.split('\n')[stackPos]; // We could utilize the whole stack after the 0th index
          var argEnd = a.length - 1;
          [].slice.call(a).reverse().some(function(arg, i) {
            var pos = argEnd - i;
            if (typeof a[pos] !== 'string') {
              return false;
            }
            if (typeof a[0] === 'string' && a[0].indexOf('%') > -1) { pos = 0 } // If formatting
            a[pos] += ' \u00a0 (' + st.slice(0, st.lastIndexOf(':')) // Strip out character count
              .slice(st.lastIndexOf('/') + 1) + ')'; // Leave only path and line (which also avoids ":" changing Safari console formatting)
            return true;
          });
        }
        return _consoleMethod.apply(null, a);
      }
    };
    return props;
  }, {}));
}());

Затем используйте его следующим образом:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <script src="console-log.js"></script>
</head>
<body>
  <script>
  function a () {
    console.log('xyz'); // xyz   (console-log.html:10)
  }
  console.info('abc'); // abc   (console-log.html:12)
  console.log('%cdef', "color:red;"); // (IN RED:) // def   (console-log.html:13)
  a();
  console.warn('uuu'); // uuu   (console-log.html:15)
  console.error('yyy'); // yyy   (console-log.html:16)
  </script>
</body>
</html>

Это работает в Firefox, Opera, Safari, Chrome и IE 10 (еще не тестировалось в IE11 или Edge).

Ответ 13

//isDebug controls the entire site.
var isDebug = true;

//debug.js
function debug(msg, level){
    var Global = this;
    if(!(Global.isDebug && Global.console && Global.console.log)){
        return;
    }
    level = level||'info';
    return 'console.log(\'' + level + ': '+ JSON.stringify(msg) + '\')';
}

//main.js
eval(debug('Here is a msg.'));

Это даст мне info: "Here is a msg." main.js(line:2).

Но требуется дополнительная eval, жалость.

Ответ 14

Код из http://www.briangrinstead.com/blog/console-log-helper-function:

// Full version of `log` that:
//  * Prevents errors on console methods when no console present.
//  * Exposes a global 'log' function that preserves line numbering and formatting.
(function () {
  var method;
  var noop = function () { };
  var methods = [
      'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
      'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
      'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
      'timeStamp', 'trace', 'warn'
  ];
  var length = methods.length;
  var console = (window.console = window.console || {});

  while (length--) {
    method = methods[length];

    // Only stub undefined methods.
    if (!console[method]) {
        console[method] = noop;
    }
  }


  if (Function.prototype.bind) {
    window.log = Function.prototype.bind.call(console.log, console);
  }
  else {
    window.log = function() { 
      Function.prototype.apply.call(console.log, console, arguments);
    };
  }
})();

var a = {b:1};
var d = "test";
log(a, d);

Ответ 15

Я рассматривал эту проблему в последнее время. Нужно что-то очень прямое, чтобы контролировать ведение журнала, но и сохранить номера строк. Мое решение не выглядит таким элегантным в коде, но обеспечивает то, что мне нужно. Если вы достаточно осторожны с закрытием и удержанием.

Я добавил небольшую оболочку в начало приложения:

window.log = {
    log_level: 5,
    d: function (level, cb) {
        if (level < this.log_level) {
            cb();
        }
    }
};

Так что я могу просто сделать следующее:

log.d(3, function(){console.log("file loaded: utils.js");});

Я протестировал его в firefox и crome, и оба браузера, похоже, отображают консольный журнал, как и предполагалось. Если вы заполняете это, вы всегда можете расширить метод "d" и передать ему другие параметры, чтобы он мог выполнять некоторые дополнительные протоколирования.

Не нашли серьезных недостатков для моего подхода, кроме уродливой строки в коде для ведения журнала.

Ответ 16

window.line = function () {
    var error = new Error(''),
        brower = {
            ie: !-[1,], // !!window.ActiveXObject || "ActiveXObject" in window
            opera: ~window.navigator.userAgent.indexOf("Opera"),
            firefox: ~window.navigator.userAgent.indexOf("Firefox"),
            chrome: ~window.navigator.userAgent.indexOf("Chrome"),
            safari: ~window.navigator.userAgent.indexOf("Safari"), // /^((?!chrome).)*safari/i.test(navigator.userAgent)?
        },
        todo = function () {
            // TODO: 
            console.error('a new island was found, please told the line()\ author(roastwind)');        
        },
        line = (function(error, origin){
            // line, column, sourceURL
            if(error.stack){
                var line,
                    baseStr = '',
                    stacks = error.stack.split('\n');
                    stackLength = stacks.length,
                    isSupport = false;
                // mac版本chrome(55.0.2883.95 (64-bit))
                if(stackLength == 11 || brower.chrome){
                    line = stacks[3];
                    isSupport = true;
                // mac版本safari(10.0.1 (12602.2.14.0.7))
                }else if(brower.safari){
                    line = stacks[2];
                    isSupport = true;
                }else{
                    todo();
                }
                if(isSupport){
                    line = ~line.indexOf(origin) ? line.replace(origin, '') : line;
                    line = ~line.indexOf('/') ? line.substring(line.indexOf('/')+1, line.lastIndexOf(':')) : line;
                }
                return line;
            }else{
                todo();
            }
            return '😭';
        })(error, window.location.origin);
    return line;
}
window.log = function () {
    var _line = window.line.apply(arguments.callee.caller),
        args = Array.prototype.slice.call(arguments, 0).concat(['\t\t\[email protected]'+_line]);
    window.console.log.apply(window.console, args);
}
log('hello');

Ответ 17

Небольшое изменение состоит в том, чтобы debug() возвращал функцию, которая затем выполняется там, где вам это нужно - debug (message)(); и поэтому правильно показывает правильный номер строки и вызывающий скрипт в окне консоли, допуская такие варианты, как перенаправление в виде предупреждения или сохранение в файл.

var debugmode='console';
var debugloglevel=3;

function debug(msg, type, level) {

  if(level && level>=debugloglevel) {
    return(function() {});
  }

  switch(debugmode) {
    case 'alert':
      return(alert.bind(window, type+": "+msg));
    break;
    case 'console':
      return(console.log.bind(window.console, type+": "+msg));
    break;
    default:
      return (function() {});
  }

}

Поскольку он возвращает функцию, эту функцию необходимо выполнить в строке отладки с помощью() ;. Во-вторых, сообщение отправляется в функцию отладки, а не в возвращаемую функцию, позволяющую выполнить предварительную обработку или проверку, которая может вам понадобиться, например, проверить состояние уровня журнала, сделать сообщение более читабельным, пропустить различные типы или только элементы отчетов. соответствие критериям уровня журнала;

debug(message, "serious", 1)();
debug(message, "minor", 4)();

Ответ 18

Я нашел некоторые ответы на эту проблему слишком сложными для моих нужд. Вот простое решение, сделанное в Coffeescript. Это адаптировано из версии Брайана Гринстеда здесь

Он принимает глобальный объект консоли.

# exposes a global 'log' function that preserves line numbering and formatting.
(() ->
    methods = [
      'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
      'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
      'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
      'timeStamp', 'trace', 'warn']
    noop = () ->
    # stub undefined methods.
    for m in methods  when  !console[m]
        console[m] = noop

    if Function.prototype.bind?
        window.log = Function.prototype.bind.call(console.log, console);
    else
        window.log = () ->
            Function.prototype.apply.call(console.log, console, arguments)
)()

Ответ 19

Как я решил, это создать объект, а затем создать новое свойство объекта с помощью Object.defineProperty() и вернуть свойство консоли, которое затем использовалось как обычная функция, но теперь с расширенной свободой.

var c = {};
var debugMode = true;

var createConsoleFunction = function(property) {
    Object.defineProperty(c, property, {
        get: function() {
            if(debugMode)
                return console[property];
            else
                return function() {};
        }
    });
};

Затем, чтобы определить свойство, которое вы только что сделали...

createConsoleFunction("warn");
createConsoleFunction("log");
createConsoleFunction("trace");
createConsoleFunction("clear");
createConsoleFunction("error");
createConsoleFunction("info");

И теперь вы можете использовать свою функцию так же, как

c.error("Error!");

Ответ 20

Вы могли бы упростить логику здесь. Это предполагает, что ваш глобальный флаг отладки НЕ является динамическим и устанавливается при загрузке приложения или передается в виде некоторой конфигурации. Это предназначено для использования для маркировки среды (например, печать только в режиме разработки, а не в производстве)

Ваниль JS:

(function(window){ 
  var Logger = {},
      noop = function(){};

  ['log', 'debug', 'info', 'warn', 'error'].forEach(function(level){
    Logger[level] = window.isDebug ? window.console[level] : noop;
  });

  window.Logger = Logger;
})(this);

ES6:

((window) => {
  const Logger = {};
  const noop = function(){};

  ['log', 'debug', 'info', 'warn', 'error'].forEach((level) => {
    Logger[level] = window.isDebug ? window.console[level] : noop;
  });

  window.Logger = Logger;
})(this);

Модуль:

const Logger = {};
const noop = function(){};

['log', 'debug', 'info', 'warn', 'error'].forEach((level) => {
  Logger[level] = window.isDebug ? window.console[level] : noop;
});

export default Logger;

Угловой 1.x:

angular
  .module('logger', [])
  .factory('Logger', ['$window',
    function Logger($window) {
      const noop = function(){};
      const logger = {};

      ['log', 'debug', 'info', 'warn', 'error'].forEach((level) => {
        logger[level] = $window.isDebug ? $window.console[level] : noop;
      });

      return logger;
    }
  ]);

Все, что вам нужно сделать сейчас, это заменить все ссылки на консоли на Logger

Ответ 21

Основываясь на других ответах (в основном @arctelix one), я создал это для Node ES6, но быстрый тест показал хорошие результаты и в браузере. Я просто передаю другую функцию в качестве ссылки.

let debug = () => {};
if (process.argv.includes('-v')) {
    debug = console.log;
    // debug = console; // For full object access
}