【问题标题】:How to get Javascript Function Calls/Trace at Runtime如何在运行时获取 Javascript 函数调用/跟踪
【发布时间】:2012-08-04 21:33:33
【问题描述】:

当我在 RUNTIME 与我的基于 AJAX 的应用程序交互时,我希望控制台能够输出它调用的所有函数。 (所以没有堆栈跟踪、断点、分析或任何东西)

例如,假设我按下了页面上的一个按钮。我想让它返回它的所有功能 发生这种情况时经历过:

所以我会在控制台中看到类似的内容(当我按下按钮时):

1. button1Clicked();
2.     calculating();
3.          printingResults();

这基本上意味着 button1Clicked() 调用了 computed() 调用了 printingResults()

是否有实用程序、插件、浏览器或语言中的某种方式来执行此操作?顺便说一句,我正在使用谷歌浏览器。

p.s 和不,我不想遍历每个函数并添加一个 "console.log("inside function X")" b/c 这太多的工作

p.p.s 作为额外的奖励,我也希望看到参数也传递到函数中,但也许这是在推动它。 :>

【问题讨论】:

  • 嗯,你可以在一个的地方使用console.trace(),而不是在很多地方使用console.log。然后堆栈将出现在开发人员工具中。这是一个可以接受的解决方案吗?

标签: javascript function google-chrome runtime trace


【解决方案1】:

我想不出一个很好的方法来全局拦截所有函数调用以插入日志记录(尽管下面的更新部分有一个不错的解决方法)。

相反,仅将日志记录添加到您关心的某个命名空间中的函数怎么样?您可以使用以下设置代码执行此操作:

var functionLogger = {};

functionLogger.log = true;//Set this to false to disable logging 

/**
 * Gets a function that when called will log information about itself if logging is turned on.
 *
 * @param func The function to add logging to.
 * @param name The name of the function.
 *
 * @return A function that will perform logging and then call the function. 
 */
functionLogger.getLoggableFunction = function(func, name) {
    return function() {
        if (functionLogger.log) {
            var logText = name + '(';

            for (var i = 0; i < arguments.length; i++) {
                if (i > 0) {
                    logText += ', ';
                }
                logText += arguments[i];
            }
            logText += ');';

            console.log(logText);
        }

        return func.apply(this, arguments);
    }
};

/**
 * After this is called, all direct children of the provided namespace object that are 
 * functions will log their name as well as the values of the parameters passed in.
 *
 * @param namespaceObject The object whose child functions you'd like to add logging to.
 */
functionLogger.addLoggingToNamespace = function(namespaceObject){
    for(var name in namespaceObject){
        var potentialFunction = namespaceObject[name];

        if(Object.prototype.toString.call(potentialFunction) === '[object Function]'){
            namespaceObject[name] = functionLogger.getLoggableFunction(potentialFunction, name);
        }
    }
};

然后,对于您想要添加日志记录的任何 namespaceObject,您只需调用:

functionLogger.addLoggingToNamespace(yourNamespaceObject);

Here's a fiddle 看看它的实际效果。

更新
请注意,您可以调用 functionLogger.addLoggingToNamespace(window); 在调用时将日志记录添加到所有全局函数。此外,如果您真的需要,您可以遍历树以查找任何函数并相应地更新它们。这种方法的一个缺点是它只适用于当时存在的函数。因此,它仍然不是最好的解决方案,但它比手动添加日志语句要少很多工作:)

【讨论】:

  • 感谢有趣的答案,但我需要它比这更通用,以处理非命名空间函数。是的,我确实想要“全局拦截所有函数调用以插入日志记录”。 (也许不是 jQuery) 必须有一种方法......希望浏览器支持这一点。它会让程序员的生活变得更轻松
  • @foreyez:没问题,抱歉我没有一个好的解决方案。同意,我多次认为这会很好:)
  • @foreyez:它不适用于小提琴,但尝试functionLogger.addLoggingToNamespace(window); 将其添加到所有全局函数中。此外,如果您愿意,您可以从那里向下迭代对象树以将日志记录添加到所有内容。
  • 很棒的东西,但由于某种原因,它使我的一些函数调用不起作用,我仍然需要研究为什么它会搞砸它们......但感谢你让这个走上正轨跨度>
  • @foreyez:欢迎您,希望您能解决。如果你有或有一个可重现的案例,请告诉我们,因为它会很好地纠正这里的任何问题或调试它。我认为这可能是范围问题,但我尝试过的每个测试用例都有正确的范围,所以不确定发生了什么。
【解决方案2】:

这称为配置文件,Chrome 和 Firebug 内置了它。在Chrome developer Tools 中,转到配置文件选项卡并单击记录(圆圈)按钮。执行您的 ajax 并在您响应后,再次单击记录按钮停止。分析的结果将显示在右侧窗格中。

注意,这将为您提供一切,因此如果您使用像 jQuery 这样的库,那么绝大多数函数调用对您来说都是垃圾。我已经尝试了几次,我发现做 console.log('inside &lt;method&gt;') 的事情会更有帮助。

【讨论】:

  • 嗯,我不想要分析器。我只想在控制台中显示 func 调用。也许在我输入 turnOnTrace() 或其他内容之后......另外,我不想跟踪 jquery 调用。所以它应该给我一个排除库的选项。最后,分析选项卡没有显示我的内部函数(),就像我写的那样,所以它甚至不起作用。也许有一种方法可以使每个函数中的 console.log 自动化。
【解决方案3】:

我刚刚发现您可以通过 console.trace() 做到这一点

【讨论】:

    【解决方案4】:

    Briguy37 解决方案的一种变体,我编写了一个接受在每个方法之前调用的函数的解决方案。它也适用于 ECMAScript 6 类,其中方法不是由 for...in 枚举的。我正在使用它来修改对象原型,将日志记录添加到我的对象的所有新实例中。

    function inject(obj, beforeFn) {
        for (let propName of Object.getOwnPropertyNames(obj)) {
            let prop = obj[propName];
            if (Object.prototype.toString.call(prop) === '[object Function]') {
                obj[propName] = (function(fnName) {
                    return function() {
                        beforeFn.call(this, fnName, arguments);
                        return prop.apply(this, arguments);
                    }
                })(propName);
            }
        }
    }
    
    function logFnCall(name, args) {
        let s = name + '(';
        for (let i = 0; i < args.length; i++) {
            if (i > 0)
                s += ', ';
            s += String(args[i]);
        }
        s += ')';
        console.log(s);
    }
    
    inject(Foo.prototype, logFnCall);
    

    【讨论】:

    • 不幸的是,这不适用于类:TypeError: Class constructors cannot be invoked without 'new'
    【解决方案5】:

    也许您可以让 JavaScript 为您完成添加 console.log 的一些工作:

    Adding console.log to every function automatically

    Paul Irish 的这篇博客也可能有所帮助:

    http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/

    它包含一个指向一些专门针对记录参数的 JavaScript 的链接:

    http://pastie.org/1033665

    【讨论】:

      【解决方案6】:

      试试diyism_trace_for_javascript.htm:

      https://code.google.com/p/diyism-trace/downloads/list

      eval('window.c=function(){3+5;}');
      declare_ticks_for(window);
      
      function a(k, c) {
        return k + 2;
      }
      
      function b() {
        4 + 3;
        a(3, {'a':'c','b':'d'});
        c();
        return 5 + 4;
      }
      
      b();
      

      在 chrome 或 firefox 的控制台选项卡中查看日志

      【讨论】:

        【解决方案7】:

        让我把第三种(当然也有些不完美的)解决方案扔进戒指。

        请注意,所有其他答案都提供两种解决方案:

        1. 在运行时手动修补您的 JS 函数,并将它们记录到控制台
          • 是的,它可以完成工作,但一旦您的项目增长到一定规模,它将毫无用处。它不会为您提供足够的可控性,除非您一直花时间继续开发此功能。
        2. Jeff 建议使用分析器进行调试
          • 不是很有帮助,因为分析器视图(至少目前是这样)旨在帮助您分析性能,而不是调用图;效果不好,除非您花大量时间训练自己习惯适得其反的用户界面。

        这就是我编写 Dbux 的原因,这是一项正在进行中的工作,目前只能作为 VSCode 插件使用,但有一些限制。然而,它是一个无所不知的调试器,具有动态执行分析工具、代码注释和成熟的动态调用图可视化工具,旨在帮助开发人员进行程序理解和调试:

        链接:

        【讨论】:

          【解决方案8】:

          我使用@Briguy37 的解决方案进行了改进。就我而言,我不想跟踪某些库中的函数,所以我添加了一些代码来排除它们。以下是它的使用方法:

          • 首先,包括您不想跟踪的函数的定义;
          • excludeLoggingToNamespace 列出迄今为止定义的函数并排除它们;
          • 包括要跟踪的函数的定义;
          • 调用 addLoggingToNamespace 为上述步骤中定义的函数添加日志记录功能。

          例子:

          <script src="js/someLibrary.js"></script>
          <script>
              functionLogger.excludeLoggingToNamespace(window);
          </script>
          <script src="js/codeIWantToTraceHere.js"></script>
          <script>
              functionLogger.addLoggingToNamespace(window);
          </script>
          

          这是我添加到@Briguy37 解决方案中的代码:

          var excludedFunctions = {};
          
                  functionLogger.excludeLoggingToNamespace = function(namespaceObject){
                      for(var name in namespaceObject){
                          var potentialFunction = namespaceObject[name];
          
                          if(Object.prototype.toString.call(potentialFunction) === '[object Function]') {
                              excludedFunctions[name] = name;
                          }
                      }
                  }; 
          

          而且我不得不修改 @Briguy37 的 addLoggingToNamespace 方法以考虑到 excludeFunctions 哈希:

          functionLogger.addLoggingToNamespace = function(namespaceObject){
              for(var name in namespaceObject){
                  var potentialFunction = namespaceObject[name];
          
                  if(Object.prototype.toString.call(potentialFunction) === '[object Function]' && 
                     !excludedFunctions[name]) {
                      namespaceObject[name] = functionLogger.getLoggableFunction(potentialFunction, name);
                  }
              }
          };    
          

          【讨论】:

            【解决方案9】:

            您可以在putout code transformer 的帮助下跟踪函数调用。 Plugin 会这样看:

            const {template, types, operator} = require('putout');
            const {replaceWith} = operator;
            const {BlockStatement} = types;
            
            // create nodes
            const buildLog = template(`console.log('TYPE' + ' ' + 'NAME')`);
            const buildLogEnter = template(`console.log('enter' + ' ' + 'NAME' + '(' + JSON.stringify(Array.from(arguments)) + ')')`);
            const buildLogException = template(`console.log('TYPE' + ' ' + 'NAME' + ': ' + trace$error.message); throw trace$error`);
            const buildTryCatch = template(`try {
                    BLOCK;
                } catch(trace$error) {
                    CATCH;
                } finally {
                    FINALLY;
                }
            `);
            
            const JSON = 'JSON';
            
            // nodes we are searching for
            module.exports.include = () => [
                'Function',
            ];
            
            module.exports.fix = (path) => {
                const name = getName(path);
                
                // create 3 types of events
                const enterLog = buildLogEnter({
                    NAME: name,
                    JSON,
                });
                const exitLog = buildLogEvent(name, 'exit');
                const errorLog = buildLogExceptionEvent(name);
                
                // move function body into try-catch
                const bodyPath = path.get('body');
                replaceWith(bodyPath, BlockStatement([buildTryCatch({
                    BLOCK: path.node.body.body,
                    CATCH: errorLog,
                    FINALLY: exitLog,
                })]));
                
                // add into the beginning of function "console.log" with "enter" event
                bodyPath.node.body.unshift(enterLog);
            };
            
            
            // get name of a function
            function getName(path) {
                if (path.isClassMethod())
                    return path.node.key.name;
                
                if (path.isFunctionDeclaration())
                    return path.node.id.name;
                
                const {line} = path.node.loc.start;
                return `<anonymous:${line}>`;
            }
            
            // build logger
            function buildLogEvent(name, type) {    
                return buildLog({
                    NAME: name,
                    TYPE: type,
                });
            }
            
            // build logger that throws
            function buildLogExceptionEvent(name) {    
                return buildLogException({
                    NAME: name,
                    TYPE: 'error',
                });
            }
            

            假设这是您要跟踪的代码:

            const processFile = (a) => a;
            process([]);
            
            function process(runners) {
                const files = getFiles(runners);
                const linted = lintFiles(files);
                
                return linted;
            }
            
            function getFiles(runners) {
                const files = [];
                
                for (const run of runners) {
                    files.push(...run());
                }
                
                return files;
            }
            
            function lintFiles(files) {
                const linted = [];
                
                for (const file of files) {
                    linted.push(processFile(file));
                }
               
                return linted;
            }
            

            这是一张全图:

            如果您将处理后的源代码保存为trace.js 并使用节点运行它,您将拥有:

            > node trace.js
            enter process([[]])
            enter getFiles([[]])
            exit getFiles
            enter lintFiles([[]])
            exit lintFiles
            exit process
            

            putout issue related to tracing functions

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-12-04
              • 2014-05-23
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多