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) 

如果我想loggingdebug()被调用的行号,如info: Here is a msg. main.js (line 2) info: Here is a msg. main.js (line 2)

这是一个老问题,提供的所有答案都过分hackey,有主要的跨浏览器的问题,并没有提供任何超级有用的东西。 这个解决scheme在每个浏览器中都能正常工作,并且完全按照它应该报告所有的控制台数据 不需要黑客和一行代码检查codepen 。

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

像这样创build开关:

 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.') 

你甚至可以用这样的开关来接pipeconsole.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') } 

我喜欢@fredrik的回答 ,所以我把它分解成另一个分解Webkit 堆栈跟踪的答案 ,并将其与@ PaulIrish的安全console.log包装器合并。 “标准化”的filename:line到一个“特殊的对象”,所以它脱颖而出,在FF和Chrome看起来大致相同。

在小提琴中testing: 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 /// * 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.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 https://stackoverflow.com/a/3806596/1037948 /// </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 

From: 如何获取JavaScript调用者函数行号? 如何获得JavaScript调用者的源URL? Error对象有一个行号属性(在FF中)。 所以这样的事情应该工作:

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

在Webkit浏览器中,你有err.stack ,它是一个表示当前调用栈的string。 它将显示当前行号和更多信息。

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); 

您可以使用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) 

保持行号的方法是: https : //gist.github.com/bgrins/5108712 。 这或多或less地归结为:

 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包装这个,如果你没有debugging,把window.log设置为function() { }

您可以将行号传递给您的debugging方法,如下所示:

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

在这里, (new Error).lineNumber会给你你的javascript代码中的当前行号。

如果你只是想控制是否使用debugging,并有正确的行号,你可以这样做:

 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(){} }; } 

当你需要访问debugging时,你可以这样做:

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

如果isDebug == true ,则控制台中显示的行号和文件名将是正确的,因为debug.log等实际上是console.log等的别名。

如果isDebug == false ,则不显示debugging消息,因为debug.log等简单地什么都不做(空函数)。

正如你已经知道的那样,一个包装函数会弄乱行号和文件名,所以最好不要使用包装函数。

Chrome Devtools可以让你用Blackboxing实现这一点。 您可以创buildconsole.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设置,find“Blackboxing”部分,添加一个你想黑盒子的文件名,在这种情况下,log-blackbox.js

 //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) info: "Here is a msg." main.js(line:2)

但额外的评价是需要的,可惜。

来自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); 

最近我一直在看这个问题。 需要一些非常简单的事情来控制日志logging,而且还要保留行号。 我的解决scheme在代码中看起来并不优雅,但提供了我所需要的。 如果一个小心封闭和保留。

我在应用程序的开始处添加了一个小包装器:

 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");}); 

我已经testing了firefox和crome,两个浏览器似乎都按预期显示了控制台日志。 如果你这样填充,你总是可以扩展'd'方法并将其他parameter passing给它,以便它可以做一些额外的日志logging。

对于我的方法,还没有发现任何严重的缺陷,除了日志代码中的丑陋行。

堆栈跟踪解决scheme显示行号,但不允许点击去源,这是一个主要问题。 保持此行为的唯一解决scheme是绑定到原始函数。

绑定可以防止包含中间逻辑,因为这个逻辑会混乱行号。 然而,通过重新定义绑定的函数和使用控制台stringreplace ,一些额外的行为仍然是可能的。

这个要点显示了一个简约的日志框架,提供了34行中的模块,日志级别,格式和适当的可点击行数。 用它作为你自己需要的基础或灵感。

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

我发现了一个简单的解决scheme,将接受的答案(绑定到console.log / error / etc)与一些外部逻辑结合起来,以过滤实际logging的内容。

 /* * Simple logging utility. */ 'use strict'; /* * Global logging trigger. * Logging a message via `LOG && log.v('x');` allows minification * tools to omit logging lines altogether when LOG is false. */ const LOG = true; var log = {}; log.ASSERT = 1; log.ERROR = 2; log.WARN = 3; log.INFO = 4; log.DEBUG = 5; log.VERBOSE = 6; log.setLevel = function(level) { if (level >= log.ASSERT) { log.a = console.assert.bind(window.console); } else { log.a = function() {} } if (level >= log.ERROR) { log.e = console.error.bind(window.console); } else { log.e = function() {} } if (level >= log.WARN) { log.w = console.warn.bind(window.console); } else { log.w = function() {} } if (level >= log.INFO) { log.i = console.info.bind(window.console); } else { log.i = function() {} } if (level >= log.DEBUG) { log.d = console.debug.bind(window.console); } else { log.d = function() {} } if (level >= log.VERBOSE) { log.v = console.log.bind(window.console); } else { log.v = function() {} } log.level = level; }; log.setLevel(log.VERBOSE); 

我发现这个问题的一些答案有点太复杂,我的需求。 这是一个简单的解决scheme,在Coffeescript中呈现。 这是从Brian Grinstead的版本改编而来的

它假定全局控制台对象。

 # 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) )() 
 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()\'s 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\t@'+_line]); window.console.log.apply(window.console, args); } log('hello'); 

这是一种保持现有console日志logging语句的同时在输出中添加文件名和行号或其他堆栈跟踪信息的方法:

 (function () { 'use strict'; var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; var isChrome = !!window.chrome && !!window.chrome.webstore; var isIE = /*@cc_on!@*/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上testing过)。

我解决这个问题的方法是创build一个对象,然后使用Object.defineProperty()在对象上创build一个新的属性,然后返回控制台属性,然后用作普通函数,但现在使用扩展的能力。

 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"); 

现在你可以像使用你的function一样

 c.error("Error!");