Skip to content

Commit 5731baa

Browse files
committed
Account for lambda passthrough trace header and add unit tests
1 parent 7beedeb commit 5731baa

File tree

7 files changed

+139
-25
lines changed

7 files changed

+139
-25
lines changed

packages/core/lib/env/aws_lambda.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,14 @@ var noOpSegment = function noOpSegment() {
175175
}
176176
};
177177

178-
// Test for valid trace data during SDK startup. It's likely we're still in the cold-start portion of the
179-
// code at this point and a valid trace header has not been set
180-
if (LambdaUtils.validTraceData(xAmznTraceId)) {
181-
if (LambdaUtils.populateTraceData(segment, xAmznTraceId)) {
182-
xAmznTraceIdPrev = xAmznTraceId;
183-
}
178+
// Since we're in a no-op segment, do not check if the trace data is valid; simply propagate the information
179+
if (LambdaUtils.populateTraceData(segment, xAmznTraceId)) {
180+
xAmznTraceIdPrev = xAmznTraceId;
184181
}
185182

186183
return segment;
187184
};
185+
186+
// For testing
187+
export const exportedFacadeSegment = { facadeSegment };
188+
export const exportedNoOpSegment = { noOpSegment };

packages/core/lib/patchers/aws3_p.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,10 @@ const getXRayMiddleware = (config: RegionResolvedConfig, manualSegment?: Segment
125125
const parent = (segment instanceof Subsegment ? segment.segment : segment);
126126
const data = parent.segment ? parent.segment.additionalTraceData : parent.additionalTraceData;
127127

128-
let traceHeader = stringify(
129-
{
130-
Root: parent.trace_id,
131-
Parent: subsegment.id,
132-
Sampled: subsegment.notTraced ? '0' : '1',
133-
},
134-
';',
135-
);
128+
let traceHeader = 'Root=' + parent.trace_id;
129+
if (!(parent && parent.noOp)) {
130+
traceHeader += ';Parent=' + subsegment.id + ';Sampled=' + (subsegment.notTraced ? '0' : '1');
131+
}
136132

137133
if (data != null) {
138134
for (const [key, value] of Object.entries(data)) {

packages/core/lib/patchers/http_p.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,18 @@ function enableCapture(module, downstreamXRayEnabled, subsegmentCallback) {
123123
subsegment = parent.addNewSubsegment(hostname);
124124
}
125125

126-
const root = parent.segment ? parent.segment : parent;
127126
subsegment.namespace = 'remote';
128127

129128
if (!options.headers) {
130129
options.headers = {};
131130
}
132131

133-
options.headers['X-Amzn-Trace-Id'] = 'Root=' + root.trace_id + ';Parent=' + subsegment.id +
134-
';Sampled=' + (subsegment.notTraced ? '0' : '1');
132+
let traceHeader = 'Root=' + (parent.segment ? parent.segment : parent).trace_id;
133+
if (!(parent && parent.noOp)) {
134+
traceHeader += ';Parent=' + subsegment.id + ';Sampled=' + (subsegment.notTraced ? '0' : '1');
135+
}
136+
137+
options.headers['X-Amzn-Trace-Id'] = traceHeader;
135138

136139
const errorCapturer = function errorCapturer(e) {
137140
if (subsegmentCallback) {

packages/core/test/unit/env/aws_lambda.test.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe('AWSLambda', function() {
8181
assert.equal(facade.trace_id, TraceID.Invalid().toString());
8282
});
8383

84-
describe('the facade segment', function() {
84+
describe('the facade/no-op segment', function() {
8585
afterEach(function() {
8686
populateStub.returns(true);
8787
delete process.env._X_AMZN_TRACE_ID;
@@ -95,17 +95,25 @@ describe('AWSLambda', function() {
9595
validateStub.should.have.been.calledWith(process.env._X_AMZN_TRACE_ID);
9696
});
9797

98-
it('should call populateTraceData if validTraceData returns true', function() {
98+
it('should call populateTraceData on Facade if validTraceData returns true', function() {
9999
Lambda.init();
100100

101+
var segment = setSegmentStub.args[0][0];
102+
assert.equal(segment.name, 'facade');
103+
assert.isTrue(segment.facade);
104+
101105
populateStub.should.have.been.calledOnce;
102106
});
103107

104-
it('should not call populateTraceData if validTraceData returns false', function() {
108+
it('should call populateTraceData on No-Op if validTraceData returns false', function() {
105109
validateStub.returns(false);
106110
Lambda.init();
107111

108-
populateStub.should.have.not.been.called;
112+
var segment = setSegmentStub.args[0][0];
113+
assert.equal(segment.name, 'no-op');
114+
assert.isTrue(segment.noOp);
115+
116+
populateStub.should.have.been.calledOnce;
109117
});
110118
});
111119
});

packages/core/test/unit/patchers/aws_p.test.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ var Utils = require('../../../lib/utils');
1313

1414
var logger = require('../../../lib/logger').getLogger();
1515

16+
import { exportedFacadeSegment, exportedNoOpSegment } from '../../../lib/env/aws_lambda';
17+
const { facadeSegment } = exportedFacadeSegment;
18+
const { noOpSegment } = exportedNoOpSegment;
19+
1620
chai.should();
1721
chai.use(sinonChai);
1822

@@ -348,4 +352,91 @@ describe('AWS patcher', function() {
348352
});
349353

350354
});
355+
356+
357+
describe('#captureAWSRequest-Lambda-PassThrough', function() {
358+
var awsClient, awsRequest, MyEmitter, sandbox, segment, stubResolve, tempHeader;
359+
360+
before(function() {
361+
MyEmitter = function() {
362+
EventEmitter.call(this);
363+
};
364+
365+
awsClient = {
366+
customizeRequests: function customizeRequests(captureAWSRequest) {
367+
this.call = captureAWSRequest;
368+
},
369+
throttledError: function throttledError() {}
370+
};
371+
awsClient = awsPatcher.captureAWSClient(awsClient);
372+
373+
util.inherits(MyEmitter, EventEmitter);
374+
});
375+
376+
beforeEach(function() {
377+
sandbox = sinon.createSandbox();
378+
379+
awsRequest = {
380+
httpRequest: {
381+
method: 'GET',
382+
url: '/',
383+
connection: {
384+
remoteAddress: 'localhost'
385+
},
386+
headers: {}
387+
},
388+
response: {}
389+
};
390+
391+
awsRequest.on = function(event, fcn) {
392+
if (event === 'complete') {
393+
this.emitter.on(event, fcn.bind(this, this.response));
394+
} else {
395+
this.emitter.on(event, fcn.bind(this, this));
396+
}
397+
return this;
398+
};
399+
400+
awsRequest.emitter = new MyEmitter();
401+
402+
tempHeader = process.env._X_AMZN_TRACE_ID;
403+
process.env._X_AMZN_TRACE_ID = 'Root=' + traceId + ';Foo=bar';
404+
405+
segment = noOpSegment();
406+
407+
stubResolve = sandbox.stub(contextUtils, 'resolveSegment').returns(segment);
408+
});
409+
410+
afterEach(function() {
411+
process.env._X_AMZN_TRACE_ID = tempHeader;
412+
sandbox.restore();
413+
});
414+
415+
it('should log an info statement and exit if parent is not found on the context or on the call params', function(done) {
416+
stubResolve.returns();
417+
var logStub = sandbox.stub(logger, 'info');
418+
419+
awsClient.call(awsRequest);
420+
421+
setTimeout(function() {
422+
logStub.should.have.been.calledOnce;
423+
done();
424+
}, 50);
425+
});
426+
427+
it('should inject the tracing headers', function(done) {
428+
sandbox.stub(contextUtils, 'isAutomaticMode').returns(true);
429+
430+
awsClient.call(awsRequest);
431+
432+
awsRequest.emitter.emit('build');
433+
434+
setTimeout(function() {
435+
var expected = new RegExp('^Root=' + traceId + ';Foo=bar$');
436+
assert.match(awsRequest.httpRequest.headers['X-Amzn-Trace-Id'], expected);
437+
done();
438+
}, 50);
439+
});
440+
441+
});
351442
});

sdk_contrib/fetch/lib/fetch_p.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,12 @@ const enableCapture = function enableCapture(baseFetchFunction, requestClass, do
104104

105105
subsegment.namespace = 'remote';
106106

107-
request.headers.set('X-Amzn-Trace-Id',
108-
'Root=' + (parent.segment ? parent.segment : parent).trace_id +
109-
';Parent=' + subsegment.id +
110-
';Sampled=' + (subsegment.notTraced ? '0' : '1'));
107+
let traceHeader = 'Root=' + (parent.segment ? parent.segment : parent).trace_id;
108+
if (!(parent && parent.noOp)) {
109+
traceHeader += ';Parent=' + subsegment.id + ';Sampled=' + (subsegment.notTraced ? '0' : '1');
110+
}
111+
112+
request.headers.set('X-Amzn-Trace-Id', traceHeader);
111113

112114
// Set up fetch call and capture any thrown errors
113115
const capturedFetch = async () => {

sdk_contrib/fetch/test/unit/fetch_p.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,19 @@ describe('Unit tests', function () {
273273
'Root=12345;Parent=999;Sampled=1');
274274
});
275275

276+
it('adds X-Amzn-Trace-Id header with only root if noOp', async function () {
277+
const activeFetch = captureFetch(true);
278+
stubParentSegment.noOp = true;
279+
stubParentSegment.trace_id = '12345';
280+
stubSubsegment.notTraced = false;
281+
stubSubsegment.id = '999';
282+
const request = new FetchRequest('https://www.foo.com/test');
283+
const requestHeadersSet = sandbox.stub(request.headers, 'set');
284+
await activeFetch(request);
285+
requestHeadersSet.should.have.been.calledOnceWith('X-Amzn-Trace-Id',
286+
'Root=12345');
287+
});
288+
276289
it('calls subsegmentCallback on successful response', async function () {
277290
const spyCallback = sandbox.spy();
278291
const activeFetch = captureFetch(true, spyCallback);

0 commit comments

Comments
 (0)