繁体   English   中英

在不影响日志行的情况下扩展 console.log

[英]Extending console.log without affecting log line

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

在此处输入图片说明

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

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

这就是我覆盖它的方式

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

有任何想法吗?

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

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

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

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/

可以轻松获得创意并传入上下文变量,并从函数定义中删除自动执行括号。 即 log("DEBUG:")("A debug message"), log("INFO:")("Here is some info") 等。

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

如果您的用例可以处理一些限制,则一种方法可以使其发挥作用。 限制是:

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

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

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

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/

至少在 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])
}

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

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

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

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

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/

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

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

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

my_log_alias = console.log.bind(console)

显然已经设计了这种能力。经过测试。 作品。

之后my_log_alias和console.log一样,可以用同样的方式调用; 从函数内部调用 this 将报告该函数调用的行号,包括适用的别名或建议函数内部的行。

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

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>缩进消息

希望有人会觉得它有用。

我已经多次研究过这个问题,但总是发现这是不可能的。

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

CoffeeScript 看起来不错,但不确定它是否适用于普通的 JS。

我只是养成了用x前缀所有内容的习惯。

logger.debug x 'Foo'

log x 'Bar'

log x('FooBar %o'), obj

Unfrotuantly 目前是不可能的,将来我们可能可以使用 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.');

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

扩展“console.debug”功能方法

/* 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)
                , ')'];
    }
}

不久前,Chrome 推出了一项功能,无需代码破解即可解决您的问题。 它被称为“黑匣子”,它基本上允许您标记应使用其工具忽略的文件。

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

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

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

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

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

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

const log = console.log;
export default function middleWare(optionalStringExtension = '') {
    console.log = (...args) => {
        log(...args, optionalStringExtension);
    }
}

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

我也遇到了这个关于扩展 console.log() 的问题,以便应用程序除了将内容记录到控制台之外,还可以扩展、控制和使用它做一些有趣的事情。 然而,丢失行号信息无异于失败​​。 在解决这个问题之后,我想出了一个冗长的解决方法,但至少它仍然是一个“1-liner”可以使用。

首先,定义一个全局类以使用或向现有的主要“app”类添加一些方法:

/**
 * 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));
    }
}

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

今天,您必须将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")();

此代码段将前缀应用于所有级别的日志( console.log console.debug console.info ...):

export const makeConsole = (context: string, cons = console): Console =>
  Object.getOwnPropertyNames(cons).reduce((c, lev) => {
    if (typeof cons[lev] === "function") {
      c[lev] = Function.prototype.bind.call(cons[lev], cons, context);
    }
    return c;
  }, {});

console.debug("Hello world!")
// >> Hello world!

console = makeConsole("[logging is fun]")
// >> [logging is fun] Hello world!

奖励,对于 React 窥视者:

export function useConsole(context: string): Console {
  return React.useMemo(() => makeConsole(context), [context]);
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM