(interactor) find context by finding error callsite with regex

This commit is contained in:
vmarchaud 2017-06-20 21:15:08 +02:00
parent 6d5f5bff1e
commit 840865ec1d
2 changed files with 101 additions and 25 deletions

View File

@ -92,43 +92,90 @@ function StackTraceParser (opts) {
this._context_size = opts.context;
}
StackTraceParser.prototype.attachContext = function (error) {
var self = this;
if (!error) return error;
// if pmx attached callsites we can parse them to retrieve the context
if (typeof (error.stackframes) === 'object') {
var result = self.parse(error.stackframes);
// no need to send it since there is already the stacktrace
delete error.stackframes;
delete error.__error_callsites;
if (result) {
error.callsite = result.callsite;
error.context = result.context;
}
}
// if the stack is here we can parse it directly from the stack string
// only if the context has been retrieved from elsewhere
if (typeof error.stack === 'string' && !error.callsite) {
var siteRegex = /(\/[^\\\n]*)/g;
var tmp;
var stack = [];
// find matching groups
while ((tmp = siteRegex.exec(error.stack))) {
stack.push(tmp[1]);
}
// parse each callsite to match the format used by the stackParser
stack = stack.map(function (callsite) {
// remove the trailing ) if present
if (callsite[callsite.length - 1] === ')') {
callsite = callsite.substr(0, callsite.length - 1);
}
var location = callsite.split(':');
return location.length < 3 ? callsite : {
file_name: location[0],
line_number: parseInt(location[1])
};
});
var finalCallsite = self.parse(stack);
if (finalCallsite) {
error.callsite = finalCallsite.callsite;
error.context = finalCallsite.context;
}
}
return error;
};
/**
* Parse the stacktrace and return callsite + context if available
*/
StackTraceParser.prototype.parse = function (stack) {
var self = this;
if (!stack || stack.length == 0)
return false;
if (!stack || stack.length === 0) return false;
for (var i = 0, len = stack.length; i < len; i++) {
var callsite = stack[i];
// avoid null values
if (!callsite ||
!callsite.file_name ||
!callsite.line_number)
continue;
if (typeof callsite !== 'object') continue;
if (!callsite.file_name || !callsite.line_number) continue;
var type = (!path.isAbsolute(callsite.file_name) && callsite.file_name[0] !== '.') ? 'core' : 'user'
var type = path.isAbsolute(callsite.file_name) || callsite.file_name[0] === '.' ? 'user' : 'core';
// only use the callsite if its inside user space
if (!callsite ||
type === 'core' ||
callsite.file_name.indexOf('node_modules') > -1 ||
callsite.file_name.indexOf('vxx') > -1)
if (!callsite || type === 'core' || callsite.file_name.indexOf('node_modules') > -1 ||
callsite.file_name.indexOf('vxx') > -1) {
continue;
}
// get the whole context (all lines) and cache them if necessary
var context = self._cache.get(callsite.file_name)
var source = []
var context = self._cache.get(callsite.file_name);
var source = [];
if (context && context.length > 0) {
// get line before the call
var preLine = callsite.line_number - self._context_size - 1;
var pre = context.slice(preLine > 0 ? preLine : 0, callsite.line_number - 1);
if (pre && pre.length > 0) {
pre.forEach(function (line) {
source.push(line.replace(/\t/g, ' '))
})
source.push(line.replace(/\t/g, ' '));
});
}
// get the line where the call has been made
if (context[callsite.line_number - 1]) {
@ -136,23 +183,23 @@ StackTraceParser.prototype.parse = function (stack) {
}
// and get the line after the call
var postLine = callsite.line_number + self._context_size;
var post = context.slice(callsite.line_number, postLine)
var post = context.slice(callsite.line_number, postLine);
if (post && post.length > 0) {
post.forEach(function (line) {
source.push(line.replace(/\t/g, ' '))
})
}
return {
context: source.join('\n'),
callsite: [ callsite.file_name, callsite.line_number ].join(':')
source.push(line.replace(/\t/g, ' '));
});
}
}
return {
context: source.length > 0 ? source.join('\n') : 'cannot retrieve source context',
callsite: [ callsite.file_name, callsite.line_number ].join(':')
};
}
return false;
}
};
module.exports = {
EWMA: EWMA,
Cache: Cache,
StackTraceParser: StackTraceParser
}
};

View File

@ -5,6 +5,7 @@ var Utility = require('../../lib/Interactor/Utility.js');
var TraceFactory = require('./misc/trace_factory.js');
var path = require('path');
var fs = require('fs');
var assert = require('assert');
describe('StackTrace Utility', function() {
var aggregator;
@ -29,7 +30,7 @@ describe('StackTrace Utility', function() {
aggregator = new Aggregator({ stackParser: stackParser});
});
describe('.parseStacktrace', function() {
describe('.parse', function() {
it('should parse stacktrace and get context', function(done) {
var obj = [{
labels: {
@ -125,4 +126,32 @@ describe('StackTrace Utility', function() {
});
});
describe('.attachContext', function () {
it('should extract context from stackframes', function () {
var error = stackParser.attachContext({
stackframes: [
{
file_name: '/toto/tmp/lol',
line_number: 10
}
]
});
assert(error !== undefined);
assert(error.stackframes === undefined);
assert(error.callsite !== undefined);
assert(error.callsite.indexOf('/toto/tmp/lol') >= 0);
});
it('should extract context from the stack string', function () {
var error = new Error();
// stack is lazy so we need to load it
error.stack = error.stack;
error = stackParser.attachContext(error);
assert(error !== undefined);
assert(error.stackframes === undefined);
assert(error.callsite.indexOf(__filename) >= 0);
assert(error.context.indexOf('var error = new Error()') >= 0);
});
});
});