简体   繁体   English

如何在运行时获取 Javascript Function 调用/跟踪

[英]How to get Javascript Function Calls/Trace at Runtime

As I interact with my AJAX based application at RUNTIME I'd like the console to spit out all the functions it's calling.当我在运行时与基于AJAX的应用程序进行交互时,我希望控制台能够吐出它正在调用的所有函数。 (so no stack trace, or breakpoints, or profiling or anything) (所以没有堆栈跟踪、断点、分析或任何东西)

So for example, let's say I pressed a button on the page.例如,假设我按下了页面上的一个按钮。 I'd like for it to return all the functions it went through when that happened:我希望它返回它在发生这种情况时经历的所有功能:

So I'd see in the console something like (when I pressed a button):所以我会在控制台中看到类似的东西(当我按下按钮时):

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

Which basically means that button1Clicked() called calculating() which called printingResults()这基本上意味着 button1Clicked() 调用了 computed() 调用了 printingResults()

Is there a utility, or plugin, browser, or maybe some way in the language to do this?是否有实用程序、插件、浏览器或语言中的某种方式来执行此操作? I'm using google chrome, btw.我正在使用谷歌浏览器,顺便说一句。

ps and NO I do not want to go through each function and add a "console.log("inside function X")" b/c that's too much work ps 和 NO 我不想 go 通过每个 function 并添加一个"console.log("inside function X")"

pps as an added bonus I'd like to see the arguments passed into the functions too, but maybe that's pushing it.:> pps 作为额外的奖励,我希望看到 arguments 也传递到函数中,但也许这是在推动它。:>

I can't think of a great way to intercept all function calls globally to insert logging (though there is a decent workaround in the update section below).我想不出一个很好的方法来拦截所有 function 全局调用以插入日志记录(尽管下面的更新部分有一个不错的解决方法)。

Instead, how about only adding logging to functions in a certain namespace that you care about?相反,仅将日志记录添加到您关心的某个命名空间中的函数怎么样? You can do this with the following setup code:您可以使用以下设置代码执行此操作:

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

Then, for whatever namespaceObject you want to add logging to, you just call:然后,对于要添加日志记录的任何命名空间对象,您只需调用:

functionLogger.addLoggingToNamespace(yourNamespaceObject);

Here's a fiddle to see it in action.这是一个在行动中看到它的小提琴

UPDATE更新
Note that you can call functionLogger.addLoggingToNamespace(window);请注意,您可以调用functionLogger.addLoggingToNamespace(window); to add logging to all global functions at the time of the call.在调用时将日志记录添加到所有全局函数。 Also, if you really want, you can traverse the tree to find any functions and update them accordingly.此外,如果您真的需要,您可以遍历树以查找任何函数并相应地更新它们。 The one downfall of this method is that it only works on functions that exist at the time.这种方法的一个缺点是它只适用于当时存在的函数。 Thus, it's still not the greatest solution, but it's a LOT less work than adding logging statements by hand:)因此,它仍然不是最好的解决方案,但它比手动添加日志语句要少很多工作:)

This is called profiling and Chrome and Firebug have it built in. In Chrome developer Tools , go to the profiles tab and click the record (circle) button.这称为分析,Chrome 和 Firebug 内置了它。在Chrome 开发人员工具中,go 到配置文件选项卡,然后单击记录(圆圈)按钮。 Perform your ajax and after your response, click the record button again to stop.执行您的 ajax 并在您响应后,再次单击录制按钮停止。 The results of the profiling will appear in the right pane.分析的结果将显示在右窗格中。

Note, this is going to give you everything so if you are using a library like jQuery, the vast majority of the function calls are going to be garbage to you.请注意,这将为您提供一切,因此如果您使用像 jQuery 这样的库,那么绝大多数 function 调用对您来说都是垃圾。 I've tried this a few times and I find it is much more helpful to do the console.log('inside <method>') thing.我已经尝试了几次,我发现执行console.log('inside <method>')会更有帮助。

I just found out that you could do that with a console.trace()我刚刚发现您可以使用console.trace()

Maybe you can have JavaScript do some of the work of adding console.log for you:也许你可以让 JavaScript 为你做一些添加 console.log 的工作:

Adding console.log to every function automatically 自动将 console.log 添加到每个 function

Also this blog by Paul Irish might help: Paul Irish 的这个博客也可能会有所帮助:

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

It includes a link to some JavaScript specifically targeted at logging arguments:它包括指向一些 JavaScript 的链接,专门针对记录 arguments:

http://pastie.org/1033665 http://pastie.org/1033665

Give a try to diyism_trace_for_javascript.htm:试试diyism_trace_for_javascript.htm:

https://code.google.com/p/diyism-trace/downloads/list 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();

View logs in console tab of chrome or firefox在 chrome 或 firefox 的控制台选项卡中查看日志

A variation on Briguy37's solution, I wrote one that accepts a function to call before each method. Briguy37 解决方案的一种变体,我编写了一个接受 function 在每个方法之前调用的解决方案。 It also works with ECMAScript 6 classes, where methods are not enumerated by for...in.它也适用于 ECMAScript 6 类,其中方法不是由 for...in 枚举的。 I'm using it to modify Object prototypes, to add logging to all new instances of my object.我用它来修改 Object 原型,为我的 object 的所有新实例添加日志记录。

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

Let me throw a third solution into the ring: an omniscient debugger .让我抛出第三种解决方案:无所不知的调试器

Note that all other answers offer two kinds of solutions:请注意,所有其他答案都提供两种解决方案:

  1. Manually patch your JS functions in run-time, and log them to console在运行时手动修补您的 JS 函数,并将它们记录到控制台
    • Yes it can get the job done, but will be useless once your project grows to a certain size.是的,它可以完成工作,但是一旦您的项目发展到一定规模,它将毫无用处。 It does not give you sufficient controlability, unless you keep spending time on keeping on deleloping this feature.它不会给你足够的可控性,除非你一直花时间去开发这个功能。
  2. Jeff proposes using a profiler for debugging purposes Jeff 建议使用分析器进行调试
    • Not very helpful, as the profiler views (at least for now) are designed to help you analyze performance, not the call graph;不是很有帮助,因为分析器视图(至少现在)旨在帮助您分析性能,而不是调用图; does not work well, unless you spend a lot of time training yourself to getting used to the counter-productive user interface.效果不好,除非您花大量时间训练自己习惯适得其反的用户界面。

That is why I wrote Dbux - a VSCode extension that provides an omniscient debugger with dynamic execution analysis tools, code annotations and a full-blown dynamic call graph visualization tool, aimed at helping developers with Program Comprehension and Debugging.这就是我编写 Dbux 的原因——一个 VSCode 扩展,它提供了一个全能的调试器,带有动态执行分析工具、代码注释和一个成熟的动态调用图可视化工具,旨在帮助开发人员进行程序理解和调试。

Some examples -一些例子 -

Call Graph of a fibonacci(6) : fibonacci(6)的调用图:

Call Graph with code and other Dbux Tools in one screen:在一个屏幕中使用代码和其他 Dbux 工具调用 Graph:

Links:链接:

You can trace function calls with help of putout code transformer .您可以在 putout代码转换器的帮助下跟踪 function 调用。 Plugin will look this way: 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',
    });
}

Let's suppose that this is the code you want to trace:假设这是您要跟踪的代码:

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

Here is a full picture:这是一张完整的图片:

在此处输入图像描述

If you save processed source as trace.js and run it with node, you will have:如果您将处理后的源代码保存为trace.js并使用 node 运行它,您将拥有:

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

There is putout issue related to tracing functions .存在与跟踪功能相关的问题

I've used @Briguy37's solution with an improvement.我使用了@ Briguy37 的解决方案并进行了改进。 In my case, I did not want to trace functions from some libraries, so I added some code to exclude them.就我而言,我不想跟踪某些库中的函数,所以我添加了一些代码来排除它们。 Here is how it is used:以下是它的使用方法:

  • First, include the definition of the functions you don't want to trace;首先,包括你不想跟踪的函数的定义;
  • excludeLoggingToNamespace to list the functions defined up to now and exclude them; excludeLoggingToNamespace 列出到目前为止定义的函数并排除它们;
  • Include the definition of the functions you want to trace;包括要跟踪的函数的定义;
  • Call addLoggingToNamespace to add the logging capability to the functions defined in the above step.调用 addLoggingToNamespace 为上述步骤中定义的函数添加日志记录功能。

Example:例子:

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

Here is the code I added to @Briguy37's solution:这是我添加到@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;
                }
            }
        }; 

And I had to modify @Briguy37's addLoggingToNamespace method to take into accound the excludedFunctions hash:而且我不得不修改@Briguy37 的 addLoggingToNamespace 方法以考虑到 excludeFunctions hash:

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

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

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