Skip to content

Commit a2344c4

Browse files
Move common code into middleware utils to make creation of new middleware simpler (#306)
* Move common code into middleware utils to make creation of new middleware simpler * Add middlewareLog function and add missing typescript types and tests * Reinstate addError() to avoid closing segments multiple times * Use middlewareLog in express middleware
1 parent 1077ea4 commit a2344c4

File tree

7 files changed

+134
-62
lines changed

7 files changed

+134
-62
lines changed

packages/core/lib/middleware/mw_utils.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export function setDefaultName(name: string): void;
2424

2525
export function disableCentralizedSampling(): void;
2626

27+
export function middlewareLog(message: string, url: string, segment: Segment): void;
28+
29+
export function traceRequestResponseCycle(req: http.IncomingMessage, res: http.ServerResponse): Segment;
30+
2731
export interface BaseRuleConfig {
2832
http_method: string;
2933
url_path: string;

packages/core/lib/middleware/mw_utils.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
* @module mw_utils
66
*/
77

8+
var Segment = require('../segments/segment');
9+
var IncomingRequestData = require('./incoming_request_data');
10+
var logger = require('../logger');
11+
var coreUtils = require('../utils');
12+
813
var wildcardMatch = require('../utils').wildcardMatch;
914
var processTraceData = require('../utils').processTraceData;
1015

@@ -134,6 +139,72 @@ var utils = {
134139
throw new Error('Please specify a path to the local sampling rules file, or supply an object containing the rules.');
135140

136141
this.sampler.setLocalRules(source);
142+
},
143+
144+
/**
145+
* Logs a debug message including core request and segment information
146+
* @param {string} message - The message to be logged
147+
* @param {string} url - The request url being traced
148+
* @param {Segment} - The current segment
149+
*/
150+
middlewareLog: function middlewareLog(message, url, segment) {
151+
logger.getLogger().debug(message + ': { url: ' + url + ', name: ' + segment.name + ', trace_id: ' +
152+
segment.trace_id + ', id: ' + segment.id + ', sampled: ' + !segment.notTraced + ' }');
153+
},
154+
155+
/**
156+
* Traces the request/response cycle of an http.IncomingMessage / http.ServerResponse pair.
157+
* Resolves sampling rules, creates a segment, adds the core request / response data adding
158+
* throttling / error / fault flags based on the response status code.
159+
* @param {http.IncomingMessage} req - The incoming request.
160+
* @param {http.ServerResponse} res - The server response.
161+
* @returns {Segment}
162+
* @memberof AWSXRay
163+
*/
164+
traceRequestResponseCycle: function traceRequestResponseCycle(req, res) {
165+
var amznTraceHeader = this.processHeaders(req);
166+
var name = this.resolveName(req.headers.host);
167+
var segment = new Segment(name, amznTraceHeader.root, amznTraceHeader.parent);
168+
169+
var responseWithEmbeddedRequest = Object.assign({}, res, { req: req });
170+
this.resolveSampling(amznTraceHeader, segment, responseWithEmbeddedRequest);
171+
172+
segment.addIncomingRequestData(new IncomingRequestData(req));
173+
174+
this.middlewareLog('Starting middleware segment', req.url, segment);
175+
176+
var middlewareLog = this.middlewareLog;
177+
var didEnd = false;
178+
var endSegment = function () {
179+
// ensure `endSegment` is only called once
180+
// in some versions of node.js 10.x and in all versions of node.js 11.x and higher,
181+
// the 'finish' and 'close' event are BOTH triggered.
182+
// Previously, only one or the other was triggered:
183+
// https://github.com/nodejs/node/pull/20611
184+
if (didEnd) return;
185+
didEnd = true;
186+
187+
if (res.statusCode === 429) {
188+
segment.addThrottleFlag();
189+
}
190+
191+
const cause = coreUtils.getCauseTypeFromHttpStatus(
192+
res.statusCode
193+
);
194+
195+
if (cause) {
196+
segment[cause] = true;
197+
}
198+
199+
segment.http.close(res);
200+
segment.close();
201+
202+
middlewareLog('Closed middleware segment successfully', req.url, segment);
203+
};
204+
205+
res.on('finish', endSegment);
206+
res.on('close', endSegment);
207+
return segment;
137208
}
138209
};
139210

packages/core/test-d/index.test-d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Socket } from 'net';
55
import { expectType, expectError } from 'tsd';
66
import * as url from 'url';
77
import * as AWSXRay from '../lib';
8+
import { Segment } from '../lib';
89

910
expectType<void>(AWSXRay.plugins.EC2Plugin.getData((metadata?: AWSXRay.plugins.EC2Metadata) => { }));
1011
expectType<void>(AWSXRay.plugins.ECSPlugin.getData((metadata?: AWSXRay.plugins.ECSMetadata) => { }));
@@ -135,6 +136,7 @@ const rulesConfig: AWSXRay.middleware.RulesConfig = {
135136
}
136137
};
137138
expectType<void>(AWSXRay.middleware.setSamplingRules(rulesConfig));
139+
expectType<void>(AWSXRay.middleware.middlewareLog('log-message', 'https://example.org', segment));
138140

139141
expectType<Namespace>(AWSXRay.getNamespace());
140142
expectType<AWSXRay.Segment | AWSXRay.Subsegment | undefined>(AWSXRay.resolveSegment(segment));
@@ -163,7 +165,9 @@ expectType<void>(segment.setMatchedSamplingRule('rule'));
163165
expectType<void>(segment.setServiceData({ version: '2.3.0', package: 'sample-app' }));
164166
expectType<void>(segment.addPluginData({ elastic_beanstalk: { environment: 'my_environment_name' } }));
165167
const incomingMessage = new http.IncomingMessage(new Socket());
168+
const serverResponse = new http.ServerResponse(incomingMessage);
166169
expectType<void>(segment.addIncomingRequestData(new AWSXRay.middleware.IncomingRequestData(incomingMessage)));
170+
expectType<Segment>(AWSXRay.middleware.traceRequestResponseCycle(incomingMessage, serverResponse))
167171

168172
function testSegmentLike(segmentLike: AWSXRay.Segment | AWSXRay.Subsegment) {
169173
expectType<void>(segmentLike.addAnnotation('key', true));

packages/core/test/unit/middleware/mw_utils.test.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var sinon = require('sinon');
44
var MWUtils = require('../../../lib/middleware/mw_utils');
55
var localSampler = require('../../../lib/middleware/sampling/local_sampler');
66
var Segment = require('../../../lib/segments/segment');
7+
var logger = require('../../../lib/logger');
78

89
//headers are case-insensitive
910
var XRAY_HEADER = 'x-amzn-trace-id';
@@ -15,7 +16,11 @@ describe('Middleware utils', function() {
1516
var traceId = '1-f9194208-2c7ad569f5d6ff149137be86';
1617

1718
function reloadMWUtils() {
18-
var path = '../../../lib/middleware/mw_utils';
19+
var path = '../../../lib/logger';
20+
delete require.cache[require.resolve(path)];
21+
logger = require(path);
22+
23+
path = '../../../lib/middleware/mw_utils';
1924
delete require.cache[require.resolve(path)];
2025
MWUtils = require(path);
2126
}
@@ -301,4 +306,35 @@ describe('Middleware utils', function() {
301306
assert.throws(function() { MWUtils.setSamplingRules(new String('')); });
302307
});
303308
});
309+
310+
describe('#middlewareLog', function () {
311+
var sandbox, debugLoggerStub;
312+
313+
beforeEach(function() {
314+
reloadMWUtils();
315+
sandbox = sinon.sandbox.create();
316+
debugLoggerStub = sandbox.stub(logger.getLogger(), 'debug');
317+
});
318+
319+
afterEach(function() {
320+
sandbox.restore();
321+
});
322+
323+
it('should log the correct message', function () {
324+
325+
var message = 'Some message to be logged';
326+
var url = 'https://example.org/some-path';
327+
var segment = {
328+
name: 'segment-name',
329+
trace_id: 'some-trace-id',
330+
id: 'segment-id',
331+
notTraced: false
332+
};
333+
MWUtils.middlewareLog(message, url, segment);
334+
335+
var expectedMessage = message + ': { url: ' + url + ', name: ' + segment.name + ', trace_id: ' +
336+
segment.trace_id + ', id: ' + segment.id + ', sampled: ' + !segment.notTraced + ' }';
337+
debugLoggerStub.should.have.been.calledWithExactly(expectedMessage);
338+
});
339+
});
304340
});

packages/express/lib/express_mw.js

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
var AWSXRay = require('aws-xray-sdk-core');
1313

1414
var mwUtils = AWSXRay.middleware;
15-
var IncomingRequestData = mwUtils.IncomingRequestData;
16-
var Segment = AWSXRay.Segment;
1715

1816
var expressMW = {
1917

@@ -32,40 +30,8 @@ var expressMW = {
3230

3331
mwUtils.setDefaultName(defaultName);
3432

35-
return function open(req, res, next) {
36-
var amznTraceHeader = mwUtils.processHeaders(req);
37-
var name = mwUtils.resolveName(req.headers.host);
38-
var segment = new Segment(name, amznTraceHeader.root, amznTraceHeader.parent);
39-
40-
mwUtils.resolveSampling(amznTraceHeader, segment, res);
41-
segment.addIncomingRequestData(new IncomingRequestData(req));
42-
43-
AWSXRay.getLogger().debug('Starting express segment: { url: ' + req.url + ', name: ' + segment.name + ', trace_id: ' +
44-
segment.trace_id + ', id: ' + segment.id + ', sampled: ' + !segment.notTraced + ' }');
45-
46-
var didEnd = false;
47-
var endSegment = function () {
48-
// ensure `endSegment` is only called once
49-
// in some versions of node.js 10.x and in all versions of node.js 11.x and higher,
50-
// the 'finish' and 'close' event are BOTH triggered.
51-
// Previously, only one or the other was triggered:
52-
// https://github.com/nodejs/node/pull/20611
53-
if (didEnd) return;
54-
didEnd = true;
55-
if (this.statusCode === 429)
56-
segment.addThrottleFlag();
57-
if (AWSXRay.utils.getCauseTypeFromHttpStatus(this.statusCode))
58-
segment[AWSXRay.utils.getCauseTypeFromHttpStatus(this.statusCode)] = true;
59-
60-
segment.http.close(this);
61-
segment.close();
62-
63-
AWSXRay.getLogger().debug('Closed express segment successfully: { url: ' + req.url + ', name: ' + segment.name + ', trace_id: ' +
64-
segment.trace_id + ', id: ' + segment.id + ', sampled: ' + !segment.notTraced + ' }');
65-
};
66-
67-
res.on('finish', endSegment);
68-
res.on('close', endSegment);
33+
return function (req, res, next) {
34+
var segment = mwUtils.traceRequestResponseCycle(req, res);
6935

7036
if (AWSXRay.isAutomaticMode()) {
7137
var ns = AWSXRay.getNamespace();
@@ -97,14 +63,12 @@ var expressMW = {
9763
if (segment && err) {
9864
segment.close(err);
9965

100-
AWSXRay.getLogger().debug('Closed express segment with error: { url: ' + req.url + ', name: ' + segment.name + ', trace_id: ' +
101-
segment.trace_id + ', id: ' + segment.id + ', sampled: ' + !segment.notTraced + ' }');
66+
mwUtils.middlewareLog('Closed express segment with error', req.url, segment);
10267

10368
} else if (segment) {
10469
segment.close();
10570

106-
AWSXRay.getLogger().debug('Closed express segment successfully: { url: ' + req.url + ', name: ' + segment.name + ', trace_id: ' +
107-
segment.trace_id + ', id: ' + segment.id + ', sampled: ' + !segment.notTraced + ' }');
71+
mwUtils.middlewareLog('Closed express segment successfully', req.url, segment);
10872
}
10973

11074
if (next)

packages/restify/lib/restify_mw.js

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
var AWSXRay = require('aws-xray-sdk-core');
1313

1414
var mwUtils = AWSXRay.middleware;
15-
var IncomingRequestData = mwUtils.IncomingRequestData;
16-
var Segment = AWSXRay.Segment;
1715

1816
var restifyMW = {
1917

@@ -40,22 +38,7 @@ var restifyMW = {
4038
var segment;
4139

4240
server.use(function open(req, res, next) {
43-
var amznTraceHeader = mwUtils.processHeaders(req);
44-
var name = mwUtils.resolveName(req.headers.host);
45-
segment = new Segment(name, amznTraceHeader.root, amznTraceHeader.parent);
46-
47-
mwUtils.resolveSampling(amznTraceHeader, segment, res);
48-
segment.addIncomingRequestData(new IncomingRequestData(req));
49-
50-
res.on('finish', function() {
51-
if (this.statusCode === 429)
52-
segment.addThrottleFlag();
53-
if (AWSXRay.utils.getCauseTypeFromHttpStatus(this.statusCode))
54-
segment[AWSXRay.utils.getCauseTypeFromHttpStatus(this.statusCode)] = true;
55-
56-
segment.http.close(this);
57-
segment.close();
58-
});
41+
segment = mwUtils.traceRequestResponseCycle(req, res);
5942

6043
if (AWSXRay.isAutomaticMode()) {
6144
var ns = AWSXRay.getNamespace();

packages/restify/test/unit/restify_mw.test.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ chai.use(sinonChai);
1919

2020
var utils = require('../test_utils');
2121

22-
describe('Express middleware', function() {
22+
describe('Restify middleware', function() {
2323
var sandbox, server;
2424
var defaultName = 'defaultName';
2525
var hostName = 'expressMiddlewareTest';
@@ -152,10 +152,15 @@ describe('Express middleware', function() {
152152
it('should add a finish event to the response', function() {
153153
server.open(req, res);
154154

155-
onEventStub.should.have.been.calledOnce;
156155
onEventStub.should.have.been.calledWithExactly('finish', sinon.match.typeOf('function'));
157156
});
158157

158+
it('should add a close event to the response', function() {
159+
server.open(req, res);
160+
161+
onEventStub.should.have.been.calledWithExactly('close', sinon.match.typeOf('function'));
162+
});
163+
159164
describe('in automatic mode', function() {
160165
it('should set the segment on the namespace if in automatic mode', function() {
161166
server.open(req, res);
@@ -185,9 +190,14 @@ describe('Express middleware', function() {
185190
it('should add a finish event to the response', function() {
186191
server.open(req, res);
187192

188-
onEventStub.should.have.been.calledOnce;
189193
onEventStub.should.have.been.calledWithExactly('finish', sinon.match.typeOf('function'));
190194
});
195+
196+
it('should add a close event to the response', function() {
197+
server.open(req, res);
198+
199+
onEventStub.should.have.been.calledWithExactly('close', sinon.match.typeOf('function'));
200+
});
191201
});
192202

193203
describe('when the request completes', function() {

0 commit comments

Comments
 (0)