Skip to content

Commit 019655c

Browse files
committed
Feature parity w/ Python #861
1 parent 0763143 commit 019655c

File tree

7 files changed

+156
-36
lines changed

7 files changed

+156
-36
lines changed

docs/core/tracer.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Tracer is an opinionated thin wrapper for [AWS X-Ray SDK for Node.js](https://gi
99

1010
## Key features
1111

12-
* Auto capture cold start as annotation, and responses or full exceptions as metadata
12+
* Auto capture cold start and service name as annotations, and responses or full exceptions as metadata
1313
* Auto-disable when not running in AWS Lambda environment
1414
* Support tracing functions via decorators, middleware, and manual instrumentation
1515
* Support tracing AWS SDK v2 and v3 via AWS X-Ray SDK for Node.js
@@ -85,7 +85,8 @@ You can quickly start by importing the `Tracer` class, initialize it outside the
8585
const segment = tracer.getSegment(); // This is the facade segment (the one that is created by AWS Lambda)
8686
// Create subsegment for the function
8787
const handlerSegment = segment.addNewSubsegment(`## ${context.functionName}`);
88-
tracer.annotateColdStart()
88+
tracer.annotateColdStart();
89+
tracer.addServiceNameAnnotation();
8990

9091
let res;
9192
try {

packages/tracing/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Tracer is an opinionated thin wrapper for [AWS X-Ray SDK for Node.js](https://gi
55
Tracing data can be visualized through AWS X-Ray Console.
66

77
## Key features
8-
* Auto capture cold start as annotation, and responses or full exceptions as metadata
8+
* Auto capture cold start and service name as annotations, and responses or full exceptions as metadata
99
* Auto-disable when not running in AWS Lambda environment
1010
* Support tracing functions via decorators, middleware, and manual instrumentation
1111
* Support tracing AWS SDK v2 and v3 via AWS X-Ray SDK for Node.js
@@ -18,7 +18,7 @@ For more usage examples, see [our documentation](https://awslabs.github.io/aws-l
1818

1919
If you use function-based Lambda handlers you can use the [captureLambdaHanlder()](./_aws_lambda_powertools_tracer.Tracer.html) middy middleware to automatically:
2020
* handle the subsegment lifecycle
21-
* add the `ColdStart` annotation
21+
* add the `ServiceName` and `ColdStart` annotations
2222
* add the function response as metadata
2323
* add the function error as metadata (if any)
2424

@@ -37,7 +37,7 @@ export const handler = middy(async (_event: any, _context: any) => {
3737

3838
If instead you use TypeScript Classes to wrap your Lambda handler you can use the [@tracer.captureLambdaHanlder()](./_aws_lambda_powertools_tracer.Tracer.html#captureLambdaHanlder) decorator to automatically:
3939
* handle the subsegment lifecycle
40-
* add the `ColdStart` annotation
40+
* add the `ServiceName` and `ColdStart` annotations
4141
* add the function response as metadata
4242
* add the function error as metadata (if any)
4343

@@ -70,7 +70,8 @@ export const handler = async (_event: any, context: any) => {
7070
const segment = tracer.getSegment(); // This is the facade segment (the one that is created by AWS Lambda)
7171
// Create subsegment for the function
7272
const handlerSegment = segment.addNewSubsegment(`## ${context.functionName}`);
73-
tracer.annotateColdStart()
73+
tracer.annotateColdStart();
74+
tracer.addServiceNameAnnotation();
7475

7576
let res;
7677
try {

packages/tracing/src/Tracer.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core';
2525
*
2626
* If you use function-based Lambda handlers you can use the [captureLambdaHanlder()](./_aws_lambda_powertools_tracer.Tracer.html) middy middleware to automatically:
2727
* * handle the subsegment lifecycle
28-
* * add the `ColdStart` annotation
28+
* * add the `ServiceName` and `ColdStart` annotations
2929
* * add the function response as metadata
3030
* * add the function error as metadata (if any)
3131
*
@@ -45,7 +45,7 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core';
4545
*
4646
* If instead you use TypeScript Classes to wrap your Lambda handler you can use the [@tracer.captureLambdaHanlder()](./_aws_lambda_powertools_tracer.Tracer.html#captureLambdaHanlder) decorator to automatically:
4747
* * handle the subsegment lifecycle
48-
* * add the `ColdStart` annotation
48+
* * add the `ServiceName` and `ColdStart` annotations
4949
* * add the function response as metadata
5050
* * add the function error as metadata (if any)
5151
*
@@ -84,6 +84,7 @@ import { Segment, Subsegment } from 'aws-xray-sdk-core';
8484
* // Create subsegment for the function
8585
* const handlerSegment = segment.addNewSubsegment(`## ${context.functionName}`);
8686
* tracer.annotateColdStart()
87+
* tracer.addServiceNameAnnotation();
8788
*
8889
* let res;
8990
* try {
@@ -116,7 +117,7 @@ class Tracer implements TracerInterface {
116117

117118
private envVarsService?: EnvironmentVariablesService;
118119

119-
private serviceName: string = 'serviceUndefined';
120+
private serviceName?: string;
120121

121122
private tracingEnabled: boolean = true;
122123

@@ -148,7 +149,7 @@ class Tracer implements TracerInterface {
148149
}
149150

150151
/**
151-
* Add an data to the current segment or subsegment as metadata.
152+
* Add response data to the current segment or subsegment as metadata.
152153
*
153154
* @see https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-annotations
154155
*
@@ -163,6 +164,17 @@ class Tracer implements TracerInterface {
163164
this.putMetadata(`${methodName} response`, data);
164165
}
165166

167+
/**
168+
* Add service name to the current segment or subsegment as annotation.
169+
*
170+
*/
171+
public addServiceNameAnnotation(): void {
172+
if (this.tracingEnabled === false || this.serviceName === undefined) {
173+
return;
174+
}
175+
this.putAnnotation('Service', this.serviceName);
176+
}
177+
166178
/**
167179
* Add ColdStart annotation to the current segment or subsegment.
168180
*
@@ -311,6 +323,7 @@ class Tracer implements TracerInterface {
311323

312324
return this.provider.captureAsyncFunc(`## ${context.functionName}`, async subsegment => {
313325
this.annotateColdStart();
326+
this.addServiceNameAnnotation();
314327
let result: unknown;
315328
try {
316329
result = await originalMethod?.apply(target, [ event, context, callback ]);

packages/tracing/src/middleware/middy.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const captureLambdaHandler = (target: Tracer): middy.MiddlewareObj => {
3232
const subsegment = new Subsegment(`## ${request.context.functionName}`);
3333
target.setSegment(subsegment);
3434
target.annotateColdStart();
35+
target.addServiceNameAnnotation();
3536
}
3637
};
3738

packages/tracing/tests/unit/Tracer.test.ts

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,54 @@ describe('Class: Tracer', () => {
6565

6666
});
6767

68+
describe('Method: addServiceNameAnnotation', () => {
69+
70+
test('when called while tracing is disabled, it does nothing', () => {
71+
72+
// Prepare
73+
const tracer: Tracer = new Tracer({ enabled: false });
74+
const putAnnotation = jest.spyOn(tracer, 'putAnnotation');
75+
76+
// Act
77+
tracer.addServiceNameAnnotation();
78+
79+
// Assess
80+
expect(putAnnotation).toBeCalledTimes(0);
81+
82+
});
83+
84+
test('when called while a serviceName has been set, it adds it as annotation', () => {
85+
86+
// Prepare
87+
const tracer: Tracer = new Tracer({ serviceName: 'foo' });
88+
const putAnnotation = jest.spyOn(tracer, 'putAnnotation').mockImplementation(() => null);
89+
90+
// Act
91+
tracer.addServiceNameAnnotation();
92+
93+
// Assess
94+
expect(putAnnotation).toBeCalledTimes(1);
95+
expect(putAnnotation).toBeCalledWith('Service', 'foo');
96+
97+
});
98+
99+
test('when called while a serviceName has not been set, it does nothing', () => {
100+
101+
// Prepare
102+
delete process.env.POWERTOOLS_SERVICE_NAME;
103+
const tracer: Tracer = new Tracer();
104+
const putAnnotation = jest.spyOn(tracer, 'putAnnotation').mockImplementation(() => null);
105+
106+
// Act
107+
tracer.addServiceNameAnnotation();
108+
109+
// Assess
110+
expect(putAnnotation).toBeCalledTimes(0);
111+
112+
});
113+
114+
});
115+
68116
describe('Method: addResponseAsMetadata', () => {
69117

70118
test('when called while tracing is disabled, it does nothing', () => {
@@ -692,22 +740,60 @@ describe('Class: Tracer', () => {
692740
// Assess
693741
expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(2);
694742
expect(captureAsyncFuncSpy).toHaveBeenCalledWith('## foo-bar-function', expect.anything());
695-
expect(putAnnotationSpy).toHaveBeenCalledTimes(2);
696-
expect(putAnnotationSpy.mock.calls).toEqual([
743+
expect(putAnnotationSpy.mock.calls.filter(call =>
744+
call[0] === 'ColdStart'
745+
)).toEqual([
697746
[ 'ColdStart', true ],
698747
[ 'ColdStart', false ],
699748
]);
700749
expect(newSubsegmentFirstInvocation).toEqual(expect.objectContaining({
701750
name: '## foo-bar-function',
702-
annotations: {
751+
annotations: expect.objectContaining({
703752
'ColdStart': true,
704-
}
753+
})
705754
}));
706755
expect(newSubsegmentSecondInvocation).toEqual(expect.objectContaining({
707756
name: '## foo-bar-function',
708-
annotations: {
757+
annotations: expect.objectContaining({
709758
'ColdStart': false,
759+
})
760+
}));
761+
762+
});
763+
764+
test('when used as decorator and with standard config, it annotates Service correctly', async () => {
765+
766+
// Prepare
767+
const tracer: Tracer = new Tracer();
768+
const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo-bar-function');
769+
jest.spyOn(tracer.provider, 'getSegment')
770+
.mockImplementation(() => newSubsegment);
771+
setContextMissingStrategy(() => null);
772+
const captureAsyncFuncSpy = jest.spyOn(tracer.provider, 'captureAsyncFunc');
773+
class Lambda implements LambdaInterface {
774+
775+
@tracer.captureLambdaHanlder()
776+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
777+
// @ts-ignore
778+
public handler<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
779+
return new Promise((resolve, _reject) => resolve({
780+
foo: 'bar'
781+
} as unknown as TResult));
710782
}
783+
784+
}
785+
786+
// Act
787+
await new Lambda().handler(event, context, () => console.log('Lambda invoked!'));
788+
789+
// Assess
790+
expect(captureAsyncFuncSpy).toHaveBeenCalledTimes(1);
791+
expect(captureAsyncFuncSpy).toHaveBeenCalledWith('## foo-bar-function', expect.anything());
792+
expect(newSubsegment).toEqual(expect.objectContaining({
793+
name: '## foo-bar-function',
794+
annotations: expect.objectContaining({
795+
'Service': 'hello-world',
796+
})
711797
}));
712798

713799
});

packages/tracing/tests/unit/helpers.test.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { ConfigServiceInterface } from '../../src/config';
22
import { TracerOptions } from '../../src/types';
33
import { createTracer, Tracer } from './../../src';
44

5-
describe('Helper: createLogger function', () => {
5+
describe('Helper: createTracer function', () => {
66
const ENVIRONMENT_VARIABLES = process.env;
77

88
beforeEach(() => {
@@ -212,20 +212,6 @@ describe('Helper: createLogger function', () => {
212212

213213
});
214214

215-
test('when POWERTOOLS_SERVICE_NAME environment variable is set to invalid value, a tracer default serviceName is returned', () => {
216-
// Prepare
217-
process.env.POWERTOOLS_SERVICE_NAME = '';
218-
219-
// Act
220-
const tracer = createTracer();
221-
222-
// Assess
223-
expect(tracer).toEqual(expect.objectContaining({
224-
serviceName: 'serviceUndefined'
225-
}));
226-
227-
});
228-
229215
test('when POWERTOOLS_TRACER_CAPTURE_RESPONSE environment variable is set, a tracer with captureResponse disabled is returned', () => {
230216
// Prepare
231217
process.env.POWERTOOLS_TRACER_CAPTURE_RESPONSE = 'false';

packages/tracing/tests/unit/middy.test.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,22 +232,54 @@ describe('Middy middlewares', () => {
232232
expect(setSegmentSpy).toHaveBeenCalledWith(expect.objectContaining({
233233
name: '## foo-bar-function',
234234
}));
235-
expect(putAnnotationSpy).toHaveBeenCalledTimes(2);
236-
expect(putAnnotationSpy.mock.calls).toEqual([
235+
expect(putAnnotationSpy.mock.calls.filter(call =>
236+
call[0] === 'ColdStart'
237+
)).toEqual([
237238
[ 'ColdStart', true ],
238239
[ 'ColdStart', false ],
239240
]);
240241
expect(newSubsegmentFirstInvocation).toEqual(expect.objectContaining({
241242
name: '## foo-bar-function',
242-
annotations: {
243+
annotations: expect.objectContaining({
243244
'ColdStart': true,
244-
}
245+
})
245246
}));
246247
expect(newSubsegmentSecondInvocation).toEqual(expect.objectContaining({
247248
name: '## foo-bar-function',
248-
annotations: {
249+
annotations: expect.objectContaining({
249250
'ColdStart': false,
250-
}
251+
})
252+
}));
253+
254+
});
255+
256+
test('when used with standard config, it annotates Service correctly', async () => {
257+
258+
// Prepare
259+
const tracer: Tracer = new Tracer();
260+
const newSubsegment: Segment | Subsegment | undefined = new Subsegment('## foo-bar-function');
261+
const setSegmentSpy = jest.spyOn(tracer.provider, 'setSegment').mockImplementation();
262+
jest.spyOn(tracer.provider, 'getSegment').mockImplementation(() => newSubsegment);
263+
setContextMissingStrategy(() => null);
264+
const lambdaHandler: Handler = async (_event: unknown, _context: Context) => ({
265+
foo: 'bar'
266+
});
267+
const handler = middy(lambdaHandler).use(captureLambdaHandler(tracer));
268+
const context = Object.assign({}, mockContext);
269+
270+
// Act
271+
await handler({}, context, () => console.log('Lambda invoked!'));
272+
273+
// Assess
274+
expect(setSegmentSpy).toHaveBeenCalledTimes(1);
275+
expect(setSegmentSpy).toHaveBeenCalledWith(expect.objectContaining({
276+
name: '## foo-bar-function',
277+
}));
278+
expect(newSubsegment).toEqual(expect.objectContaining({
279+
name: '## foo-bar-function',
280+
annotations: expect.objectContaining({
281+
'Service': 'hello-world',
282+
})
251283
}));
252284

253285
});

0 commit comments

Comments
 (0)