扩展console.log而不影响日志行

时间:2012-03-04 23:09:01

标签: javascript google-chrome

我想扩展'console.log'函数以向其输出添加其他信息 - 但我不想影响浏览器在控制台窗口中生成的脚本名称/行号信息。看看如果我创建自己的实现,我得到无用的跟踪信息,我是否应该找到代码区域...(它们都链接到日志实现,而不是导致日志消息的实际脚本)

enter image description here

基本上,我的应用程序是一个非常可插拔的基础架构,任何日志输出都可能出现在任意数量的帧中。 因此,我希望每条日志消息都在日志​​消息的开头包含一个特殊的唯一标识符。

我尝试用自己的方法替换console.log方法,但chrome抱怨 Uncaught TypeError: Illegal invocation

这是我覆盖它的方式

var orig = console.log;
console.log = function( message )
{
    orig( (window == top ? '[root]' : '[' + window.name + ']') + ': ' + message );
}

有什么想法吗?

[编辑] 注意:修复“非法调用”问题后,覆盖率似乎仍然“污染”文件名/编号...

[编辑] 看起来一般的答案是 - 不 - 尽管有一些令人困惑的鹅追逐,但是在当前版本的浏览器中无法实现所需的功能。

15 个答案:

答案 0 :(得分:20)

是的,可以添加信息而不会弄乱日志调用的原始行号。这里的一些其他答案很接近,但诀窍是让您的自定义日志记录方法返回修改后的记录器。下面是一个简单的示例,仅使用上下文变体进行了适度测试。

log = function() {
    var context = "My Descriptive Logger Prefix:";
    return Function.prototype.bind.call(console.log, console, context);
}();

这可用于:

log("A log message..."); 

这是一个jsfiddle:http://jsfiddle.net/qprro98v/

可以轻松获得创意并传递上下文变量,并从函数定义中删除自动执行的parens。即log(“DEBUG:”)(“调试消息”),log(“INFO:”)(“这是一些信息”)等。

关于函数的唯一真正导入部分(关于行号)是它返回记录器。

答案 1 :(得分:10)

如果您的用例可以处理一些限制, 可以使其成为可行的方式。限制是:

  • 必须在绑定时计算额外的日志内容;它不能是时间敏感的,也不能以任何方式依赖传入的日志消息。

  • 额外的日志内容只能放在日志消息的开头。

有了这些限制,以下内容可能对您有用:

var context = "ALIASED LOG:"
var logalias;

if (console.log.bind === 'undefined') { // IE < 10
    logalias = Function.prototype.bind.call(console.log, console, context);
}
else {
    logalias = console.log.bind(console, context);
}

logalias('Hello, world!');

http://jsfiddle.net/Wk2mf/

答案 2 :(得分:8)

实际上至少可以使用chrome。这是最相关的。这可能会因设置而异,我获得分组的方法只是记录整个堆栈,并找到我需要的信息。

        var stack = new Error().stack;
        var file = stack.split("\n")[2].split("/")[4].split("?")[0]
        var line = stack.split("\n")[2].split(":")[5];

这就是整个事情,保留了本机对象的记录。

var orig = console.log
console.log = function(input) {
    var isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
    if(isChrome){
        var stack = new Error().stack;
        var file = stack.split("\n")[2].split("/")[4].split("?")[0]
        var line = stack.split("\n")[2].split(":")[5];
        var append = file + ":" + line;
    }
    orig.apply(console, [input, append])
}

答案 3 :(得分:4)

可接受的解决方案是创建自己的日志函数,该函数返回与日志参数绑定的console.log函数。

&#13;
&#13;
log = function() {
    // Put your extension code here
    var args = Array.prototype.slice.call(arguments);		
    args.unshift(console);
    return Function.prototype.bind.apply(console.log, args);
}

// Note the extra () to call the original console.log
log("Foo", {bar: 1})();
&#13;
&#13;
&#13;

这样console.log调用将从正确的行开始,并将在控制台中很好地显示,允许您点击它和所有内容。

答案 4 :(得分:3)

您需要使用正确的上下文(console.log)调用console

orig.call(console, message);

完成允许多个参数的函数:

var orig = console.log;
console.log = function() {
    var msgs = [],
        prefix = (window== top ? '[root]' : '[' + window.name + ']');
    while(arguments.length) {
        msgs.push(prefix + ': ' + [].shift.call(arguments));
    }
    orig.apply(console, msgs);
};

演示:http://jsfiddle.net/je2wR/

请记住,当使用 + 符号将对象与字符串组合时,您会在控制台中丢失内置对象/数组浏览器。

答案 5 :(得分:2)

我刚刚在一篇帖子中回答了这个问题,该帖子帮我回答了原来的“别名”问题:

(http://stackoverflow.com/a/12942764/401735)

my_log_alias = console.log.bind(console)

显然,执行此操作的能力已经过设计。经过测试。的工作原理。

此后my_log_alias与console.log相同,可以以相同的方式调用;从函数内部调用此函数将报告该函数调用的行号,包括别名或建议函数内的行(如果适用)。

具体来说,Chrome提供的行号会告诉您该行所在的文件,因此您所做的可能是不必要的;考虑将此报告为chrome中的错误/功能请求,它在console.log中提供此信息。

答案 6 :(得分:1)

Christopher Currie提供了一个出色的解决方案。为了我的需要,我已经扩展了一点。这是AMD模块:

define([], function () {

    var enableDebug = true;
    var separator = ">";    

    function bind(f, thisArg, ctx) {
        if (f.bind !== 'undefined') { // IE < 10
            return Function.prototype.bind.call(f, thisArg, ctx);
        }
        else {
            return f.bind(thisArg, ctx);
        }
    }

    function newConsole(context, parentConsole) {
        var log;
        var debug;
        var warn;
        var error;

        if (!parentConsole) {
            parentConsole = console;
        }

        context = context + separator;


        if (enableDebug) {
            debug = bind(console.log, console, context + "DEBUG" + separator);
        } else {
            debug = function () {
                // suppress all debug messages
            };
        }

        log = bind(console.log, console, context);

        warn = bind(console.warn, console, context);

        error = bind(console.error, console, context);

        return {
            debug: debug,
            info: log,
            log: log,
            warn: warn,
            error: error,
            /* access console context information */
            context: context,
            /* create a new console with nested context */
            nest: function (subContext) {
                return newConsole(context + subContext, this);
            },
            parent: parentConsole
        };
    }

    return newConsole("");
});

默认情况下,这将输出> {message}。您还可以将嵌套上下文添加到日志记录中,例如console.nest("my").log("test")将输出>my> test

我还添加了一个debug函数,该函数将使用>DEBUG>

缩进邮件

希望有人会发现它很有用。

答案 7 :(得分:1)

我已经多次调查过,并且总是发现这是不可能的。

如果您感兴趣,我的解决方法是将控制台分配给另一个变量,然后将所有日志消息包装在一个函数中,该函数允许我修改/样式/消息上的任何内容。

使用CoffeeScript看起来很不错,不确定它与普通JS的实用性。

我养成了用x为前缀添加前缀的习惯。

logger.debug x 'Foo'

log x 'Bar'

log x('FooBar %o'), obj

答案 8 :(得分:1)

毫无疑问,这是不可能的,将来我们可以使用ECMAScript 6中的Proxy对象来完成。

我的用例是使用有用的信息(如传递的参数和执行方法)自动为控制台消息添加前缀。目前我最接近的是使用Function.prototype.apply

一个简单的方法就是编写调试语句:

console.info('=== LazyLoad.css(', arguments, '): css files are skipped, gives us a clean slate to style within theme\'s CSS.');

一个复杂的方法是使用如下的帮助函数,我个人现在更喜欢简单的方法。

Extending 'console.debug' function approach

/* Debug prefixing function
 * ===========================
 * 
 * A helper used to provide useful prefixing information 
 * when calling `console.log`, `console.debug`, `console.error`.
 * But the catch is that to utilize one must leverage the 
 * `.apply` function as shown in the below examples.
 *
 * ```
 * console.debug.apply(console, _fDebugPrefix(arguments)
 *    .concat('your message'));
 *
 * // or if you need to pass non strings
 * console.debug.apply(console, _fDebugPrefix(arguments)
 *    .concat('json response was:', oJson));
 *
 *
 * // if you need to use strict mode ("use strict") one can't
 * // extract the function name but following approach works very
 * // well; updating the name is just a matter of search and replace
 * var aDebugPrefix = ['fYourFunctionName('
 *                     ,Array.prototype.slice.call(arguments, 0), 
 *                     ,')'];
 * console.debug.apply(console, 
 *                     aDebugPrefix.concat(['json response was:', oJson]));
 * ```
 */
function _fDebugPrefix(oArguments) {
    try {
        return [oArguments.callee.name + '('
                ,Array.prototype.slice.call(oArguments, 0)
                , ')'];
    }
    catch(err) { // are we in "use strict" mode ?
        return ['<callee.name unsupported in "use strict">('
                ,Array.prototype.slice.call(oArguments, 0)
                , ')'];
    }
}

答案 9 :(得分:1)

TS/JS 中的可重用类

// File: LogLevel.ts
enum LogLevel {
   error = 0,
   warn,
   info,
   debug,
   verbose,
 }

 export default LogLevel;
// File: Logger.js
import LogLevel from "./LogLevel";

export default class Logger {
  static id = "App";
  static level = LogLevel.info;

  constructor(id) {
    this.id = id;

    const commonPrefix = `[${Logger.id}/${this.id}]`;

    const verboseContext = `[V]${commonPrefix}`;
    if (console.log.bind === "undefined") {
      // IE < 10
      this.verbose = Function.prototype.bind.call(console.log, console, verboseContext);
    } else {
      this.verbose = console.log.bind(console, verboseContext);
    }
    if (LogLevel.verbose > Logger.level) {
      this.verbose = function() {
        return // Suppress
      };
    }

    const debugContext = `[D]${commonPrefix}`;
    if (console.debug.bind === "undefined") {
      // IE < 10
      this.debug = Function.prototype.bind.call(console.debug, console, debugContext);
    } else {
      this.debug = console.debug.bind(console, debugContext);
    }
    if (LogLevel.debug > Logger.level) {
      this.debug = function() {
        return // Suppress
      };
    }

    const infoContext = `[I]${commonPrefix}`;
    if (console.info.bind === "undefined") {
      // IE < 10
      this.info = Function.prototype.bind.call(console.info, console, infoContext);
    } else {
      this.info = console.info.bind(console, infoContext);
    }
    if (LogLevel.info > Logger.level) {
      this.info = function() {
        return // Suppress
      };
    }

    const warnContext = `[W]${commonPrefix}`;
    if (console.warn.bind === "undefined") {
      // IE < 10
      this.warn = Function.prototype.bind.call(console.warn, console, warnContext);
    } else {
      this.warn = console.warn.bind(console, warnContext);
    }
    if (LogLevel.warn > Logger.level) {
      this.warn = function() {
        return // Suppress
      };
    }

    const errorContext = `[E]${commonPrefix}`;
    if (console.error.bind === "undefined") {
      // IE < 10
      this.error = Function.prototype.bind.call(console.error, console, errorContext);
    } else {
      this.error = console.error.bind(console, errorContext);
    }
    if (LogLevel.error > Logger.level) {
      this.error = function() {
        return // Suppress
      };
    }
  }
}

用法(反应):

// File: src/index.tsx

// ...

Logger.id = "MCA"
const env = new Env()
if (env.env == Environment.dev) {
  Logger.level = LogLevel.verbose
  const log = new Logger("Main")
  log.info("Environment is 'Development'")
}

///...
// File: src/App/CookieConsent/index.tsx
import React, { useEffect } from "react";
import { useCookies } from "react-cookie";
import "./index.scss";

import Logger from "@lib/Logger" // @lib is just alias configured in webpack.

const cookieName = "mca-cookie-consent";

// const log = new Logger(CookieConsent.name) // IMPORTANT! Don't put log instance here. It is too early! Put inside function.

export default function CookieConsent(): JSX.Element {
  const log = new Logger(CookieConsent.name) // IMPORTANT! Have to be inside function, not in global scope (after imports)

  useEffect(() => {
    log.verbose(`Consent is accepted: ${isAccepted()}`);
  }, []);

  const [cookie, setCookie] = useCookies([cookieName]);

  function isAccepted(): boolean {
    return cookie[cookieName] != undefined;
  }

  function containerStyle(): React.CSSProperties {
    return isAccepted() ? { display: "none" } : {};
  }

  function handleClick() {
    const expires = new Date();
    expires.setFullYear(expires.getFullYear() + 1);
    log.verbose(`Accepted cookie consent. Expiration: ${expires}`)
    setCookie(cookieName, true, { path: "/", expires: expires, sameSite: "lax" });
  }

  return (
    <div className="cookieContainer" style={containerStyle()}>
      <div className="cookieContent">
        <div>
          <p className="cookieText">This website uses cookies to enhance the user experience.</p>
        </div>
        <div>
          <button onClick={handleClick} className="cookieButton">
            I understand
          </button>
        </div>
      </div>
    </div>
  );
}

浏览器控制台输出:

20:47:48.190 [I][MCA/Main] Environment is 'Development' index.tsx:19
20:47:48.286 [V][MCA/CookieConsent] Consent is accepted: false index.tsx:13
20:47:52.250 [V][MCA/CookieConsent] Accepted cookie consent. Expiration: Sun Jan 30 2022 20:47:52 GMT+0100 (Central European Standard Time) index.tsx:29

答案 10 :(得分:0)

我也遇到了关于扩展console.log()的问题,以便除了将内容记录到控制台之外,应用程序还可以扩展,控制和使用它。然而,丢失行号信息无异于失败​​。在与问题搏斗之后,我提出了一个冗长的解决方法,但至少它仍然是一个&#34; 1-liner&#34;使用。

首先,定义一个要使用的全局类,或者将一些方法添加到主现有的&#34; app&#34;类:

/**
 * Log message to our in-app and possibly on-screen console, return args.
 * @param {!string} aMsgLevel - one of "log", "debug", "info", "warn", or "error"
 * @param {any} aArgs - the arguments to log (not used directly, just documentation helper)
 * @returns args so it can be nested within a console.log.apply(console,app.log()) statement.
 */
MyGlobalClassWithLogMethods.prototype.debugLog = function(aMsgLevel, aArgs) {
    var s = '';
    var args = [];
    for (var i=1; i<arguments.length; i++) {
        args.push(arguments[i]);
        if (arguments[i])
            s += arguments[i].toString()+' ';
    }
    if (typeof this.mLog === 'undefined')
        this.mLog = [];
    this.mLog.push({level: aMsgLevel, msg: s});
    return args;
};

MyGlobalClassWithLogMethods.prototype.log = function() {
    var args = ['log'].concat(Array.prototype.slice.call(arguments));
    return this.debugLog.apply(this,args);
};

MyGlobalClassWithLogMethods.prototype.debug = function() {
    var args = ['debug'].concat(Array.prototype.slice.call(arguments));
    return this.debugLog.apply(this,args);
};

MyGlobalClassWithLogMethods.prototype.info = function() {
    var args = ['info'].concat(Array.prototype.slice.call(arguments));
    return this.debugLog.apply(this,args);
};

MyGlobalClassWithLogMethods.prototype.warn = function() {
    var args = ['warn'].concat(Array.prototype.slice.call(arguments));
    return this.debugLog.apply(this,args);
};

MyGlobalClassWithLogMethods.prototype.error = function() {
    var args = ['error'].concat(Array.prototype.slice.call(arguments));
    return this.debugLog.apply(this,args);
};

//not necessary, but it is used in my example code, so defining it
MyGlobalClassWithLogMethods.prototype.toString = function() {
    return "app: " + JSON.stringify(this);
};

接下来,我们将这些方法用于:

//JS line done as early as possible so rest of app can use logging mechanism
window.app = new MyGlobalClassWithLogMethods();

//only way to get "line info" reliably as well as log the msg for actual page display;
//  ugly, but works. Any number of params accepted, and any kind of var will get
//  converted to str using .toString() method.
console.log.apply(console,app.log('the log msg'));
console.debug.apply(console,app.debug('the log msg','(debug)', app));
console.info.apply(console,app.info('the log msg','(info)'));
console.warn.apply(console,app.warn('the log msg','(warn)'));
console.error.apply(console,app.error('the log msg','(error)'));

现在,控制台会获取包含相应行信息的日志消息,同时我们的应用程序还包含一系列可以使用的日志消息。例如,要使用HTML,JQuery和一些CSS显示您的应用内日志,可以使用以下简单示例。

首先,HTML:

<div id="debug_area">
    <h4 class="text-center">Debug Log</h4>
    <ul id="log_list">
        <!-- console log/debug/info/warn/error ('msg') lines will go here -->
    </ul>
</div>

一些CSS:

.log_level_log {
    color: black;
    background-color: white;
    font-size: x-small;
}
.log_level_debug {
    color: #060;
    background-color: #80FF80;
    font-size: x-small;
}
.log_level_info {
    color: #00F;
    background-color: #BEF;
    font-size: x-small;
}
.log_level_warn {
    color: #E65C00;
    background-color: #FB8;
    font-size: x-small;
}
.log_level_error {
    color: #F00;
    background-color: #FBB;
    font-size: x-small;
}

和一些JQuery:

var theLog = app.mLog || [];
if (theLog.length>0) {
    var theLogList = $('#log_list');
    theLogList.empty();
    for (var i=0; i<theLog.length; i++) {
        theLogList.prepend($('<li class="log_level_'+theLog[i].level+'"></li>').text(theLog[i].msg));
    }
}

这是一个简单的用法,但是一旦你有了这个机制,你就可以做任何你想象的事情,包括将日志行留在代码中,但设置一个阈值,以便只有警告和错误通过。希望这有助于他人的项目。

答案 11 :(得分:0)

尝试setTimeout(console.log.bind(console,'foo'));

答案 12 :(得分:0)

不久前,Chrome推出了一项功能,可以在没有代码破解的情况下解决您的问题。它被称为&#34; blackbox&#34;这基本上允许你标记应该用他们的工具忽略的文件。

https://gist.github.com/paulirish/c307a5a585ddbcc17242

是的,此解决方案是特定于浏览器的,但如果您使用Chrome,则需要此解决方案。

针对每个日志抛出错误的大量破解的解决方案可以显示正确的行,但它不会是控制台中的可点击链接。

基于绑定/别名的解决方案仅允许您修改打印文本。您将无法将参数转发给第三个函数以进行进一步处理。

答案 13 :(得分:0)

今天,您必须将 argsrest operator 一起使用,因为正如 Mozilla 文档所说,Function.arguments 已被弃用并且无法在箭头函数中访问。所以简单地你可以像下面这样扩展它:

//#1
const myLog= (...args) =>
  console.log.bind(console, ...args);
//myLog("this is my new log")();
//#2
const myNewLog= (...args) =>{
 const prefix = "Prefixed: ";
 return console.log.bind(console, ...[prefix,...args]);
}
//myNewLog("test")()

你可以像这样制作一个beautifulLog

//#3
const colorizedLog = (text, color= "#40a7e3", ...args) =>
  console.log.bind(
    console,
    `%c ${text}`,
    `font-weight:bold; color:${color}`,
    ...args
  );
//colorizedLog("Title:", "#40a7e3", "This is a working example")();

答案 14 :(得分:0)

希望这对您的某些情况有所帮助...

new ListView(
  scrollDirection: Axis.horizontal,
  crossAxisSize: CrossAxisSize.min,
  children: <Widget>[
    Column(
      children:[
    new ListItem(),
    new ListItem(),
       ], 
     )
    
  ],
);

作为中间件、文件顶部或函数的第一行运行。