|
|
@@ -0,0 +1,239 @@ |
|
|
const callerMap = {}; |
|
|
|
|
|
function getCaller(error) { |
|
|
if (error && error.stack) { |
|
|
const lines = error.stack.split('\n'); |
|
|
if (lines.length > 2) { |
|
|
let match = lines[2].match(/at ([a-zA-Z\-_$.]+) (.*)/); |
|
|
if (match) { |
|
|
return { |
|
|
name: match[1].replace(/^Proxy\./, ''), |
|
|
file: match[2], |
|
|
}; |
|
|
} else { |
|
|
match = lines[2].match(/at (.*)/); |
|
|
if (match) { |
|
|
return { |
|
|
name: 'unknown', |
|
|
file: match[1], |
|
|
}; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
return { |
|
|
name: 'unknown', |
|
|
file: '', |
|
|
}; |
|
|
} |
|
|
|
|
|
function getFunctionName(fn, context) { |
|
|
let contextName = ''; |
|
|
if (typeof context === 'function') { |
|
|
contextName = `{context.name}.`; |
|
|
} else if (context && context.constructor && context.constructor.name !== 'Object') { |
|
|
contextName = `${context.constructor.name}.`; |
|
|
} |
|
|
return `${contextName}${fn.name}`; |
|
|
} |
|
|
|
|
|
function trackFunctionCall(options = {}) { |
|
|
return function(target, thisArg, argumentsList) { |
|
|
const { trackTime, trackCaller, trackCount, stdout, filter } = options; |
|
|
const error = trackCaller && new Error(); |
|
|
const caller = getCaller(error); |
|
|
const name = getFunctionName(target, thisArg); |
|
|
if (trackCount) { |
|
|
if (!callerMap[name]) { |
|
|
callerMap[name] = 1; |
|
|
} else { |
|
|
callerMap[name]++; |
|
|
} |
|
|
} |
|
|
let start, end; |
|
|
if (trackTime) { |
|
|
start = Date.now(); |
|
|
} |
|
|
const retVal = target.apply(thisArg, argumentsList); |
|
|
if (trackTime) { |
|
|
end = Date.now(); |
|
|
} |
|
|
let output = `${name} was called`; |
|
|
if (trackCaller) { |
|
|
output += ` by ${caller.name}`; |
|
|
} |
|
|
if (trackCount) { |
|
|
output += ` for the ${callerMap[name]} time`; |
|
|
} |
|
|
if (trackTime) { |
|
|
output += ` and took ${end-start} mils.`; |
|
|
} |
|
|
let canReport = true; |
|
|
if (filter) { |
|
|
canReport = filter({ |
|
|
type: 'function', |
|
|
name, |
|
|
caller, |
|
|
count: callerMap[name], |
|
|
time: end - start, |
|
|
}); |
|
|
} |
|
|
if (canReport) { |
|
|
if (stdout) { |
|
|
stdout(output); |
|
|
} else { |
|
|
console.log(output); |
|
|
} |
|
|
} |
|
|
return retVal; |
|
|
}; |
|
|
} |
|
|
|
|
|
function trackPropertySet(options = {}) { |
|
|
return function set(target, prop, value, receiver) { |
|
|
const { trackCaller, trackCount, stdout, filter } = options; |
|
|
const error = trackCaller && new Error(); |
|
|
const caller = getCaller(error); |
|
|
const contextName = target.constructor.name === 'Object' ? '' : `${target.constructor.name}.`; |
|
|
const name = `${contextName}${prop}`; |
|
|
const hashKey = `set_${name}`; |
|
|
if (trackCount) { |
|
|
if (!callerMap[hashKey]) { |
|
|
callerMap[hashKey] = 1; |
|
|
} else { |
|
|
callerMap[hashKey]++; |
|
|
} |
|
|
} |
|
|
let output = `${name} is being set`; |
|
|
if (trackCaller) { |
|
|
output += ` by ${caller.name}`; |
|
|
} |
|
|
if (trackCount) { |
|
|
output += ` for the ${callerMap[hashKey]} time`; |
|
|
} |
|
|
let canReport = true; |
|
|
if (filter) { |
|
|
canReport = filter({ |
|
|
type: 'get', |
|
|
prop, |
|
|
name, |
|
|
caller, |
|
|
count: callerMap[hashKey], |
|
|
value, |
|
|
}); |
|
|
} |
|
|
if (canReport) { |
|
|
if (stdout) { |
|
|
stdout(output); |
|
|
} else { |
|
|
console.log(output); |
|
|
} |
|
|
} |
|
|
return Reflect.set(target, prop, value, receiver); |
|
|
}; |
|
|
} |
|
|
|
|
|
function trackPropertyGet(options = {}) { |
|
|
return function get(target, prop, receiver) { |
|
|
const { trackCaller, trackCount, stdout, filter } = options; |
|
|
if (typeof target[prop] === 'function' || prop === 'prototype') { |
|
|
return target[prop]; |
|
|
} |
|
|
const error = trackCaller && new Error(); |
|
|
const caller = getCaller(error); |
|
|
const contextName = target.constructor.name === 'Object' ? '' : `${target.constructor.name}.`; |
|
|
const name = `${contextName}${prop}`; |
|
|
const hashKey = `get_${name}`; |
|
|
|
|
|
if (trackCount) { |
|
|
if (!callerMap[hashKey]) { |
|
|
callerMap[hashKey] = 1; |
|
|
} else { |
|
|
callerMap[hashKey]++; |
|
|
} |
|
|
} |
|
|
let output = `${name} is being get`; |
|
|
if (trackCaller) { |
|
|
output += ` by ${caller.name}`; |
|
|
} |
|
|
if (trackCount) { |
|
|
output += ` for the ${callerMap[hashKey]} time`; |
|
|
} |
|
|
let canReport = true; |
|
|
if (filter) { |
|
|
canReport = filter({ |
|
|
type: 'get', |
|
|
prop, |
|
|
name, |
|
|
caller, |
|
|
count: callerMap[hashKey], |
|
|
}); |
|
|
} |
|
|
if (canReport) { |
|
|
if (stdout) { |
|
|
stdout(output); |
|
|
} else { |
|
|
console.log(output); |
|
|
} |
|
|
} |
|
|
return target[prop]; |
|
|
}; |
|
|
} |
|
|
|
|
|
function proxyFunctions(trackedEntity, options) { |
|
|
if (typeof trackedEntity === 'function') return; |
|
|
Object.getOwnPropertyNames(trackedEntity).forEach((name) => { |
|
|
if (typeof trackedEntity[name] === 'function') { |
|
|
trackedEntity[name] = new Proxy(trackedEntity[name], { |
|
|
apply: trackFunctionCall(options), |
|
|
}); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function trackObject(obj, options = {}) { |
|
|
const { trackFunctions, trackProps } = options; |
|
|
|
|
|
let resultObj = obj; |
|
|
if (trackFunctions) { |
|
|
proxyFunctions(resultObj, options); |
|
|
} |
|
|
if (trackProps) { |
|
|
resultObj = new Proxy(resultObj, { |
|
|
get: trackPropertyGet(options), |
|
|
set: trackPropertySet(options), |
|
|
}); |
|
|
} |
|
|
return resultObj; |
|
|
} |
|
|
|
|
|
const defaultOptions = { |
|
|
trackFunctions: true, |
|
|
trackProps: true, |
|
|
trackTime: true, |
|
|
trackCaller: true, |
|
|
trackCount: true, |
|
|
filter: null, |
|
|
}; |
|
|
|
|
|
|
|
|
function trackClass(cls, options = {}) { |
|
|
cls.prototype = trackObject(cls.prototype, options); |
|
|
cls.prototype.constructor = cls; |
|
|
|
|
|
return new Proxy(cls, { |
|
|
construct(target, args) { |
|
|
const obj = new target(...args); |
|
|
return new Proxy(obj, { |
|
|
get: trackPropertyGet(options), |
|
|
set: trackPropertySet(options), |
|
|
}); |
|
|
}, |
|
|
apply: trackFunctionCall(options), |
|
|
}); |
|
|
} |
|
|
|
|
|
export function proxyTrack(entity, options = defaultOptions) { |
|
|
if (typeof entity === 'function') return trackClass(entity, options); |
|
|
return trackObject(entity, options); |
|
|
} |