Skip to content

Commit a63f431

Browse files
committed
docs: document feature
1 parent 0407074 commit a63f431

9 files changed

+330
-21
lines changed

docs/core/logger.md

+164-3
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,167 @@ We prioritise log level settings in this order:
547547

548548
In the event you have set a log level in Powertools to a level that is lower than the ACL setting, we will output a warning log message informing you that your messages will be discarded by Lambda.
549549

550+
### Buffering logs
551+
552+
Log buffering enables you to buffer logs for a specific request or invocation. Enable log buffering by passing `logBufferOptions` when initializing a Logger instance. You can buffer logs at the `WARNING`, `INFO`, `DEBUG`, or `TRACE` level, and flush them automatically on error or manually as needed.
553+
554+
!!! tip "This is useful when you want to reduce the number of log messages emitted while still having detailed logs when needed, such as when troubleshooting issues."
555+
556+
=== "logBufferingGettingStarted.ts"
557+
558+
```typescript hl_lines="4-7"
559+
--8<-- "examples/snippets/logger/logBufferingGettingStarted.ts"
560+
```
561+
562+
#### Configuring the buffer
563+
564+
When configuring the buffer, you can set the following options to fine-tune how logs are captured, stored, and emitted. You can configure the following options in the `logBufferOptions` constructor parameter:
565+
566+
| Parameter | Description | Configuration | Default |
567+
|-------------------- |------------------------------------------------- |------------------------------------ | ------- |
568+
| `enabled` | Enable or disable log buffering | `true`, `false` | `false` |
569+
| `maxBytes` | Maximum size of the log buffer in bytes | `number` | 20480 |
570+
| `bufferAtVerbosity` | Minimum log level to buffer | `TRACE`, `DEBUG`, `INFO`, `WARNING` | `DEBUG` |
571+
| `flushOnErrorLog` | Automatically flush buffer when logging an error | `true`, `false` | `true` |
572+
573+
=== "logBufferingBufferAtVerbosity.ts"
574+
575+
```typescript hl_lines="5"
576+
--8<-- "examples/snippets/logger/logBufferingBufferAtVerbosity.ts"
577+
```
578+
579+
1. Setting `bufferAtVerbosity: 'warn'` configures log buffering for `WARNING` and all lower severity levels like `INFO`, `DEBUG`, and `TRACE`.
580+
2. Calling `logger.clearBuffer()` will clear the buffer without emitting the logs.
581+
582+
=== "logBufferingflushOnErrorLog.ts"
583+
584+
```typescript hl_lines="6"
585+
--8<-- "examples/snippets/logger/logBufferingflushOnErrorLog.ts"
586+
```
587+
588+
1. Disabling `flushOnErrorLog` will not flush the buffer when logging an error. This is useful when you want to control when the buffer is flushed by calling the `logger.flushBuffer()` method.
589+
590+
#### Flushing on errors
591+
592+
When using the `logger.injectLambdaContext()` class method decorator or the `injectLambdaContext()` middleware, you can configure the logger to automatically flush the buffer when an error occurs. This is done by setting the `flushBufferOnUncaughtError` option to `true` in the decorator or middleware options.
593+
594+
=== "logBufferingFlushOnErrorDecorator.ts"
595+
596+
```typescript hl_lines="6 10-12"
597+
--8<-- "examples/snippets/logger/logBufferingFlushOnErrorDecorator.ts"
598+
```
599+
600+
=== "logBufferingFlushOnErrorMiddy.ts"
601+
602+
```typescript hl_lines="6 11"
603+
--8<-- "examples/snippets/logger/logBufferingFlushOnErrorMiddy.ts"
604+
```
605+
606+
#### Buffering workflows
607+
608+
##### Manual flush
609+
610+
<center>
611+
```mermaid
612+
sequenceDiagram
613+
participant Client
614+
participant Lambda
615+
participant Logger
616+
participant CloudWatch
617+
Client->>Lambda: Invoke Lambda
618+
Lambda->>Logger: Initialize with DEBUG level buffering
619+
Logger-->>Lambda: Logger buffer ready
620+
Lambda->>Logger: logger.debug("First debug log")
621+
Logger-->>Logger: Buffer first debug log
622+
Lambda->>Logger: logger.info("Info log")
623+
Logger->>CloudWatch: Directly log info message
624+
Lambda->>Logger: logger.debug("Second debug log")
625+
Logger-->>Logger: Buffer second debug log
626+
Lambda->>Logger: logger.flush_buffer()
627+
Logger->>CloudWatch: Emit buffered logs to stdout
628+
Lambda->>Client: Return execution result
629+
```
630+
<i>Flushing buffer manually</i>
631+
</center>
632+
633+
##### Flushing when logging an error
634+
635+
<center>
636+
```mermaid
637+
sequenceDiagram
638+
participant Client
639+
participant Lambda
640+
participant Logger
641+
participant CloudWatch
642+
Client->>Lambda: Invoke Lambda
643+
Lambda->>Logger: Initialize with DEBUG level buffering
644+
Logger-->>Lambda: Logger buffer ready
645+
Lambda->>Logger: logger.debug("First log")
646+
Logger-->>Logger: Buffer first debug log
647+
Lambda->>Logger: logger.debug("Second log")
648+
Logger-->>Logger: Buffer second debug log
649+
Lambda->>Logger: logger.debug("Third log")
650+
Logger-->>Logger: Buffer third debug log
651+
Lambda->>Lambda: Exception occurs
652+
Lambda->>Logger: logger.error("Error details")
653+
Logger->>CloudWatch: Emit buffered debug logs
654+
Logger->>CloudWatch: Emit error log
655+
Lambda->>Client: Raise exception
656+
```
657+
<i>Flushing buffer when an error happens</i>
658+
</center>
659+
660+
##### Flushing on error
661+
662+
This works only when using the `logger.injectLambdaContext()` class method decorator or the `injectLambdaContext()` middleware. You can configure the logger to automatically flush the buffer when an error occurs by setting the `flushBufferOnUncaughtError` option to `true` in the decorator or middleware options.
663+
664+
<center>
665+
```mermaid
666+
sequenceDiagram
667+
participant Client
668+
participant Lambda
669+
participant Logger
670+
participant CloudWatch
671+
Client->>Lambda: Invoke Lambda
672+
Lambda->>Logger: Using decorator
673+
Logger-->>Lambda: Logger context injected
674+
Lambda->>Logger: logger.debug("First log")
675+
Logger-->>Logger: Buffer first debug log
676+
Lambda->>Logger: logger.debug("Second log")
677+
Logger-->>Logger: Buffer second debug log
678+
Lambda->>Lambda: Uncaught Exception
679+
Lambda->>CloudWatch: Automatically emit buffered debug logs
680+
Lambda->>Client: Raise uncaught exception
681+
```
682+
<i>Flushing buffer when an uncaught exception happens</i>
683+
</center>
684+
685+
#### Buffering FAQs
686+
687+
1. **Does the buffer persist across Lambda invocations?**
688+
No, each Lambda invocation has its own buffer. The buffer is initialized when the Lambda function is invoked and is cleared after the function execution completes or when flushed manually.
689+
690+
2. **Are my logs buffered during cold starts?**
691+
No, we never buffer logs during cold starts. This is because we want to ensure that logs emitted during this phase are always available for debugging and monitoring purposes. The buffer is only used during the execution of the Lambda function.
692+
693+
3. **How can I prevent log buffering from consuming excessive memory?**
694+
You can limit the size of the buffer by setting the `maxBytes` option in the `logBufferOptions` constructor parameter. This will ensure that the buffer does not grow indefinitely and consume excessive memory.
695+
696+
4. **What happens if the log buffer reaches its maximum size?**
697+
Older logs are removed from the buffer to make room for new logs. This means that if the buffer is full, you may lose some logs if they are not flushed before the buffer reaches its maximum size. When this happens, we emit a warning when flushing the buffer to indicate that some logs have been dropped.
698+
699+
5. **What timestamp is used when I flush the logs?**
700+
The timestamp preserves the original time when the log record was created. If you create a log record at 11:00:10 and flush it at 11:00:25, the log line will retain its original timestamp of 11:00:10.
701+
702+
6. **What happens if I try to add a log line that is bigger than max buffer size?**
703+
The log will be emitted directly to standard output and not buffered. When this happens, we emit a warning to indicate that the log line was too big to be buffered.
704+
705+
7. **What happens if Lambda times out without flushing the buffer?**
706+
Logs that are still in the buffer will be lost. If you are using the log buffer to log asynchronously, you should ensure that the buffer is flushed before the Lambda function times out. You can do this by calling the `logger.flushBuffer()` method at the end of your Lambda function.
707+
708+
8. **Do child loggers inherit the buffer?**
709+
No, child loggers do not inherit the buffer from their parent logger but only the buffer configuration. This means that if you create a child logger, it will have its own buffer and will not share the buffer with the parent logger.
710+
550711
### Reordering log keys position
551712

552713
You can change the order of [standard Logger keys](#standard-structured-keys) or any keys that will be appended later at runtime via the `logRecordOrder` parameter.
@@ -566,7 +727,7 @@ You can change the order of [standard Logger keys](#standard-structured-keys) or
566727
--8<-- "examples/snippets/logger/reorderLogKeysOutput.json"
567728
```
568729

569-
### Setting timestamp to custom Timezone
730+
### Setting timestamp to custom timezone
570731

571732
By default, Logger emits records with the default Lambda timestamp in **UTC**, i.e. `2016-06-20T12:08:10.000Z`
572733

@@ -586,7 +747,7 @@ If you prefer to log in a specific timezone, you can configure it by setting the
586747
--8<-- "examples/snippets/logger/customTimezoneOutput.json"
587748
```
588749

589-
### Using multiple Logger instances across your code
750+
### Creating child loggers
590751

591752
The `createChild` method allows you to create a child instance of the Logger, which inherits all of the attributes from its parent. You have the option to override any of the settings and attributes from the parent logger, including [its settings](#utility-settings), any [extra keys](#appending-additional-keys), and [the log formatter](#custom-log-formatter).
592753

@@ -803,7 +964,7 @@ When working with custom log formatters, you take full control over the structur
803964

804965
Note that when implementing this method, you should avoid mutating the `attributes` and `additionalLogAttributes` objects directly. Instead, create a new object with the desired structure and return it. If mutation is necessary, you can create a [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/Window/structuredClone) of the object to avoid side effects.
805966

806-
### Bring your own JSON serializer
967+
### Extend JSON replacer function
807968

808969
You can extend the default JSON serializer by passing a custom serializer function to the `Logger` constructor, using the `jsonReplacerFn` option. This is useful when you want to customize the serialization of specific values.
809970

docs/upgrade.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ Logger `sampleRateValue` **continues** to determine the percentage of concurrent
213213

214214
### Custom log formatter
215215

216-
!!! note "Disregard if you are not customizing log output with a [custom log formatter](./core/logger.md#custom-log-formatter-bring-your-own-formatter)."
216+
!!! note "Disregard if you are not customizing log output with a [custom log formatter](./core/logger.md#custom-log-formatter)."
217217

218218
In v1, `Logger` exposed the [standard](./core/logger.md#standard-structured-keys) as a single argument, _e.g., `formatAttributes(attributes: UnformattedAttributes)`_. It expected a plain object with keys and values you wanted in the final log output.
219219

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Logger } from '@aws-lambda-powertools/logger';
2+
3+
const logger = new Logger({
4+
logBufferOptions: {
5+
bufferAtVerbosity: 'warn', // (1)!
6+
},
7+
});
8+
9+
export const handler = async () => {
10+
// All logs below are buffered
11+
logger.debug('This is a debug message');
12+
logger.info('This is an info message');
13+
logger.warn('This is a warn message');
14+
15+
logger.clearBuffer(); // (2)!
16+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Logger } from '@aws-lambda-powertools/logger';
2+
import type { Context } from 'aws-lambda';
3+
4+
const logger = new Logger({
5+
logLevel: 'DEBUG',
6+
logBufferOptions: { enabled: true },
7+
});
8+
9+
class Lambda {
10+
@logger.injectLambdaContext({
11+
flushBufferOnUncaughtError: true,
12+
})
13+
async handler(_event: unknown, _context: Context) {
14+
// Both logs below are buffered
15+
logger.debug('a debug log');
16+
logger.debug('another debug log');
17+
18+
throw new Error('an error log'); // This causes the buffer to flush
19+
}
20+
}
21+
22+
const lambda = new Lambda();
23+
export const handler = lambda.handler.bind(lambda);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Logger } from '@aws-lambda-powertools/logger';
2+
import { injectLambdaContext } from '@aws-lambda-powertools/logger/middleware';
3+
import middy from '@middy/core';
4+
5+
const logger = new Logger({
6+
logLevel: 'DEBUG',
7+
logBufferOptions: { enabled: true },
8+
});
9+
10+
export const handler = middy()
11+
.use(injectLambdaContext(logger, { flushBufferOnUncaughtError: true }))
12+
.handler(async (event: unknown) => {
13+
// Both logs below are buffered
14+
logger.debug('a debug log');
15+
logger.debug('another debug log');
16+
17+
throw new Error('an error log'); // This causes the buffer to flush
18+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Logger } from '@aws-lambda-powertools/logger';
2+
3+
const logger = new Logger({
4+
logBufferOptions: {
5+
maxBytes: 20480,
6+
flushOnErrorLog: true,
7+
},
8+
});
9+
10+
logger.debug('This is a debug message'); // This is NOT buffered
11+
12+
export const handler = async () => {
13+
logger.debug('This is a debug message'); // This is buffered
14+
logger.info('This is an info message');
15+
16+
// your business logic here
17+
18+
logger.error('This is an error message'); // This also flushes the buffer
19+
// or logger.flushBuffer(); // to flush the buffer manually
20+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Logger } from '@aws-lambda-powertools/logger';
2+
3+
const logger = new Logger({
4+
logBufferOptions: {
5+
maxBytes: 20480,
6+
flushOnErrorLog: false, // (1)!
7+
},
8+
});
9+
10+
export const handler = async () => {
11+
logger.debug('This is a debug message'); // This is buffered
12+
13+
try {
14+
throw new Error('a non fatal error');
15+
} catch (error) {
16+
logger.error('A non fatal error occurred', { error }); // This does NOT flush the buffer
17+
}
18+
19+
logger.debug('This is another debug message'); // This is buffered
20+
21+
try {
22+
throw new Error('a fatal error');
23+
} catch (error) {
24+
logger.error('A fatal error occurred', { error }); // This does NOT flush the buffer
25+
logger.flushBuffer();
26+
}
27+
};

packages/logger/src/Logger.ts

+14-14
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ class Logger extends Utility implements LoggerInterface {
178178
/**
179179
* Buffer configuration options.
180180
*/
181-
#bufferOptions: {
181+
#bufferConfig: {
182182
/**
183183
* Whether the buffer should is enabled
184184
*/
@@ -306,13 +306,13 @@ class Logger extends Utility implements LoggerInterface {
306306
environment: this.powertoolsLogData.environment,
307307
persistentLogAttributes: this.persistentLogAttributes,
308308
jsonReplacerFn: this.#jsonReplacerFn,
309-
...(this.#bufferOptions.enabled && {
309+
...(this.#bufferConfig.enabled && {
310310
logBufferOptions: {
311-
maxBytes: this.#bufferOptions.maxBytes,
311+
maxBytes: this.#bufferConfig.maxBytes,
312312
bufferAtVerbosity: this.getLogLevelNameFromNumber(
313-
this.#bufferOptions.bufferAtVerbosity
313+
this.#bufferConfig.bufferAtVerbosity
314314
),
315-
flushOnErrorLog: this.#bufferOptions.flushOnErrorLog,
315+
flushOnErrorLog: this.#bufferConfig.flushOnErrorLog,
316316
},
317317
}),
318318
},
@@ -360,7 +360,7 @@ class Logger extends Utility implements LoggerInterface {
360360
* @param extraInput - The extra input to log.
361361
*/
362362
public error(input: LogItemMessage, ...extraInput: LogItemExtraInput): void {
363-
if (this.#bufferOptions.enabled && this.#bufferOptions.flushOnErrorLog) {
363+
if (this.#bufferConfig.enabled && this.#bufferConfig.flushOnErrorLog) {
364364
this.flushBuffer();
365365
}
366366
this.processLogItem(LogLevelThreshold.ERROR, input, extraInput);
@@ -1294,24 +1294,24 @@ class Logger extends Utility implements LoggerInterface {
12941294
return;
12951295
}
12961296
// `enabled` is a boolean, so we set it to true if it's not explicitly set to false
1297-
this.#bufferOptions.enabled = options?.enabled !== false;
1297+
this.#bufferConfig.enabled = options?.enabled !== false;
12981298
// if `enabled` is false, we don't need to set any other options
1299-
if (this.#bufferOptions.enabled === false) return;
1299+
if (this.#bufferConfig.enabled === false) return;
13001300

13011301
if (options?.maxBytes !== undefined) {
1302-
this.#bufferOptions.maxBytes = options.maxBytes;
1302+
this.#bufferConfig.maxBytes = options.maxBytes;
13031303
}
13041304
this.#buffer = new CircularMap({
1305-
maxBytesSize: this.#bufferOptions.maxBytes,
1305+
maxBytesSize: this.#bufferConfig.maxBytes,
13061306
});
13071307

13081308
if (options?.flushOnErrorLog === false) {
1309-
this.#bufferOptions.flushOnErrorLog = false;
1309+
this.#bufferConfig.flushOnErrorLog = false;
13101310
}
13111311

13121312
const bufferAtLogLevel = options?.bufferAtVerbosity?.toUpperCase();
13131313
if (this.isValidLogLevel(bufferAtLogLevel)) {
1314-
this.#bufferOptions.bufferAtVerbosity =
1314+
this.#bufferConfig.bufferAtVerbosity =
13151315
LogLevelThreshold[bufferAtLogLevel];
13161316
}
13171317
}
@@ -1400,9 +1400,9 @@ class Logger extends Utility implements LoggerInterface {
14001400
logLevel: number
14011401
): boolean {
14021402
return (
1403-
this.#bufferOptions.enabled &&
1403+
this.#bufferConfig.enabled &&
14041404
traceId !== undefined &&
1405-
logLevel <= this.#bufferOptions.bufferAtVerbosity
1405+
logLevel <= this.#bufferConfig.bufferAtVerbosity
14061406
);
14071407
}
14081408
}

0 commit comments

Comments
 (0)