51

我如何编写一个控制台日志包装器:

  • 保持日志语句记录的行号和文件名不变
  • 提供对所有日志严重性方法(错误、日志、调试...)的访问,并在控制台中显示它们,因为它们记录在哪里
  • 确实提供了一些回退(例如当浏览器不支持错误时调用 log 方法)
  • 可以在一个中心位置关闭,所以我可以关闭生产日志
  • 确实处理不存在控制台的情况,并且不抛出错误

由于在 Java Script 中登录是如此不一致,因此必须有一些解决方案。自己实现有点繁琐,但是好像没有好的库。

我目前发现这个记录器提供了所有功能,但它确实弄乱了行号。http://benalman.com/projects/javascript-debug-console-log/

4

8 回答 8

17

有我自己的log4javascript,它有自己的日志控制台,但也提供了一个包装器console.log。除了保持行号完整之外,它满足您的所有标准,如果您console.log()在另一个函数中包装对等的调用,这是不可能实现的。

var log = log4javascript.getLogger("main");
var appender = new log4javascript.BrowserConsoleAppender();
log.addAppender(appender);
log.debug("Hello world");
于 2012-07-03T10:18:31.930 回答
8

我还推荐 log4javascript 并解释如何仍然保留有关打印文件名和行的信息,至少在 Chrome 中。

我不是在谈论更改 Chrome 打印的文件名和行,但您可以获取您感兴趣的信息并将其附加到日志语句中。我的解决方案是快速破解,但我认为通过更多的工作,您可以获得格式良好的日志语句。它可能还会对性能产生重大影响,但是由于您不会在生产中激活日志,因此这应该不是太大的问题。

这个概念

在 Chrome 中,您可以创建一个错误对象,该对象提供一个堆栈属性,该属性显示您当前的堆栈位置以及堆栈字符串中的某个位置,您可以找到调用脚本的文件和行号。

  > new Error().stack
  "Error
    at eval at <anonymous> (eval at evaluate (unknown source))
    at eval at evaluate (unknown source)
    at FrameMirror.evaluate (native)
    at Object.evaluate (unknown source)
    at Object._evaluateOn (unknown source)
    at Object._evaluateAndWrap (unknown source)
    at Object.evaluateOnCallFrame (unknown source)
    at meinAjaxAufruf (http://localhost:8080/numberajax.js:21:9)
    at HTMLInputElement.onkeyup (http://localhost:8080/numberajax.html:15:188)"

对于 log4javascript 调用,堆栈跟踪可能如下所示:

"Error
    at Object.append (http://localhost:8080/log4javascript_uncompressed.js:1921:17)
    at Object.doAppend (http://localhost:8080/log4javascript_uncompressed.js:1047:9)
    at Object.callAppenders (http://localhost:8080/log4javascript_uncompressed.js:647:27)
    at Object.log (http://localhost:8080/log4javascript_uncompressed.js:640:10)
    at Object.debug (http://localhost:8080/log4javascript_uncompressed.js:748:9)
    at meinAjaxAufruf (http://localhost:8080/numberajax.js:36:16)
    at HTMLInputElement.onkeyup (http://localhost:8080/numberajax.html:16:188)"

进行 log4javascript 调用并且我感兴趣的文件和行是

at meinAjaxAufruf (http://localhost:8080/numberajax.js:36:16)

解决方案

我猜测从您感兴趣的脚本到实际console调用发生位置的堆栈深度总是相同的。所以现在你只需要找出BrowserConsoleAppender它的window.console访问位置并将你感兴趣的行添加到格式化的字符串中。我对log4javascript_uncompressed.js(版本 1.4.2 第 1913 行)进行了以下更改:

} else if (window.console && window.console.log) { // Safari and Firebug
        var formattedMesage = getFormattedMessage();

        //---my additions
        var isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
        if(isChrome){
            var stack = new Error().stack;
            var lineAccessingLogger = stack.split("\n")[6];
            formattedMesage += "\n" + lineAccessingLogger;
        }
        //---

        // Log to Firebug using its logging methods or revert to the console.log
        // method in Safari
        if (window.console.debug && Level.DEBUG.isGreaterOrEqual(loggingEvent.level)) {
            window.console.debug(formattedMesage);
        } else if (window.console.info && Level.INFO.equals(loggingEvent.level)) {
        ...

现在代替

17:53:22,872 DEBUG - sending /NumberServlet?zahl=1&text=
                                                 log4javascript.js:154

我明白了

17:55:53,008 DEBUG - sending /NumberServlet?zahl=1&text=

    at meinAjaxAufruf (http://localhost:8080/numberajax.js:36:16) log4javascript_uncompressed.js:1930

这肯定不是一个好的解决方案:),但我得到了我需要的东西。

通过对框架的更多了解,我想可以更改 PatternLayout 的方式,您可以定义如何打印文件名/位置和行号。

编辑我对 PatternLayout.prototype.format 函数进行了一些修改,而不是我之前的解决方案,所以现在我可以使用附加选项 %l 来定义我想要输出调用文件及其行的位置和方式。我以Gist的形式发布了我的更改和使用示例。

于 2012-08-23T16:00:44.283 回答
8

我们的日志包装器也遇到了这个问题,事实证明,使用部分函数应用程序有一个奇妙、简单的解决方法:

if(DEBUG_ENABLED && (typeof console != 'undefined')) {
    this.debug = console.log.bind(console);
}
else {
    this.debug = function(message) {};
}

这样,您的浏览器将检测到您要记录的源的正确行号和文件。

于 2014-06-13T16:14:47.213 回答
6

来自相关问题的交叉发布(具有正确行号的 console.log 的适当包装器?)但更新了解决多种方法的解决方案。


我喜欢@fredrik 的答案,所以我将它与另一个拆分 Webkit堆栈跟踪的答案卷起来,并将其与@PaulIrish 的安全 console.log 包装器合并。将其“标准化”filename:line为“特殊对象”,因此它在 FF 和 Chrome 中脱颖而出并且看起来几乎相同。

小提琴测试:http: //jsfiddle.net/drzaus/pWe6W/9/

_log = (function (methods, undefined) {

    var Log = Error; // does this do anything?  proper inheritance...?
    Log.prototype.write = function (args, method) {
        /// <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>
        /// <param name="method" type="string">the console method to use:  debug, log, warn, info, error</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
        /// * https://stackoverflow.com/a/3806596/1037948
        /// </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[method]) {
            if (console[method].apply) { console[method].apply(console, args); } else { console[method](args); } // nicer display in some browsers
        }
    };
    var extractLineNumberFromStack = function (stack) {
        /// <summary>
        /// Get the line/filename detail from a Webkit stack trace.  See https://stackoverflow.com/a/3806596/1037948
        /// </summary>
        /// <param name="stack" type="String">the stack string</param>

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

    // method builder
    var logMethod = function(method) {
        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), method); // turn into proper array & declare method to use
        };//--  fn  logMethod
    };
    var result = logMethod('log'); // base for backwards compatibility, simplicity
    // add some extra juice
    for(var i in methods) result[methods[i]] = logMethod(methods[i]);

    return result; // expose
})(['error', 'debug', 'info', 'warn']);//--- _log
于 2013-02-12T22:01:46.723 回答
2

为简单起见,我为控制台方法提供了以下包装器:

var noop = function () {};
window.consolex = {
    debug : window.console && window.console.debug && console.debug.bind(console) || noop,
    log : window.console && window.console.log && console.log.bind(console) || noop,
    warn: window.WARN = window.console && window.console.warn && console.warn.bind(console) || noop,
    error: window.ERROR = window.console && window.console.error && console.error.bind(console) || noop
};

此外,为了更好地在 IE 和旧浏览器中登录,请阅读:详细的控制台日志记录

于 2013-05-28T05:54:36.043 回答
2

谷歌浏览器很快就会有这个线程感兴趣的功能。

您现在可以通过以下方式启用它:

  1. 启用 chrome://flags/#enable-devtools-experiments
  2. 单击开发工具中的 cog
  3. 转到实验选项卡
  4. 检查“Javascript框架调试”
  5. 转到常规选项卡
  6. 在来源部分下
  7. 选中“跳过具有特定名称的源”
  8. 在模式输入框中:输入您现在看到的文件名 (app.log.js)

重新启动并享受:)

参考:

来自 chrom devtools 的测试

devtools 问题线程

开发工具代码审查

于 2014-09-08T16:49:38.553 回答
2

我在这里回答了这个问题,但简而言之,请参阅codepen以获得完整的实现。但是,这可以满足您的所有需求,跨浏览器,没有错误,正确的行号,所有可用的控制台方法,全局和本地控制:

var Debugger = function(gState, klass) {
  this.debug = {}
  if (!window.console) return function(){}
  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!')
于 2015-10-04T01:28:34.470 回答
-2

我在网上找到了一个解决方案(需要 jquery),但它在大多数浏览器中都不起作用。我对其进行了更改,它可以在 Firefox(Mac、Linux.Android)、Chrome(Mac、Linux.Android)和 Safari 以及其他 Android webkit 浏览器中运行。

只需将以下代码写入名为例如 debug.js 的文件,并在网页的 <head> 部分中包含“jquery.js”后将其包含在内,它会在页面加载后工作(document.ready)。我仍然需要在加载所有内容之前进行调试(例如,只有 <head>...</head> )。该网页必须在 URL 中使用 ?d=1 调用,并且在使用 Safari 时 ?d=1s 因为我无法区分 Safari 和用户代理中的另一个 Webkit 浏览器,并且 Safari 在行号和文件名方面具有不同的行为处理比其他 Webkit 浏览器。

函数 p_r(expression) 记录到 id #js_debug 的窗口和带有文件名和行号的控制台(如果打开)。

var g_d = null;


function sortObj(theObj)
{
  var sortable = [];
  for (var i in theObj) {
    sortable.push(i);
  }
  sortable.sort();

  var copy = new Object;
  for (var i in sortable) {
    var ind = sortable[i];
    copy[ind] = theObj[ind];
  }

  return copy;

}

function p_r(s, comment, level)
{
  if (!g_d) return;
  var res = s;
  var pre = new Array("","  " , "    ", "      ", "        ");
  if (comment) comment += ' : ';
  if (arguments.length<2) comment='';
  if (arguments.length<3) level = 0;
//  if (console) console.log(s);
  if (typeof(s) == 'object') {
    var copy = sortObj(s);
    comment += '\n';
    res = '[object]\n';
    if (level < 2) {
      for (var i in copy) {
        if (typeof(copy[i]) != "function")
          res += pre[level] + (i) + " : " + p_r(copy[i], '', level+1) +  " : " + typeof(copy[i]) + "\n";
      }
      res += pre[level] + "[/object]\n";
    }
  }
  else if (typeof(s) == 'function')
    res = 'function';
  else if (typeof(s) != 'string')
    res = '' + s;
  res = res.replace(/&/g, '&amp;');
  res = res.replace(/\x3C/g, '&lt;');
  res = res.replace(/>/g, '&gt;');
  if (level == 0) {
window.LOG=res;
console.log(window.LOG + comment + res);
    g_d.innerHTML += (window.LOG + comment + res + '\n');
  }
  return res;
}

if (location.href.match(/d\=[1-9]/)) {

  $(document).ready(function() {
    $("body").prepend("<div id=\"js_debugclick\" onclick=\"$('#js_debug').toggle();\">JS DEBUG</div>\
  <pre onclick=\"$('#js_debug').toggle();\" id='js_debug'></pre>\
");

    $("head").append("<style type=\"text/css\">\
pre#js_debug {\
border: solid black 1px; background-color: #1CF; color: #000; display:none; position:absolute; top: 20px;\
font-family: Lucida Console, monospace; font-size: 9pt; height: 400px; overflow:scroll; width:100%;\
z-index:100;\
} \
#js_debugclick { \
  color:red; font-weight:bold; \
} \
</style>\
");
    g_d = document.getElementById('js_debug');
  });

  var __moredebug = location.href.match(/d\=[2-9]/);

    var __issafari = /safari/.test(navigator.userAgent.toLowerCase()) && location.href.match(/d\=[1-9]s/);
    var __iswebkit = /webkit/.test(navigator.userAgent.toLowerCase());
    var __isopera  = /opera/.test(navigator.userAgent.toLowerCase());
  if (__moredebug) console.log(__issafari, __iswebkit);

/*@const*/ //for closure-compiler
//DEBUG=2 // 0=off, 1=msg:file:line:column, 2=msg:stack-trace

/*@const @constructor*/
Object.defineProperty(window,'__stack__',{get:function(){
    try{i.dont.exist()}catch(e){
if (__moredebug)  var x=e.stack.split(":"); for (i in x){console.log(i,x[i]);}
//    console.log(e.stack.split(":")[13].match(/(\d+)/)[1]);
    return e.stack.split(":")}
}})

/*@const @constructor*/
Object.defineProperty(window,'__file__',{get:function(){
    var s=__stack__,l=s.length
    var f= __issafari ? s[9] : (__isopera ? s[12] : (__iswebkit ? s[14] : s[9]));
    return f.replace(/^.+?\/([^\/]+?)\?.+?$/, "$1");
}})

/*@const @constructor*/
Object.defineProperty(window,'__line__',{get:function(){
    var s=__stack__,l=s.length
    return __issafari ? s[10].match(/(\d+)/)[1] :(__isopera ? s[13].match(/(\d+)/)[1] : (__iswebkit ? s[15] : s[10].replace(/\n/, " ").replace(/(\d+).+?$/, "$1")));
}})

/*@const @constructor*/
Object.defineProperty(window,'__col__',{get:function(){
    var s=__stack__,l=s.length
    return (isNaN(s[l-2]))?"NA":s[l-1]
}})

/*@const @constructor*/
Object.defineProperty(window,'LOG',{
    get:function(){return out},
    set:function(msg){if(0)out=msg+"\t-\t"+__stack__
        else out=__file__+" "+__line__+": ";
        }
})



}//end if(DEBUG)
于 2013-07-17T19:18:12.047 回答