Skip to content

Commit d944cbd

Browse files
committed
feat(logger): add context decorator functionality
1 parent 3086905 commit d944cbd

File tree

12 files changed

+199
-31
lines changed

12 files changed

+199
-31
lines changed

packages/logger/README.md

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { Logger } from '../src';
1010
// When going public, it will be something like: import { Logger } from '@aws-lambda-powertools/logger';
1111

12-
// Environment variables set for the Lambda
12+
// Environment variables set for the lambda
1313
process.env.LOG_LEVEL = 'WARN';
1414
process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
1515

@@ -50,8 +50,10 @@ logger.error('This is an ERROR log');
5050

5151
### Capturing Lambda context info
5252

53+
Without decorators:
54+
5355
```typescript
54-
// Environment variables set for the Lambda
56+
// Environment variables set for the lambda
5557
process.env.LOG_LEVEL = 'WARN';
5658
process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
5759
process.env.POWERTOOLS_CONTEXT_ENABLED = 'TRUE';
@@ -108,11 +110,56 @@ const lambdaHandler: Handler = async (event, context) => {
108110
</details>
109111

110112

113+
With decorators:
114+
115+
```typescript
116+
// Environment variables set for the lambda
117+
process.env.LOG_LEVEL = 'INFO';
118+
process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
119+
120+
const logger = new Logger();
121+
122+
class Lambda implements LambdaInterface {
123+
124+
@logger.injectLambdaContext()
125+
public handler<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
126+
127+
logger.info('This is an INFO log with some context');
128+
129+
}
130+
131+
}
132+
133+
new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!'));
134+
135+
```
136+
137+
<details>
138+
<summary>Click to expand and see the logs outputs</summary>
139+
140+
```bash
141+
142+
{
143+
aws_request_id: 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef',
144+
lambda_function_arn: 'arn:aws:lambda:eu-central-1:123456789012:function:Example',
145+
lambda_function_memory_size: 128,
146+
lambda_function_name: 'foo-bar-function',
147+
level: 'INFO',
148+
message: 'This is an INFO log with some context',
149+
service: 'hello-world',
150+
timestamp: '2021-03-17T08:25:41.198Z',
151+
xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
152+
}
153+
154+
```
155+
</details>
156+
157+
111158
### Appending additional keys
112159

113160
```typescript
114161

115-
// Environment variables set for the Lambda
162+
// Environment variables set for the lambda
116163
process.env.LOG_LEVEL = 'WARN';
117164
process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
118165

@@ -175,7 +222,7 @@ const lambdaHandler: Handler = async () => {
175222
### Reusing Logger across your code
176223
177224
```typescript
178-
// Environment variables set for the Lambda
225+
// Environment variables set for the lambda
179226
process.env.LOG_LEVEL = 'INFO';
180227

181228
const parentLogger = new Logger();
@@ -235,7 +282,7 @@ const lambdaHandler: Handler = async () => {
235282
236283
```typescript
237284

238-
// Environment variables set for the Lambda
285+
// Environment variables set for the lambda
239286
process.env.LOG_LEVEL = 'WARN';
240287
process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
241288

packages/logger/examples/child-logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,4 @@ const lambdaHandler: Handler = async () => {
3030

3131
};
3232

33-
lambdaHandler(dummyEvent, dummyContext, () => {});
33+
lambdaHandler(dummyEvent, dummyContext, () => console.log('Lambda invoked!'));
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { populateEnvironmentVariables } from '../tests/helpers';
2+
3+
// Populate runtime
4+
populateEnvironmentVariables();
5+
// Additional runtime variables
6+
process.env.LOG_LEVEL = 'INFO';
7+
process.env.POWERTOOLS_SERVICE_NAME = 'hello-world';
8+
9+
import * as dummyEvent from '../../../tests/resources/events/custom/hello-world.json';
10+
import { context as dummyContext } from '../../../tests/resources/contexts/hello-world';
11+
import { LambdaInterface } from '../src/lambda/LambdaInterface';
12+
import { Logger } from '../src';
13+
import { Callback, Context } from 'aws-lambda/handler';
14+
15+
const logger = new Logger();
16+
17+
class Lambda implements LambdaInterface {
18+
19+
@logger.injectLambdaContext()
20+
public handler<TEvent, TResult>(_event: TEvent, _context: Context, _callback: Callback<TResult>): void | Promise<TResult> {
21+
22+
logger.info('This is an INFO log with some context');
23+
24+
}
25+
26+
}
27+
28+
new Lambda().handler(dummyEvent, dummyContext, () => console.log('Lambda invoked!'));

packages/logger/examples/sample-rate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ const lambdaHandler: Handler = async () => {
2727

2828
};
2929

30-
lambdaHandler(dummyEvent, dummyContext, () => console.log('Lambda invoked!'));
30+
lambdaHandler(dummyEvent, dummyContext, () => console.log('lambda invoked!'));

packages/logger/src/Logger.ts

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ import {
1313
LogLevelThresholds,
1414
LambdaFunctionContext,
1515
LoggerInput,
16-
LoggerExtraInput
16+
LoggerExtraInput,
17+
HandlerMethodDecorator
1718
} from '../types';
1819

1920
class Logger implements LoggerInterface {
@@ -26,7 +27,7 @@ class Logger implements LoggerInterface {
2627

2728
private static readonly defaultLogLevelThreshold: LogLevel = 'INFO';
2829

29-
private envVarsService: EnvironmentVariablesService;
30+
private readonly envVarsService: EnvironmentVariablesService;
3031

3132
private static isColdStart: boolean = true;
3233

@@ -106,17 +107,11 @@ class Logger implements LoggerInterface {
106107
}
107108

108109
public debug(input: LoggerInput, ...extraInput: LoggerExtraInput): void {
109-
if (!this.shouldPrint('DEBUG')) {
110-
return;
111-
}
112-
this.printLog(this.createLogItem('DEBUG', input, extraInput).getAttributes());
110+
this.processLogInputData('DEBUG', input, extraInput);
113111
}
114112

115113
public error(input: LoggerInput, ...extraInput: LoggerExtraInput): void {
116-
if (!this.shouldPrint('ERROR')) {
117-
return;
118-
}
119-
this.printLog(this.createLogItem('ERROR', input, extraInput).getAttributes());
114+
this.processLogInputData('ERROR', input, extraInput);
120115
}
121116

122117
public static getIsColdStart(): boolean {
@@ -130,17 +125,25 @@ class Logger implements LoggerInterface {
130125
}
131126

132127
public info(input: LoggerInput, ...extraInput: LoggerExtraInput): void {
133-
if (!this.shouldPrint('INFO')) {
134-
return;
135-
}
136-
this.printLog(this.createLogItem('INFO', input, extraInput).getAttributes());
128+
this.processLogInputData('INFO', input, extraInput);
129+
}
130+
131+
public injectLambdaContext(enableContext: boolean = true): HandlerMethodDecorator {
132+
return (target, propertyKey, descriptor ) => {
133+
const originalMethod = descriptor.value;
134+
135+
descriptor.value = (event, context, callback) => {
136+
this.setIsContextEnabled(enableContext);
137+
this.addContext(context);
138+
const result = originalMethod?.apply(this, [ event, context, callback ]);
139+
140+
return result;
141+
};
142+
};
137143
}
138144

139145
public warn(input: LoggerInput, ...extraInput: LoggerExtraInput): void {
140-
if (!this.shouldPrint('WARN')) {
141-
return;
142-
}
143-
this.printLog(this.createLogItem('WARN', input, extraInput).getAttributes());
146+
this.processLogInputData('WARN', input, extraInput);
144147
}
145148

146149
private addToPowertoolLogAttributes(...attributesArray: Array<Partial<PowertoolLogAttributes>>): void {
@@ -292,6 +295,13 @@ class Logger implements LoggerInterface {
292295
console.log(log);
293296
}
294297

298+
private processLogInputData(logLevel: LogLevel, input: LoggerInput, extraInput: LoggerExtraInput): void {
299+
if (!this.shouldPrint(logLevel)) {
300+
return;
301+
}
302+
this.printLog(this.createLogItem(logLevel, input, extraInput).getAttributes());
303+
}
304+
295305
private setCustomAttributes(attributes: LogAttributes): void {
296306
this.customAttributes = attributes;
297307
}

packages/logger/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './Logger';
2+
export * from './LoggerInterface';
23
export * from './LoggerInterface';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Handler } from 'aws-lambda';
2+
3+
interface LambdaInterface {
4+
handler: Handler
5+
}
6+
7+
export {
8+
LambdaInterface
9+
};

packages/logger/src/lambda/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './LambdaInterface';

packages/logger/tests/unit/Logger.test.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { context as dummyContext } from '../../../../tests/resources/contexts/hello-world';
12
import { Logger } from '../../src';
23
import { populateEnvironmentVariables } from '../helpers';
34

@@ -55,8 +56,13 @@ describe('Logger', () => {
5556

5657
logger.error('foo');
5758
logger.error('foo', { bar: 'baz' });
59+
logger.error({ bar: 'baz', message: 'foo' });
5860

59-
expect(console.log).toBeCalledTimes(2);
61+
const error = new Error('Something happened!');
62+
error.stack = 'A custom stack trace';
63+
logger.error('foo', { bar: 'baz' }, error);
64+
65+
expect(console.log).toBeCalledTimes(4);
6066
expect(console.log).toHaveBeenNthCalledWith(1, {
6167
message: 'foo',
6268
service: 'hello-world',
@@ -72,6 +78,27 @@ describe('Logger', () => {
7278
timestamp: '2016-06-20T12:08:10.000Z',
7379
xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
7480
});
81+
expect(console.log).toHaveBeenNthCalledWith(3, {
82+
bar: 'baz',
83+
message: 'foo',
84+
service: 'hello-world',
85+
level: 'ERROR',
86+
timestamp: '2016-06-20T12:08:10.000Z',
87+
xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
88+
});
89+
expect(console.log).toHaveBeenNthCalledWith(4, {
90+
bar: 'baz',
91+
error: {
92+
message: 'Something happened!',
93+
name: 'Error',
94+
stack: 'A custom stack trace',
95+
},
96+
message: 'foo',
97+
service: 'hello-world',
98+
level: 'ERROR',
99+
timestamp: '2016-06-20T12:08:10.000Z',
100+
xray_trace_id: 'abcdef123456abcdef123456abcdef123456'
101+
});
75102
});
76103

77104
test('should return a valid DEBUG log', () => {
@@ -125,5 +152,44 @@ describe('Logger', () => {
125152

126153
});
127154

155+
test('should return a valid INFO log with context enabled', () => {
156+
157+
const logger = new Logger({
158+
isContextEnabled: true
159+
});
160+
logger.addContext(dummyContext);
161+
162+
logger.info('foo');
163+
logger.info( { message: 'foo', bar: 'baz' });
164+
165+
expect(console.log).toBeCalledTimes(2);
166+
expect(console.log).toHaveBeenNthCalledWith(1, {
167+
'aws_request_id': 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef',
168+
'cold_start': true,
169+
'lambda_function_arn': 'arn:aws:lambda:eu-central-1:123456789012:function:Example',
170+
'lambda_function_memory_size': 128,
171+
'lambda_function_name': 'foo-bar-function',
172+
'level': 'INFO',
173+
'message': 'foo',
174+
'service': 'hello-world',
175+
'timestamp': '2016-06-20T12:08:10.000Z',
176+
'xray_trace_id': 'abcdef123456abcdef123456abcdef123456'
177+
});
178+
expect(console.log).toHaveBeenNthCalledWith(2, {
179+
'aws_request_id': 'c6af9ac6-7b61-11e6-9a41-93e8deadbeef',
180+
'bar': 'baz',
181+
'cold_start': true,
182+
'lambda_function_arn': 'arn:aws:lambda:eu-central-1:123456789012:function:Example',
183+
'lambda_function_memory_size': 128,
184+
'lambda_function_name': 'foo-bar-function',
185+
'level': 'INFO',
186+
'message': 'foo',
187+
'service': 'hello-world',
188+
'timestamp': '2016-06-20T12:08:10.000Z',
189+
'xray_trace_id': 'abcdef123456abcdef123456abcdef123456'
190+
});
191+
192+
});
193+
128194
});
129195

packages/logger/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"compilerOptions": {
3+
"experimentalDecorators": true,
34
"noImplicitAny": true,
45
"target": "ES2020",
56
"module": "commonjs",

packages/logger/types/Logger.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { ConfigServiceInterface } from '../src/config';
2+
import { Handler } from 'aws-lambda';
3+
import { LambdaInterface } from '../src/lambda';
24
import { LogFormatterInterface } from '../src/formatter';
35
import { Environment, LogAttributes, LogAttributesWithMessage, LogLevel } from './Log';
46

@@ -47,7 +49,10 @@ type UnformattedAttributes = PowertoolLogAttributes & {
4749
type LoggerInput = string | LogAttributesWithMessage;
4850
type LoggerExtraInput = Array<Error | LogAttributes>;
4951

52+
type HandlerMethodDecorator = (target: LambdaInterface, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<Handler>) => TypedPropertyDescriptor<Handler> | void;
53+
5054
export {
55+
HandlerMethodDecorator,
5156
LoggerInput,
5257
LoggerExtraInput,
5358
LambdaFunctionContext,

0 commit comments

Comments
 (0)