Skip to content

Commit 6722990

Browse files
authored
feat: support lambda structured logging format (#51)
1 parent 7f32ac6 commit 6722990

File tree

7 files changed

+95
-19
lines changed

7 files changed

+95
-19
lines changed

README.md

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,46 @@ Output
140140
}
141141
```
142142

143-
## Customize output format
143+
### Lambda Structured Log Format
144144

145-
By default, the `pinoLambdaDestination` uses the `CloudwatchLogFormatter`. If you want the request tracing features of `pino-lambda`, but don't need the Cloudwatch format, you can use the `PinoLogFormatter` which matches the default object output format of `pino`.
145+
By default, the `pinoLambdaDestination` uses the `CloudwatchLogFormatter`.
146+
147+
If you would like to use the new [AWS Lambda Advanced Logging Controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) format for your logs, ensure your Lambda function is properly configured and enable `StructuredLogFormatter` in `pino-lambda`.
148+
149+
```ts
150+
import pino from 'pino';
151+
import { lambdaRequestTracker, pinoLambdaDestination, StructuredLogFormatter } from 'pino-lambda';
152+
153+
const destination = pinoLambdaDestination({
154+
formatter: new StructuredLogFormatter()
155+
});
156+
const logger = pino(destination);
157+
const withRequest = lambdaRequestTracker();
158+
159+
async function handler(event, context) {
160+
withRequest(event, context);
161+
logger.info({ data: 'Some data' }, 'A log message');
162+
}
163+
```
164+
165+
Output
166+
167+
```json
168+
{
169+
"timestamp": "2016-12-01T06:00:00.000Z",
170+
"requestId": "6fccb00e-0479-11e9-af91-d7ab5c8fe19e",
171+
"level": "INFO",
172+
"message": {
173+
"msg": "A log message",
174+
"data": "Some data",
175+
"x-correlation-trace-id": "Root=1-5c1bcbd2-9cce3b07143efd5bea1224f2;Parent=07adc05e4e92bf13;Sampled=1"
176+
}
177+
}
178+
```
179+
180+
### Pino Log Format
181+
182+
If you want the request tracing features of `pino-lambda`, but don't need the Cloudwatch format, you can use the `PinoLogFormatter` which matches the default object output format of `pino`.
146183

147184
```ts
148185
import pino from 'pino';
@@ -162,16 +199,19 @@ async function handler(event, context) {
162199

163200
Output
164201

165-
```
202+
```json
166203
{
167-
"awsRequestId": "6fccb00e-0479-11e9-af91-d7ab5c8fe19e",
168-
"x-correlation-trace-id": "Root=1-5c1bcbd2-9cce3b07143efd5bea1224f2;Parent=07adc05e4e92bf13;Sampled=1",
169-
"level": 30,
170-
"message": "A log message",
171-
"data": "Some data"
204+
"awsRequestId": "6fccb00e-0479-11e9-af91-d7ab5c8fe19e",
205+
"x-correlation-trace-id": "Root=1-5c1bcbd2-9cce3b07143efd5bea1224f2;Parent=07adc05e4e92bf13;Sampled=1",
206+
"level": 30,
207+
"time": 1480572000000,
208+
"msg": "A log message",
209+
"data": "Some data"
172210
}
173211
```
174212

213+
### Custom Log Format
214+
175215
The formatter function can also be replaced with any custom implementation you need by using the supplied interface.
176216

177217
```ts
@@ -197,7 +237,7 @@ Output
197237
"awsRequestId": "6fccb00e-0479-11e9-af91-d7ab5c8fe19e",
198238
"x-correlation-trace-id": "Root=1-5c1bcbd2-9cce3b07143efd5bea1224f2;Parent=07adc05e4e92bf13;Sampled=1",
199239
"level": 30,
200-
"message": "A log message",
240+
"msg": "A log message",
201241
"data": "Some data"
202242
}
203243
```

src/formatters/format.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import pino from 'pino';
2+
3+
export const formatLevel = (level: string | number): string => {
4+
if (typeof level === 'string') {
5+
return level.toLocaleUpperCase();
6+
} else if (typeof level === 'number') {
7+
return pino.levels.labels[level]?.toLocaleUpperCase();
8+
}
9+
return level;
10+
};

src/formatters/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './lambda';
22
export * from './pino';
3+
export * from './structured';

src/formatters/lambda.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
1-
import pino from 'pino';
21
import { ILogFormatter, LogData } from '../types';
3-
4-
const formatLevel = (level: string | number): string => {
5-
if (typeof level === 'string') {
6-
return level.toLocaleUpperCase();
7-
} else if (typeof level === 'number') {
8-
return pino.levels.labels[level]?.toLocaleUpperCase();
9-
}
10-
return level;
11-
};
2+
import { formatLevel } from './format';
123

134
/**
145
* Formats the log in native cloudwatch format and

src/formatters/structured.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ILogFormatter, LogData } from '../types';
2+
import { formatLevel } from './format';
3+
4+
/**
5+
* Formats the log in structured JSON format while
6+
* including the Lambda context data automatically
7+
* @see https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions
8+
*/
9+
export class StructuredLogFormatter implements ILogFormatter {
10+
format({ awsRequestId, level, ...data }: LogData): string {
11+
return JSON.stringify({
12+
timestamp: new Date().toISOString(),
13+
level: formatLevel(level),
14+
requestId: awsRequestId,
15+
message: data,
16+
});
17+
}
18+
}

src/tests/index.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
LambdaEvent,
1212
PinoLogFormatter,
1313
LogData,
14+
StructuredLogFormatter,
1415
} from '../';
1516

1617
import { GlobalContextStorageProvider } from '../context';
@@ -190,6 +191,17 @@ tap.test('should allow default pino formatter', (t) => {
190191
t.end();
191192
});
192193

194+
tap.test('should allow structured logging format for cloudwatch', (t) => {
195+
const { log, output, withRequest } = createLogger(undefined, {
196+
formatter: new StructuredLogFormatter(),
197+
});
198+
199+
withRequest({}, { awsRequestId: '431234' });
200+
log.info('Message with pino formatter');
201+
t.matchSnapshot(output.buffer);
202+
t.end();
203+
});
204+
193205
tap.test('should allow custom formatter', (t) => {
194206
const bananaFormatter = {
195207
format(data: LogData) {

tap-snapshots/src/tests/index.spec.ts.test.cjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ exports[`src/tests/index.spec.ts TAP should allow removing default request data
2525
2016-12-01T06:00:00.000Z 431234 INFO Message with trace ID {"awsRequestId":"431234","level":30,"time":1480572000000,"msg":"Message with trace ID"}
2626
`
2727

28+
exports[`src/tests/index.spec.ts TAP should allow structured logging format for cloudwatch > must match snapshot 1`] = `
29+
{"timestamp":"2016-12-01T06:00:00.000Z","level":"INFO","requestId":"431234","message":{"x-correlation-id":"431234","time":1480572000000,"msg":"Message with pino formatter"}}
30+
`
31+
2832
exports[`src/tests/index.spec.ts TAP should capture custom request data > must match snapshot 1`] = `
2933
2016-12-01T06:00:00.000Z 431234 INFO Message with trace ID {"awsRequestId":"431234","x-correlation-id":"431234","host":"www.host.com","level":30,"time":1480572000000,"msg":"Message with trace ID"}
3034
`

0 commit comments

Comments
 (0)