Skip to content

Instantly share code, notes, and snippets.

@guptag
Last active January 23, 2023 17:40
Show Gist options
  • Save guptag/7439788 to your computer and use it in GitHub Desktop.
Save guptag/7439788 to your computer and use it in GitHub Desktop.
Trace the execution path of the client-side code as it executes.
/*
TRACER.js
Trace the execution path of the client-side code as it executes
(super helpful in large client-side codebase esp for new members in the team).
This tool can be helpful :
- to trace the code path for a corresponding UI action in the web app.
- to trace the code path when a global event occured (like resize browser window).
- while debugging tricky UI issues (that may happen in global events like onblur, resize, drag etc).
Sample output in browser console :
---------------------------------------------------------
// function name, flattened argument names and their values, raw arguments array
Object {fn: "showLanding", company: "microsoft", raw_args: Arguments[3]} landing-main.js:730
Object {fn: "renderList", list: Array[20]} index.js:14
Object {fn: "setHeader", title: "Hello, User", raw_args: Arguments[2]} index.js:34
This script modifies target javascript files by adding console statements to all the methods.
It supports flexible configuration options to append the traces to just a single file or to all
files within a directory or to trace the entire code base (which will overwhelm the console but
might be needed to fix some tricky issues). It lists the method names as they get executed
and also displays the name/value pairs or arguments right within the console.
To run:
* create ~/.tracer_config file with the following json (and update as per needed)
{
// path to un-minified post build (or target) javascript files
// WARNING: do not refer to the actual source files - this script modifies the files)
"root": "build/app/scripts",
//whitelist or blacklist
"scanMode": "whitelist",
// trace ONLY the dirs and files listed here
"whitelist": {
"dirs": ["landing/main"], /* all files under the listed directories (recursively) */
"files": ["events.js"] /* all listed files */
},
//trace ALL the files in the root except the ones listed here
//(to exclude libraries like jQuery, Lui.js etc)
"blacklist": {
"dirs": [ "vendor"], /* all files under the listed directories (recursively) */
"files": [ "config/conf.js"] /* all listed files */
},
// to trace anonymouse methods or not
// ('false' might overwhelm the output due to nature of our code pattern)
"skipAnonymousMethods": true
}
* then run - node tracer.js
*/
var fs = require('fs');
// Make sure to update the above readme if there are any changes to this config
var defaultConfig = {
// path to un-minified post build (or target) javascript files
// WARNING: do not refer to the actual source files - this script modifies the files)
"root": "build/app/scripts",
//whitelist or blacklist
"scanMode": "whitelist",
// trace ONLY the dirs and files listed here
"whitelist": {
"dirs": ["landing/main"], /* all files under the listed directories (recursively) */
"files": ["events.js"] /* all listed files */
},
//trace ALL the files in the root except the ones listed here
//(to exclude libraries like jQuery, Lui.js etc)
"blacklist": {
"dirs": [ "vendor"], /* all files under the listed directories (recursively) */
"files": [ "config/conf.js"] /* all listed files */
},
// to trace anonymouse methods or not ('false' might overwhelm the output due to nature of our code pattern)
"skipAnonymousMethods": true
};
var getTracerConfiguration = function (defaultConfig) {
var configPath = process.env.HOME + "/.tracer_config";
var config = defaultConfig;
try {
if (fs.existsSync(configPath)) {
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
console.log("Loaded configuration file @" + configPath);
} else {
console.log("Configuration file not found @" + configPath);
console.log("Switching to default config");
}
} catch (e) {
console.log("Error occured while loading configuation file. Switching to default config");
console.log(e);
}
var toFullPath = function (element, index, array) {
if (element) {
array[index] = config.root + "/" + element;
}
};
// update paths in the configuration file
config.whitelist && config.whitelist.files.forEach(toFullPath);
config.whitelist && config.whitelist.dirs.forEach(toFullPath);
config.blacklist && config.blacklist.files.forEach(toFullPath);
config.blacklist && config.blacklist.dirs.forEach(toFullPath);
return config;
};
// Recursively grab all the files in a folder (inclduing sub-directories)
// Based on http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
var globFiles = function(srcPath, done) {
var results =[];
fs.readdir(srcPath, function(err, list) {
var tobeProcessed = list && list.length || 0;
if(tobeProcessed === 0) return done(null, results);
list.forEach(function(file) {
var fullPath = srcPath + '/' + file
fs.stat(fullPath, function(err, stat){
if (err) { return done(err); }
if (stat && stat.isDirectory()) {
globFiles(fullPath, function(err, fileList) {
if (err) { return done(err);}
results = results.concat(fileList);
if (!--tobeProcessed) done(null, results);
});
} else {
results.push(fullPath);
if (!--tobeProcessed) done(null, results);
}
})
})
})
};
// Add trace statements to a file
var traceFile = function(fileFullName, skipAnonymousMethods) {
var fullPath = fileFullName;
var pattern = /(?:([\w$ƒ\.]*)\s*(?:=|:)\s*)*(?:\s*new\s*)*function\s*(.*?)\s*(\(.*?\))\s*{/g;
var debug = "\n " +
"console && console.log( {" +
"fn: {name}," +
"{paramsStr}" +
"raw_args: arguments" +
"});";
// parse function name (if exists)
var getFuncName = function ($1, $2) {
if ($1) return "'" + $1 + "'";
if ($2 && $2.indexOf("(") === -1) return "'" + $2 + "'";
return "(arguments.callee.name || '<anon>')";
};
// try to map the arguments with the declared variable names
// function test (a, b) -> "a:arguments[0], b:arguments[1],"
var serializeArgumentsWithValues = function($2, $3) {
var args = ($2 && $2.indexOf("(") !== -1) ? $2 : $3;
var serializedParams = "";
if (args != "()" && args.indexOf("(") !== -1) {
args = args.replace("(", "").replace(")", "");
args = args.split(",");
for(var index = 0; index < args.length; ++ index) {
if(index !== 0) serializedParams += ",";
serializedParams += args[index] + ":" + "arguments[" + index + "]";
}
if(serializedParams.length > 0) {
serializedParams += ",";
}
}
return serializedParams;
};
// read file content, parse functions and append trace statements
fs.readFile(fullPath, 'utf8', function (err, data) {
var content = "";
if (data) {
//split file content into line and add connsole statement if the line contains a function defnition
data.toString().split('\n').forEach(function (line) {
if (line.trim().indexOf("//") != 0) {
var replacedLine = line.replace(pattern, function(match, $1, $2, $3) {
return match + debug.replace("{name}", getFuncName($1, $2))
.replace("{paramsStr}", serializeArgumentsWithValues($2, $3));
});
if (skipAnonymousMethods) {
// do not add trace statements to anonymouse methods
// if replacedLine contains 'anon' use original line
content += (replacedLine.indexOf("anon") !== -1 ? line : replacedLine) + "\n";
} else {
content += replacedLine + "\n";
}
}
});
fs.writeFile(fullPath, content, function(err) {
if (err) {
console.log("error writing " + fullPath);
} else {
console.log("tracing: " + fullPath);
}
});
}
});
};
// Start processing the files
(function () {
// load the config
var tracerConfig = getTracerConfiguration(defaultConfig);
// display loaded configuration
console.log(tracerConfig);
// scan all the files and add tracing as per the configuration
globFiles(tracerConfig.root, function(err, fileList) {
//console.log(fileList);
var filteredFiles = fileList.filter(function(fileName) {
if (fileName.indexOf(".js", fileName.length - 3) === -1) return false;
if(tracerConfig.scanMode === "whitelist") {
return (tracerConfig[tracerConfig.scanMode].files.indexOf(fileName) >= 0 ? true :
(tracerConfig[tracerConfig.scanMode].dirs.filter(function(dir) {
return (fileName.indexOf(dir) >= 0);
}).length > 0));
} else {
return (tracerConfig[tracerConfig.scanMode].files.indexOf(fileName) >= 0 ? false :
(tracerConfig[tracerConfig.scanMode].dirs.filter(function(dir) {
return (fileName.indexOf(dir) >= 0);
}).length == 0));
}
});
//console.log(filteredFiles);
// trace all the filtered files
filteredFiles.forEach(function(fileFullName) {
traceFile(fileFullName, tracerConfig.skipAnonymousMethods);
})
}); /* end globFiles */
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment