Last active
January 23, 2023 17:40
-
-
Save guptag/7439788 to your computer and use it in GitHub Desktop.
Trace the execution path of the client-side code as it executes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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