libraries/bugcore/js/src/trace/Tracer.js
/*
* Copyright (c) 2016 airbug Inc. http://airbug.com
*
* bugcore may be freely distributed under the MIT license.
*/
//-------------------------------------------------------------------------------
// Annotations
//-------------------------------------------------------------------------------
//@Export('Tracer')
//@Require('ArgUtil')
//@Require('Class')
//@Require('Obj')
//@Require('Proxy')
//@Require('StackTraceUtil')
//@Require('StringUtil')
//@Require('Trace')
//@Require('TraceTree')
//@Require('TypeUtil')
//-------------------------------------------------------------------------------
// Context
//-------------------------------------------------------------------------------
require('bugpack').context("*", function(bugpack) {
//-------------------------------------------------------------------------------
// BugPack
//-------------------------------------------------------------------------------
var ArgUtil = bugpack.require('ArgUtil');
var Class = bugpack.require('Class');
var Obj = bugpack.require('Obj');
var Proxy = bugpack.require('Proxy');
var StackTraceUtil = bugpack.require('StackTraceUtil');
var StringUtil = bugpack.require('StringUtil');
var Trace = bugpack.require('Trace');
var TraceTree = bugpack.require('TraceTree');
var TypeUtil = bugpack.require('TypeUtil');
//-------------------------------------------------------------------------------
// Declare Class
//-------------------------------------------------------------------------------
/**
* @class
* @extends {Obj}
*/
var Tracer = Class.extend(Obj, {
_name: "Tracer",
//-------------------------------------------------------------------------------
// Constructor
//-------------------------------------------------------------------------------
/**
* @constructs
*/
_constructor: function() {
this._super();
//-------------------------------------------------------------------------------
// Private Properties
//-------------------------------------------------------------------------------
/**
* @private
* @type {boolean}
*/
this.enabled = false;
/**
* @private
* @type {TraceTree}
*/
this.traceTree = new TraceTree();
/**
* @private
* @type {Trace}
*/
this.currentTrace = null;
},
//-------------------------------------------------------------------------------
// Init Methods
//-------------------------------------------------------------------------------
/**
* @return {Tracer}
*/
init: function() {
var _this = this._super();
if (_this) {
_this.currentTrace = new Trace("", "ROOT_TRACE");
_this.traceTree.setRootTrace(_this.currentTrace);
}
return _this;
},
//-------------------------------------------------------------------------------
// Getters and Setters
//-------------------------------------------------------------------------------
/**
* @return {Trace}
*/
getCurrentTrace: function() {
return this.currentTrace;
},
/**
* @param {Trace} currentTrace
*/
setCurrentTrace: function(currentTrace) {
this.currentTrace = currentTrace;
},
//-------------------------------------------------------------------------------
// Public Methods
//-------------------------------------------------------------------------------
/**
* @param {Throwable | Error} error
* @return {Throwable | Error}
*/
$error: function(error) {
if (this.enabled) {
if (TypeUtil.isObject(error) && !error.bugTraced) {
error.bugTraced = true;
if (!error.stack) {
error.stack = StackTraceUtil.generateStackTrace();
}
var traceStack = this.generateTraceStack(this.currentTrace);
error.stack = error.stack + "\n" + traceStack;
}
}
return error;
},
/**
* @param {string} name
*/
$name: function(name) {
if (this.currentTrace) {
this.currentTrace.setName(name);
}
},
/**
* @param {function(...):*} callback
* @return {function}
*/
$trace: function(callback) {
if (this.enabled) {
var _this = this;
var stack = StackTraceUtil.generateStackTrace();
var trace = this.addTraceStack(stack);
if (callback.aCallback) {
throw new Error("This callback has already been wrapped in a trace");
}
var newCallback = function() {
newCallback.aCallback = true;
var args = ArgUtil.toArray(arguments);
_this.currentTrace = trace;
callback.apply(null, args);
//NOTE BRN: If one async thread ends and a new one starts that we have not wrapped in our own trace callback
//we do not want any new traces that the thread creates to attach to the previous current trace (since they
//are unrelated). So, we reset the current trace to the root trace after the completion of every callback.
_this.currentTrace = _this.traceTree.getRootTrace();
_this.checkTraceForRemoval(trace);
};
return newCallback;
} else {
return callback;
}
},
/**
* @param {function(Error, ...)} callback
* @return {function}
*/
$traceWithError: function(callback) {
if (this.enabled) {
var _this = this;
var stack = StackTraceUtil.generateStackTrace();
var trace = this.addTraceStack(stack);
if (callback.aCallback) {
throw new Error("This callback has already been wrapped in a trace");
}
var newCallback = function() {
newCallback.aCallback = true;
var args = ArgUtil.toArray(arguments);
var error = args[0];
if (error) {
args[0] = _this.$error(error);
}
_this.currentTrace = trace;
callback.apply(null, args);
_this.currentTrace = _this.traceTree.getRootTrace();
_this.checkTraceForRemoval(trace);
};
return newCallback;
} else {
return callback;
}
},
/**
*
*/
enable: function() {
this.enabled = true;
},
/**
* @param {string} name
* @return {string}
*/
generateNamedTraceStack: function(name) {
var firstNamedTrace = this.traceTree.findFirstTrace(function(trace) {
return (trace.getName() === name);
});
if (firstNamedTrace) {
var currentTrace = null;
var nextTrace = firstNamedTrace;
while (nextTrace) {
currentTrace = nextTrace;
nextTrace = null;
if (currentTrace.numberChildTraces() > 0) {
var childTraces = currentTrace.getChildTraces();
for (var i = childTraces.length - 1; i >= 0; i--) {
var childTrace = childTraces[i];
if (childTrace.getName() === name) {
nextTrace = childTrace;
break;
}
}
}
}
return this.generateTraceStack(currentTrace);
} else {
return "";
}
},
/**
* @param {Trace} trace
* @return {string}
*/
generateTraceStack: function(trace) {
var traceStack = [];
var currentTrace = trace;
while (!Obj.equals(currentTrace, this.traceTree.getRootTrace())) {
var stack = trace.getStack();
var stackParts = stack.split("\n");
traceStack.push("-------- Async Break ---------");
traceStack = traceStack.concat(stackParts);
currentTrace = currentTrace.getParentTrace();
}
return traceStack.join("\n");
},
//-------------------------------------------------------------------------------
// Private Methods
//-------------------------------------------------------------------------------
/**
* @private
* @param {string} stack
* @return {*}
*/
addTraceStack: function(stack) {
var trace = new Trace(stack);
trace.setName(this.currentTrace.getName());
this.currentTrace.addChildTrace(trace);
return trace;
},
/**
* @private
* @param {Trace} trace
*/
checkTraceForRemoval: function(trace) {
//console.log("check trace - numberChildren:" + node.numberChildTraces() + " Obj.equals(trace, this.traceTree.getRootTrace()):" + Obj.equals(trace, this.traceTree.getRootTrace()) + " trace:" + trace);
if (trace.numberChildTraces() === 0 && !Obj.equals(trace, this.traceTree.getRootTrace())) {
//TEST
//console.log("removing trace - trace:" + trace);
if (trace.removed) {
throw new Error("Trying to remove the same trace TWICE!");
}
var parentTrace = trace.getParentTrace();
parentTrace.removeChildTrace(trace);
trace.removed = true;
this.checkTraceForRemoval(parentTrace);
}
}
});
//-------------------------------------------------------------------------------
// Private Static Properties
//-------------------------------------------------------------------------------
/**
* @static
* @private
* @type {Tracer}
*/
Tracer.instance = null;
//-------------------------------------------------------------------------------
// Public Static Methods
//-------------------------------------------------------------------------------
/**
* @static
* @return {Tracer}
*/
Tracer.getInstance = function() {
if (Tracer.instance === null) {
Tracer.instance = new Tracer();
}
return Tracer.instance;
};
Proxy.proxy(Tracer, Proxy.method(Tracer.getInstance), [
"$error",
"$name",
"$trace",
"$traceWithError",
"enable",
"generateNamedTraceStack"
]);
//-------------------------------------------------------------------------------
// Export
//-------------------------------------------------------------------------------
bugpack.export('Tracer', Tracer);
});