Skip to content

Commit aef6203

Browse files
committed
Merge pull request #27 from RisingStack/feature/stacktrace
feat(stacktrace): collect data from errors
2 parents 81fa6f8 + 5708244 commit aef6203

10 files changed

Lines changed: 158 additions & 26 deletions

lib/collector.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
var events = require('events');
22
var util = require('util');
33
var fs = require('fs');
4-
var path = require('path');
54

65
var debug = require('debug')('trace-collector');
7-
86
var microtime = require('microtime');
97

8+
var config = require('./config');
109
var getNamespace = require('continuation-local-storage').getNamespace;
1110

1211
/*
@@ -30,6 +29,8 @@ function Collector(options) {
3029
this.on(Collector.SERVER_SEND, this.onServerSend);
3130
this.on(Collector.SERVER_RECV, this.onServerReceive);
3231

32+
this.formattableLogFile = config.logFilePath + config.logFilePrefix + '%s.log';
33+
3334
this._updateLogFile();
3435
}
3536

@@ -199,7 +200,7 @@ Collector.prototype.onServerReceive = function (data) {
199200
};
200201

201202
Collector.prototype._updateLogFile = function () {
202-
this.logFile = path.join(__dirname, '../../../', util.format('../trace_%s.log', Date.now()));
203+
this.logFile = util.format(this.formattableLogFile, Date.now());
203204
};
204205

205206
Collector.prototype.report = function (data) {

lib/config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
var path = require('path');
2+
13
var config = {};
24

35
config.collectInterval = 10 * 1000;
@@ -7,6 +9,7 @@ config.collectorApi = 'http://comingsoon.trace.risingstack.com';
79
config.collectorApiServiceEndpoint = '/service';
810
config.collectorApiSampleEndpoint = '/service/sample';
911

12+
config.logFilePath = path.join(__dirname, '../../../../');
1013
config.logFilePrefix = 'trace_';
1114

1215
config.configPath = process.env.TRACE_CONFIG_PATH;

lib/index.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,20 @@ var session = require('continuation-local-storage').createNamespace('trace');
66
var collectorConfig = require('./config');
77
var Collector = require('./collector');
88
var getConfig = require('./utils/configReader').getConfig;
9+
var wraps = require('./wraps');
910

1011
var traceAgent;
1112

12-
var wraps = require('./wraps');
13-
1413
function getOrphanTraces(config, callback) {
15-
var files = fs.readdirSync(path.join(__dirname, '../../../../'));
14+
var files = fs.readdirSync(collectorConfig.logFilePath);
1615
var logFileStartsWith = collectorConfig.logFilePrefix;
1716

1817
var logFiles = files.filter(function (file) {
1918
return file.indexOf(logFileStartsWith) > -1;
2019
});
2120

2221
logFiles = logFiles.map(function (logFile) {
23-
return path.join(__dirname, '../../../../', logFile);
22+
return path.join(collectorConfig.logFilePath, logFile);
2423
});
2524

2625
return callback(null, logFiles);

lib/index.spec.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var sinon = require('sinon');
44

55
var collectorConfig = require('./config');
66
var Collector = require('./collector');
7+
var wraps = require('./wraps');
78

89
describe('The Trace module', function () {
910

@@ -30,6 +31,10 @@ describe('The Trace module', function () {
3031
});
3132
});
3233

34+
after(function () {
35+
wraps.uninstrumentNatives();
36+
});
37+
3338
it('exposes methods', function () {
3439
expect(trace.report).to.be.ok;
3540
expect(trace.getTransactionId).to.be.ok;

lib/wraps/http.request.spec.js

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
11
var http = require('http');
22
var url = require('url');
33

4-
var chai = require('chai');
5-
var chaiSubset = require('chai-subset');
4+
var expect = require('chai').expect;
65
var nock = require('nock');
7-
var sinon = require('sinon');
8-
var sinonChai = require('sinon-chai');
96

107
var Shimmer = require('./shimmer');
118
var wrapper = require('./http.request');
129

13-
chai.use(chaiSubset);
14-
chai.use(sinonChai);
15-
16-
var expect = chai.expect;
1710
var dummyCollector = {
1811
emit: function () {},
1912
getService: function () {
2013
return 1;
2114
}
2215
};
23-
var sandbox;
2416

2517
describe('The wrapper module', function () {
2618
before(function () {
@@ -37,14 +29,6 @@ describe('The wrapper module', function () {
3729
});
3830
});
3931

40-
beforeEach(function () {
41-
sandbox = sinon.sandbox.create();
42-
});
43-
44-
afterEach(function () {
45-
sandbox.restore();
46-
});
47-
4832
describe('skips every whitelisted hosts', function () {
4933
it('wraps the HTTP.request(options) method', function () {
5034
nock('http://localhost:8000')
@@ -64,7 +48,7 @@ describe('The wrapper module', function () {
6448
});
6549

6650
it('wraps the HTTP.get(urlString) method', function () {
67-
var urlParseSpy = sandbox.spy(url, 'parse');
51+
var urlParseSpy = this.sandbox.spy(url, 'parse');
6852

6953
nock('http://localhost:8000')
7054
.get('/')
@@ -99,7 +83,7 @@ describe('The wrapper module', function () {
9983
});
10084

10185
it('wraps the HTTP.get(urlString) method', function () {
102-
var urlParseSpy = sandbox.spy(url, 'parse');
86+
var urlParseSpy = this.sandbox.spy(url, 'parse');
10387

10488
nock('http://non-whitelisted:8000')
10589
.get('/')

lib/wraps/index.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,26 @@ function instrument (collector) {
3737
return require('./http.request')(original, collector);
3838
});
3939

40+
Shimmer.wrap(process, 'process', '_fatalException', function (original) {
41+
return require('./process._fatalException')(original, collector);
42+
});
43+
4044
thirdParty.instrument();
4145
}
4246

47+
function uninstrumentNatives () {
48+
Shimmer.unwrap(https.Server.prototype, 'https.Server.prototype', 'on');
49+
Shimmer.unwrap(https.Server.prototype, 'https.Server.prototype', 'addListener');
50+
51+
Shimmer.unwrap(http.Server.prototype, 'https.Server.prototype', 'addListener');
52+
Shimmer.unwrap(http.Server.prototype, 'https.Server.prototype', 'addListener');
53+
54+
Shimmer.unwrap(http, 'http', 'request');
55+
56+
Shimmer.unwrap(https, 'https', 'request');
57+
58+
Shimmer.unwrap(process, 'process', '_fatalException');
59+
}
60+
4361
module.exports.instrument = instrument;
62+
module.exports.uninstrumentNatives = uninstrumentNatives;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var getNamespace = require('continuation-local-storage').getNamespace;
2+
var microtime = require('microtime');
3+
4+
function wrapRequest (original, collector) {
5+
var session = getNamespace('trace');
6+
7+
return session.bind(function (stackTrace) {
8+
collector.onCrash({
9+
id: session.get('request-id'),
10+
time: microtime.now(),
11+
stackTrace: stackTrace.stack
12+
});
13+
return original.apply(this, arguments);
14+
});
15+
}
16+
17+
module.exports = wrapRequest;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
var fs = require('fs');
2+
var http = require('http');
3+
var spawn = require('child_process').spawn;
4+
var path = require('path');
5+
6+
var extend = require('lodash/object/extend');
7+
var expect = require('chai').expect;
8+
9+
function startFailingServer () {
10+
var env = extend({}, process.env);
11+
env.TRACE_CONFIG_PATH = path.join(__dirname, '../../test/mocks', 'failingServer.config.js');
12+
13+
var child = spawn('node', ['--harmony', 'test/mocks/failingServer.js'], {
14+
env: env
15+
});
16+
17+
return child;
18+
}
19+
20+
describe('The stacktrace wrapper module', function () {
21+
22+
var spawned;
23+
24+
after(function () {
25+
spawned.kill();
26+
});
27+
28+
it('writes stacktrace into log file', function (done) {
29+
spawned = startFailingServer();
30+
31+
spawned.stdout.on('data', function () {
32+
http
33+
.get('http://localhost:15124')
34+
.on('error', function (err) {
35+
36+
expect(err.code).to.be.eq('ECONNRESET');
37+
38+
var files = fs.readdirSync(path.join(__dirname, '../../', 'test/mocks'));
39+
40+
var logFiles = files.filter(function (file) {
41+
return file.indexOf('trace_') > -1;
42+
});
43+
44+
var LOGFILE_PATH = path.join(__dirname, '../../', 'test/mocks', logFiles[0]);
45+
46+
fs.readFile(LOGFILE_PATH, 'utf-8', function (err, raw) {
47+
expect(err).to.be.not.ok;
48+
expect(raw).to.be.ok;
49+
50+
var stacktrace = JSON.parse(raw.slice(0, -2)).events[0];
51+
52+
expect(stacktrace.type).to.be.eq('st');
53+
54+
var errMsg = 'Error: Very error';
55+
expect(stacktrace.data.trace.slice(0, errMsg.length)).to.be.eq(errMsg);
56+
done();
57+
});
58+
});
59+
});
60+
});
61+
62+
});

test/mocks/failingServer.config.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var config = {};
2+
3+
config.appName = '#fail';
4+
5+
function FailingServerTestReporter () {
6+
this.send = function (data, cb) {
7+
cb(null);
8+
};
9+
10+
this.getService = function () {
11+
return config.appName;
12+
};
13+
}
14+
15+
config.reporter = new FailingServerTestReporter();
16+
17+
module.exports = config;

test/mocks/failingServer.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
var path = require('path');
2+
3+
var express = require('express');
4+
var collectorConfig = require('../../lib/config');
5+
6+
// override log file path
7+
collectorConfig.logFilePath = path.join(__dirname, '/');
8+
9+
// use trace collector
10+
require('../../lib');
11+
12+
var failingServer = express();
13+
14+
failingServer.get('/', function () {
15+
setTimeout(function () {
16+
throw new Error('Very error');
17+
}, 0);
18+
});
19+
20+
failingServer.listen(15124, function (err) {
21+
if (err) {
22+
throw err;
23+
}
24+
console.log('listening');
25+
});

0 commit comments

Comments
 (0)