Logging V2
The logging utility provides a Lambda optimized logger with output structured as JSON.
Key features¶
- Capture key fields from Lambda context, cold start and structures logging output as JSON
- Log Lambda event when instructed (disabled by default)
- Log sampling enables DEBUG log level for a percentage of requests (disabled by default)
- Append additional keys to structured log at any point in time
- Ahead-of-Time compilation to native code support AOT
- Custom log formatter to override default log structure
- Support for AWS Lambda Advanced Logging Controls (ALC)
- Support for Microsoft.Extensions.Logging and ILogger interface
- Support for ILoggerFactory interface
- Support for message templates
{}
and{@}
for structured logging
Installation¶
Powertools for AWS Lambda (.NET) are available as NuGet packages. You can install the packages
from NuGet Gallery or from Visual Studio
editor by searching AWS.Lambda.Powertools*
to see various utilities available.
dotnet add package AWS.Lambda.Powertools.Logging
Getting started¶
Info
AOT Support If loooking for AOT specific configurations navigate to the AOT section
Logging requires two settings:
Setting | Description | Environment variable | Attribute parameter |
---|---|---|---|
Service | Sets Service key that will be present across all log statements | POWERTOOLS_SERVICE_NAME |
Service |
Logging level | Sets how verbose Logger should be (Information, by default) | POWERTOOLS_LOG_LEVEL |
LogLevel |
Full list of environment variables¶
Environment variable | Description | Default |
---|---|---|
POWERTOOLS_SERVICE_NAME | Sets service name used for tracing namespace, metrics dimension and structured logging | "service_undefined" |
POWERTOOLS_LOG_LEVEL | Sets logging level | Information |
POWERTOOLS_LOGGER_CASE | Override the default casing for log keys | SnakeCase |
POWERTOOLS_LOGGER_LOG_EVENT | Logs incoming event | false |
POWERTOOLS_LOGGER_SAMPLE_RATE | Debug log sampling | 0 |
Setting up the logger¶
You can set up the logger in different ways. The most common way is to use the Logging
attribute on your Lambda.
You can also use the ILogger
interface to log messages. This interface is part of the Microsoft.Extensions.Logging.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Customizing the logger¶
You can customize the logger by setting the following properties in the Logger.Configure
method:
Property | Description |
---|---|
Service |
The name of the service. This is used to identify the service in the logs. |
MinimumLogLevel |
The minimum log level to log. This is used to filter out logs below the specified level. |
LogFormatter |
The log formatter to use. This is used to customize the structure of the log entries. |
JsonOptions |
The JSON options to use. This is used to customize the serialization of logs. |
LogBuffering |
The log buffering options. This is used to configure log buffering. |
TimestampFormat |
The format of the timestamp. This is used to customize the format of the timestamp in the logs. |
SamplingRate |
Sets a percentage (0.0 to 1.0) of logs that will be dynamically elevated to DEBUG level |
LoggerOutputCase |
The output casing of the logger. This is used to customize the casing of the log entries. |
LogOutput |
Specifies the console output wrapper used for writing logs. This property allows redirecting log output for testing or specialized handling scenarios. |
Configuration¶
You can configure Powertools Logger using the static Logger
class. This class is a singleton and is created when the
Lambda function is initialized. You can configure the logger using the Logger.Configure
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
ILogger¶
You can also use the ILogger
interface to log messages. This interface is part of the Microsoft.Extensions.Logging.
With this approach you get more flexibility and testability using dependency injection (DI).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Standard structured keys¶
Your logs will always include the following keys to your structured logging:
Key | Type | Example | Description |
---|---|---|---|
Level | string | "Information" | Logging level |
Message | string | "Collecting payment" | Log statement value. Unserializable JSON values will be cast to string |
Timestamp | string | "2020-05-24 18:17:33,774" | Timestamp of actual log statement |
Service | string | "payment" | Service name defined. "service_undefined" will be used if unknown |
ColdStart | bool | true | ColdStart value. |
FunctionName | string | "example-powertools-HelloWorldFunction-1P1Z6B39FLU73" | |
FunctionMemorySize | string | "128" | |
FunctionArn | string | "arn:aws:lambda:eu-west-1:012345678910:function:example-powertools-HelloWorldFunction-1P1Z6B39FLU73" | |
FunctionRequestId | string | "899856cb-83d1-40d7-8611-9e78f15f32f4" | AWS Request ID from lambda context |
FunctionVersion | string | "12" | |
XRayTraceId | string | "1-5759e988-bd862e3fe1be46a994272793" | X-Ray Trace ID when Lambda function has enabled Tracing |
Name | string | "Powertools for AWS Lambda (.NET) Logger" | Logger name |
SamplingRate | int | 0.1 | Debug logging sampling rate in percentage e.g. 10% in this case |
Customer Keys |
Message templates¶
You can use message templates to extract properties from your objects and log them as structured data.
Info
Override the ToString()
method of your object to return a meaningful string representation of the object.
This is especially important when using {}
to log the object as a string.
1 2 3 4 5 6 7 8 9 10 11 |
|
If you want to log the object as a JSON object, use {@}
. This will serialize the object and log it as a JSON object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
If you want to log the object as a string, use {}
. This will call the ToString()
method of the object and log it as
a string.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Logging incoming event¶
When debugging in non-production environments, you can instruct Logger to log the incoming event with LogEvent
parameter or via POWERTOOLS_LOGGER_LOG_EVENT
environment variable.
Warning
Log event is disabled by default to prevent sensitive info being logged.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Setting a Correlation ID¶
You can set a Correlation ID using CorrelationIdPath
parameter by passing
a JSON Pointer expression.
Attention
The JSON Pointer expression is case sensitive
. In the bellow example /headers/my_request_id_header
would work but
/Headers/my_request_id_header
would not find the element.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
We provide built-in JSON Pointer expression {target="_blank"} for known event sources, where either a request ID or X-Ray Trace ID are present.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Appending additional keys¶
Custom keys are persisted across warm invocations
Always set additional keys as part of your handler to ensure they have the latest value, or explicitly clear them with
ClearState=true
.
You can append your own keys to your existing logs via AppendKey
. Typically this value would be passed into the
function via the event. Appended keys are added to all subsequent log entries in the current execution from the point
the logger method is called. To ensure the key is added to all log entries, call this method as early as possible in the
Lambda handler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Removing additional keys¶
You can remove any additional key from entry using Logger.RemoveKeys()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Extra Keys¶
Extra keys allow you to append additional keys to a log entry. Unlike AppendKey
, extra keys will only apply to the
current log entry.
Extra keys argument is available for all log levels' methods, as implemented in the standard logging library - e.g. Logger.Information, Logger.Warning.
It accepts any dictionary, and all keyword arguments will be added as part of the root structure of the logs for that log statement.
Info
Any keyword argument added using extra keys will not be persisted for subsequent messages.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Clearing all state¶
Logger is commonly initialized in the global scope. Due
to Lambda Execution Context reuse, this means that
custom keys can be persisted across invocations. If you want all custom keys to be deleted, you can use
ClearState=true
attribute on [Logging]
attribute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
Sampling debug logs¶
You can dynamically set a percentage of your logs to DEBUG level via env var POWERTOOLS_LOGGER_SAMPLE_RATE
or
via SamplingRate
parameter on attribute.
Info
Configuration on environment variable is given precedence over sampling rate configuration on attribute, provided it's in valid value range.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 6 7 8 |
|
Configure Log Output Casing¶
By definition Powertools for AWS Lambda (.NET) outputs logging keys using snake case (e.g. "function_memory_size": 128). This allows developers using different Powertools for AWS Lambda (.NET) runtimes, to search logs across services written in languages such as Python or TypeScript.
If you want to override the default behavior you can either set the desired casing through attributes, as described in
the example below, or by setting the POWERTOOLS_LOGGER_CASE
environment variable on your AWS Lambda function. Allowed
values are: CamelCase
, PascalCase
and SnakeCase
.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Below are some output examples for different casing.
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
Advanced¶
Log Levels¶
The default log level is Information
and can be set using the MinimumLogLevel
property option or by using the POWERTOOLS_LOG_LEVEL
environment variable.
We support the following log levels:
Level | Numeric value | Lambda Level |
---|---|---|
Trace |
0 | trace |
Debug |
1 | debug |
Information |
2 | info |
Warning |
3 | warn |
Error |
4 | error |
Critical |
5 | fatal |
None |
6 |
Using AWS Lambda Advanced Logging Controls (ALC)¶
When is it useful?
When you want to set a logging policy to drop informational or verbose logs for one or all AWS Lambda functions, regardless of runtime and logger used.
With AWS Lambda Advanced Logging Controls (ALC) {target="_blank"}, you can enforce a minimum log level that Lambda will accept from your application code.
When enabled, you should keep Logger
and ALC log level in sync to avoid data loss.
When using AWS Lambda Advanced Logging Controls (ALC)
- When Powertools Logger output is set to
PascalCase
Level
property name will be replaced byLogLevel
as a property name. - ALC takes precedence over
POWERTOOLS_LOG_LEVEL
and when setting it in code using[Logging(LogLevel = )]
Here's a sequence diagram to demonstrate how ALC will drop both Information
and Debug
logs emitted from Logger
,
when ALC log level is stricter than Logger
.
sequenceDiagram
title Lambda ALC allows WARN logs only
participant Lambda service
participant Lambda function
participant Application Logger
Note over Lambda service: AWS_LAMBDA_LOG_LEVEL="WARN"
Note over Application Logger: POWERTOOLS_LOG_LEVEL="DEBUG"
Lambda service->>Lambda function: Invoke (event)
Lambda function->>Lambda function: Calls handler
Lambda function->>Application Logger: Logger.Warning("Something happened")
Lambda function-->>Application Logger: Logger.Debug("Something happened")
Lambda function-->>Application Logger: Logger.Information("Something happened")
Lambda service->>Lambda service: DROP INFO and DEBUG logs
Lambda service->>CloudWatch Logs: Ingest error logs
Priority of log level settings in Powertools for AWS Lambda
We prioritise log level settings in this order:
- AWS_LAMBDA_LOG_LEVEL environment variable
- Setting the log level in code using
[Logging(LogLevel = )]
- POWERTOOLS_LOG_LEVEL environment variable
If you set Logger
level lower than ALC, we will emit a warning informing you that your messages will be discarded by
Lambda.
NOTE With ALC enabled, we are unable to increase the minimum log level below the
AWS_LAMBDA_LOG_LEVEL
environment variable value, see AWS Lambda service documentation {target="_blank"} for more details.
Using JsonSerializerOptions¶
Powertools supports customizing the serialization and deserialization of Lambda JSON events and your own types using
JsonSerializerOptions
.
You can do this by creating a custom JsonSerializerOptions
and passing it to the JsonOptions
of the Powertools
Logger.
Supports TypeInfoResolver
and DictionaryKeyPolicy
options. These two options are the most common ones used to
customize the serialization of Powertools Logger.
TypeInfoResolver
: This option allows you to specify a customJsonSerializerContext
that contains the types you want to serialize and deserialize. This is especially useful when using AOT compilation, as it allows you to specify the types that should be included in the generated assembly.DictionaryKeyPolicy
: This option allows you to specify a custom naming policy for the properties in the JSON output. This is useful when you want to change the casing of the property names or use a different naming convention.
Info
If you want to preserve the original casing of the property names (keys), you can set the DictionaryKeyPolicy
to
null
.
1 2 3 4 5 6 7 8 |
|
Custom Log formatter (Bring Your Own Formatter)¶
You can customize the structure (keys and values) of your log entries by implementing a custom log formatter and
override default log formatter using LogFormatter
property in the configure
options.
You can implement a custom log formatter by
inheriting the ILogFormatter
class and implementing the object FormatLogEntry(LogEntry logEntry)
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Buffering logs¶
Log buffering enables you to buffer logs for a specific request or invocation. Enable log buffering by passing LogBufferingOptions
when configuring a Logger instance. You can buffer logs at the Warning
, Information
, Debug
or Trace
level, and flush them automatically on error or manually as needed.
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Configuring the buffer¶
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:
Parameter | Description | Configuration | Default |
---|---|---|---|
MaxBytes |
Maximum size of the log buffer in bytes | number |
20480 |
BufferAtLogLevel |
Minimum log level to buffer | Trace , Debug , Information , Warning |
Debug |
FlushOnErrorLog |
Automatically flush buffer when logging an error | True , False |
True |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
- Setting
BufferAtLogLevel: 'Warning'
configures log buffering forWarning
and all lower severity levels likeInformation
,Debug
, andTrace
. - Calling
Logger.ClearBuffer()
will clear the buffer without emitting the logs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
- 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 theLogger.FlushBuffer()
method.
Flushing on errors¶
When using the Logger
decorator, 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Buffering workflows¶
Manual flush¶
sequenceDiagram
participant Client
participant Lambda
participant Logger
participant CloudWatch
Client->>Lambda: Invoke Lambda
Lambda->>Logger: Initialize with DEBUG level buffering
Logger-->>Lambda: Logger buffer ready
Lambda->>Logger: Logger.LogDebug("First debug log")
Logger-->>Logger: Buffer first debug log
Lambda->>Logger: Logger.LogInformation("Info log")
Logger->>CloudWatch: Directly log info message
Lambda->>Logger: Logger.LogDebug("Second debug log")
Logger-->>Logger: Buffer second debug log
Lambda->>Logger: Logger.FlushBuffer()
Logger->>CloudWatch: Emit buffered logs to stdout
Lambda->>Client: Return execution result
Flushing buffer manually
Flushing when logging an error¶
sequenceDiagram
participant Client
participant Lambda
participant Logger
participant CloudWatch
Client->>Lambda: Invoke Lambda
Lambda->>Logger: Initialize with DEBUG level buffering
Logger-->>Lambda: Logger buffer ready
Lambda->>Logger: Logger.LogDebug("First log")
Logger-->>Logger: Buffer first debug log
Lambda->>Logger: Logger.LogDebug("Second log")
Logger-->>Logger: Buffer second debug log
Lambda->>Logger: Logger.LogDebug("Third log")
Logger-->>Logger: Buffer third debug log
Lambda->>Lambda: Exception occurs
Lambda->>Logger: Logger.LogError("Error details")
Logger->>CloudWatch: Emit buffered debug logs
Logger->>CloudWatch: Emit error log
Lambda->>Client: Raise exception
Flushing buffer when an error happens
Flushing on error¶
This works only when using the Logger
decorator. You can configure the logger to automatically flush the buffer when an error occurs by setting the FlushBufferOnUncaughtError
option to true
in the decorator.
sequenceDiagram
participant Client
participant Lambda
participant Logger
participant CloudWatch
Client->>Lambda: Invoke Lambda
Lambda->>Logger: Using decorator
Logger-->>Lambda: Logger context injected
Lambda->>Logger: Logger.LogDebug("First log")
Logger-->>Logger: Buffer first debug log
Lambda->>Logger: Logger.LogDebug("Second log")
Logger-->>Logger: Buffer second debug log
Lambda->>Lambda: Uncaught Exception
Lambda->>CloudWatch: Automatically emit buffered debug logs
Lambda->>Client: Raise uncaught exception
Flushing buffer when an uncaught exception happens
Buffering FAQs¶
-
Does the buffer persist across Lambda invocations? 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.
-
Are my logs buffered during cold starts? 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.
-
How can I prevent log buffering from consuming excessive memory? You can limit the size of the buffer by setting the
MaxBytes
option in theLogBufferingOptions
constructor parameter. This will ensure that the buffer does not grow indefinitely and consume excessive memory. -
What happens if the log buffer reaches its maximum size? 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.
-
How is the log size of a log line calculated? The log size is calculated based on the size of the serialized log line in bytes. This includes the size of the log message, the size of any additional keys, and the size of the timestamp.
-
What timestamp is used when I flush the logs? 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.
-
What happens if I try to add a log line that is bigger than max buffer size? 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.
-
What happens if Lambda times out without flushing the buffer? 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.
Timestamp formatting¶
You can customize the timestamp format by setting the TimestampFormat
property in the Logger.Configure
method. The default format is o
, which is the ISO 8601 format.
You can use any valid DateTime format string to customize the timestamp format.
For example, to use the yyyy-MM-dd HH:mm:ss
format, you can do the following:
1 2 3 4 |
|
1 2 3 4 5 6 7 |
|
AOT Support¶
Info
If you want to use the LogEvent
, Custom Log Formatter
features, or serialize your own types when Logging events, you need to either pass JsonSerializerContext
or make changes in your Lambda Main
method.
Info
Starting from version 1.6.0, it is required to update the Amazon.Lambda.Serialization.SystemTextJson NuGet package to version 2.4.3 in your csproj.
Using JsonSerializerOptions¶
To be able to serializer your own types, you need to pass your JsonSerializerContext
to the TypeInfoResolver
of the Logger.Configure
method.
1 2 3 4 5 6 7 |
|
Using PowertoolsSourceGeneratorSerializer¶
Replace SourceGeneratorLambdaJsonSerializer
with PowertoolsSourceGeneratorSerializer
.
This change enables Powertools to construct an instance of JsonSerializerOptions
used to customize the serialization
and deserialization of Lambda JSON events and your own types.
1 2 3 4 |
|
1 2 3 4 |
|
For example when you have your own Demo type
1 2 3 4 5 |
|
To be able to serialize it in AOT you have to have your own JsonSerializerContext
1 2 3 4 5 6 |
|
When you update your code to use PowertoolsSourceGeneratorSerializer<MyCustomJsonSerializerContext>
, we combine your
JsonSerializerContext
with Powertools' JsonSerializerContext
. This allows Powertools to serialize your types and
Lambda events.
Custom Log Formatter¶
To use a custom log formatter with AOT, pass an instance of ILogFormatter
to PowertoolsSourceGeneratorSerializer
instead of using the static Logger.UseFormatter
in the Function constructor as you do in non-AOT Lambdas.
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
Anonymous types¶
Note
While we support anonymous type serialization by converting to a Dictionary<string, object>
, this is not a best practice and is not recommended when using native AOT.
We recommend using concrete classes and adding them to your JsonSerializerContext
.
Testing¶
You can change where the Logger
will output its logs by setting the LogOutput
property.
We also provide a helper class for tests TestLoggerOutput
or you can provider your own implementation of IConsoleWrapper
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
ILogger¶
If you are using ILogger interface you can inject the logger in a dedicated constructor for your Lambda function and thus you can mock your ILogger instance.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|