@@ -5,7 +5,8 @@ description: Getting started with Logging in Native AOT applications
5
5
6
6
# Getting Started with AWS Lambda Powertools for .NET Logger in Native AOT
7
7
8
- This tutorial shows you how to set up an AWS Lambda project using Native AOT compilation with Powertools for .NET Logger, addressing performance, trimming, and deployment considerations.
8
+ This tutorial shows you how to set up an AWS Lambda project using Native AOT compilation with Powertools for .NET
9
+ Logger, addressing performance, trimming, and deployment considerations.
9
10
10
11
## Prerequisites
11
12
@@ -16,7 +17,8 @@ This tutorial shows you how to set up an AWS Lambda project using Native AOT com
16
17
17
18
## 1. Understanding Native AOT
18
19
19
- Native AOT (Ahead-of-Time) compilation converts your .NET application directly to native code during build time rather than compiling to IL (Intermediate Language) code that gets JIT-compiled at runtime. Benefits for AWS Lambda include:
20
+ Native AOT (Ahead-of-Time) compilation converts your .NET application directly to native code during build time rather
21
+ than compiling to IL (Intermediate Language) code that gets JIT-compiled at runtime. Benefits for AWS Lambda include:
20
22
21
23
- Faster cold start times (typically 50-70% reduction)
22
24
- Lower memory footprint
@@ -74,130 +76,114 @@ dotnet add package AWS.Lambda.Powertools.Logging
74
76
Let's modify the Function.cs file to implement our function with Powertools Logger in an AOT-compatible way:
75
77
76
78
``` csharp
77
- using System .Text .Json ;
78
79
using Amazon .Lambda .Core ;
79
80
using Amazon .Lambda .RuntimeSupport ;
80
81
using Amazon .Lambda .Serialization .SystemTextJson ;
82
+ using System .Text .Json .Serialization ;
83
+ using System .Text .Json ;
81
84
using AWS .Lambda .Powertools .Logging ;
85
+ using Microsoft .Extensions .Logging ;
86
+
82
87
83
88
namespace PowertoolsAotLoggerDemo ;
84
89
85
90
public class Function
86
91
{
87
- /// <summary >
88
- /// The main entry point for the Lambda function. The main function is called once during the Lambda init phase.
89
- /// It initializes the Lambda runtime client and passes the function handler to it.
90
- /// </summary >
92
+ private static ILogger _logger ;
93
+
91
94
private static async Task Main ()
92
95
{
93
- // Configure the serializer
94
- var serializer = new DefaultLambdaJsonSerializer (options =>
95
- {
96
- options .PropertyNameCaseInsensitive = true ;
97
- });
98
-
99
- // Create a runtime client and pass the handler function
100
- using var handlerWrapper = LambdaBootstrapBuilder .Create ()
101
- .WithSerializer (serializer )
102
- .WithHandler (FunctionHandler )
103
- .Build ();
104
-
105
- // Start handling Lambda events
106
- await handlerWrapper .RunAsync ();
96
+ _logger = LoggerFactory .Create (builder =>
97
+ {
98
+ builder .AddPowertoolsLogger (config =>
99
+ {
100
+ config .Service = " TestService" ;
101
+ config .LoggerOutputCase = LoggerOutputCase .PascalCase ;
102
+ config .JsonOptions = new JsonSerializerOptions
103
+ {
104
+ TypeInfoResolver = LambdaFunctionJsonSerializerContext .Default
105
+ };
106
+ });
107
+ }).CreatePowertoolsLogger ();
108
+
109
+ Func < string , ILambdaContext , string > handler = FunctionHandler ;
110
+ await LambdaBootstrapBuilder .Create (handler , new SourceGeneratorLambdaJsonSerializer <LambdaFunctionJsonSerializerContext >())
111
+ .Build ()
112
+ .RunAsync ();
107
113
}
108
114
109
- /// <summary >
110
- /// This function handler processes incoming events.
111
- /// </summary >
112
- [Logging (LoggerOutputCase = LoggerOutputCase .CamelCase , CorrelationIdPath = " /headers/x-correlation-id" )]
113
- public static async Task <string > FunctionHandler (JsonElement input , ILambdaContext context )
115
+ public static string FunctionHandler (string input , ILambdaContext context )
114
116
{
115
- // Log the incoming event
116
- Logger .LogInformation (" Processing event: {@Event}" , input );
117
-
118
- // Extract a name from the event (if provided)
119
- string name = " World" ;
120
- if (input .TryGetProperty (" name" , out var nameProperty ) && nameProperty .ValueKind == JsonValueKind .String )
121
- {
122
- name = nameProperty .GetString () ?? " World" ;
123
- Logger .LogInformation (" Name provided: {Name}" , name );
124
- }
125
- else
126
- {
127
- Logger .LogInformation (" Using default name" );
128
- }
129
-
130
- // Create a simple response
131
- var response = new
132
- {
133
- Message = $" Hello, {name }! (from Native AOT)" ,
134
- ProcessedAt = DateTime .UtcNow .ToString (" o" ),
135
- ExecutionEnvironment = " Native AOT"
136
- };
137
-
138
- // Log the response
139
- Logger .LogInformation (" Returning response: {@Response}" , response );
140
-
141
- // Return the serialized response
142
- return JsonSerializer .Serialize (response );
117
+ _logger .LogInformation (" Processing input: {Input}" , input );
118
+ _logger .LogInformation (" Processing context: {@Context}" , context );
119
+
120
+ return input .ToUpper ();
143
121
}
144
122
}
123
+
124
+
125
+ [JsonSerializable (typeof (string ))]
126
+ [JsonSerializable (typeof (ILambdaContext ))] // make sure to include ILambdaContext for serialization
127
+ public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext
128
+ {
129
+ }
145
130
```
146
131
147
132
## 6. Updating the Project File for AOT Compatibility
148
133
149
-
150
134
``` xml
135
+
151
136
<Project Sdk =" Microsoft.NET.Sdk" >
152
- <PropertyGroup >
153
- <TargetFramework >net8.0</TargetFramework >
154
- <ImplicitUsings >enable</ImplicitUsings >
155
- <Nullable >enable</Nullable >
156
-
157
- <!-- Enable AOT compilation -->
158
- <PublishAot >true</PublishAot >
159
-
160
- <!-- Enable trimming, required for Native AOT -->
161
- <PublishTrimmed >true</PublishTrimmed >
162
-
163
- <!-- Set trimming level: full is most aggressive -->
164
- <TrimMode >full</TrimMode >
165
-
166
- <!-- Prevent warnings from becoming errors -->
167
- <TrimmerWarningLevel >0</TrimmerWarningLevel >
168
-
169
- <!-- If you're encountering trimming issues, enable this for more detailed info -->
170
- <!-- <TrimmerLogLevel>detailed</TrimmerLogLevel> -->
171
-
172
- <!-- These settings optimize for Lambda -->
173
- <StripSymbols >true</StripSymbols >
174
- <OptimizationPreference >Size</OptimizationPreference >
175
- <InvariantGlobalization >true</InvariantGlobalization >
176
-
177
- <!-- Assembly attributes needed for Lambda -->
178
- <GenerateRuntimeConfigurationFiles >true</GenerateRuntimeConfigurationFiles >
179
- <AWSProjectType >Lambda</AWSProjectType >
180
-
181
- <!-- Native AOT requires executable, not library -->
182
- <OutputType >Exe</OutputType >
183
-
184
- <!-- Avoid the copious logging from the native AOT compiler -->
185
- <IlcGenerateStackTraceData >false</IlcGenerateStackTraceData >
186
- <IlcOptimizationPreference >Size</IlcOptimizationPreference >
187
- </PropertyGroup >
188
-
189
- <ItemGroup >
190
- <PackageReference Include =" Amazon.Lambda.RuntimeSupport" Version =" 1.10 .0" />
191
- <PackageReference Include =" Amazon.Lambda.Core" Version =" 2.2 .0" />
192
- <PackageReference Include =" Amazon.Lambda.Serialization.SystemTextJson" Version =" 2.4.0 " />
193
- <PackageReference Include =" AWS.Lambda.Powertools.Logging" Version =" 1.4 .0" />
194
- </ItemGroup >
137
+ <PropertyGroup >
138
+ <TargetFramework >net8.0</TargetFramework >
139
+ <ImplicitUsings >enable</ImplicitUsings >
140
+ <Nullable >enable</Nullable >
141
+
142
+ <!-- Enable AOT compilation -->
143
+ <PublishAot >true</PublishAot >
144
+
145
+ <!-- Enable trimming, required for Native AOT -->
146
+ <PublishTrimmed >true</PublishTrimmed >
147
+
148
+ <!-- Set trimming level: full is most aggressive -->
149
+ <TrimMode >full</TrimMode >
150
+
151
+ <!-- Prevent warnings from becoming errors -->
152
+ <TrimmerWarningLevel >0</TrimmerWarningLevel >
153
+
154
+ <!-- If you're encountering trimming issues, enable this for more detailed info -->
155
+ <!-- <TrimmerLogLevel>detailed</TrimmerLogLevel> -->
156
+
157
+ <!-- These settings optimize for Lambda -->
158
+ <StripSymbols >true</StripSymbols >
159
+ <OptimizationPreference >Size</OptimizationPreference >
160
+ <InvariantGlobalization >true</InvariantGlobalization >
161
+
162
+ <!-- Assembly attributes needed for Lambda -->
163
+ <GenerateRuntimeConfigurationFiles >true</GenerateRuntimeConfigurationFiles >
164
+ <AWSProjectType >Lambda</AWSProjectType >
165
+
166
+ <!-- Native AOT requires executable, not library -->
167
+ <OutputType >Exe</OutputType >
168
+
169
+ <!-- Avoid the copious logging from the native AOT compiler -->
170
+ <IlcGenerateStackTraceData >false</IlcGenerateStackTraceData >
171
+ <IlcOptimizationPreference >Size</IlcOptimizationPreference >
172
+ </PropertyGroup >
173
+
174
+ <ItemGroup >
175
+ <PackageReference Include =" Amazon.Lambda.RuntimeSupport" Version =" 1.12 .0" />
176
+ <PackageReference Include =" Amazon.Lambda.Core" Version =" 2.5 .0" />
177
+ <PackageReference Include =" Amazon.Lambda.Serialization.SystemTextJson" Version =" 2.4.4 " />
178
+ <PackageReference Include =" AWS.Lambda.Powertools.Logging" Version =" 2.0 .0" />
179
+ </ItemGroup >
195
180
</Project >
196
181
```
197
182
198
183
## 8. Cross-Platform Deployment Considerations
199
184
200
- Native AOT compilation must target the same OS and architecture as the deployment environment. AWS Lambda runs on Amazon Linux 2023 (AL2023) with x64 architecture.
185
+ Native AOT compilation must target the same OS and architecture as the deployment environment. AWS Lambda runs on Amazon
186
+ Linux 2023 (AL2023) with x64 architecture.
201
187
202
188
### Building for AL2023 on Different Platforms
203
189
@@ -210,6 +196,7 @@ dotnet lambda deploy-function --function-name powertools-aot-logger-demo --funct
210
196
```
211
197
212
198
This will:
199
+
213
200
1 . Detect your project is using Native AOT
214
201
2 . Use Docker behind the scenes to compile for Amazon Linux
215
202
3 . Deploy the resulting function
@@ -267,9 +254,42 @@ You should see a response like:
267
254
268
255
``` json
269
256
{
270
- "Message" : " Hello, PowertoolsAOT! (from Native AOT)" ,
271
- "ProcessedAt" : " 2023-11-10T10:15:20.1234567Z" ,
272
- "ExecutionEnvironment" : " Native AOT"
257
+ "Level" : " Information" ,
258
+ "Message" : " test" ,
259
+ "Timestamp" : " 2025-05-06T09:52:19.8222787Z" ,
260
+ "Service" : " TestService" ,
261
+ "ColdStart" : true ,
262
+ "XrayTraceId" : " 1-6819dbd3-0de6dc4b6cc712b020ee8ae7" ,
263
+ "Name" : " AWS.Lambda.Powertools.Logging.Logger"
264
+ }
265
+ {
266
+ "Level" : " Information" ,
267
+ "Message" : " Processing context: Amazon.Lambda.RuntimeSupport.LambdaContext" ,
268
+ "Timestamp" : " 2025-05-06T09:52:19.8232664Z" ,
269
+ "Service" : " TestService" ,
270
+ "ColdStart" : true ,
271
+ "XrayTraceId" : " 1-6819dbd3-0de6dc4b6cc712b020ee8ae7" ,
272
+ "Name" : " AWS.Lambda.Powertools.Logging.Logger" ,
273
+ "Context" : {
274
+ "AwsRequestId" : " 20f8da57-002b-426d-84c2-c295e4797e23" ,
275
+ "ClientContext" : {
276
+ "Environment" : null ,
277
+ "Client" : null ,
278
+ "Custom" : null
279
+ },
280
+ "FunctionName" : " powertools-aot-logger-demo" ,
281
+ "FunctionVersion" : " $LATEST" ,
282
+ "Identity" : {
283
+ "IdentityId" : null ,
284
+ "IdentityPoolId" : null
285
+ },
286
+ "InvokedFunctionArn" : " your arn" ,
287
+ "Logger" : {},
288
+ "LogGroupName" : " /aws/lambda/powertools-aot-logger-demo" ,
289
+ "LogStreamName" : " 2025/05/06/[$LATEST]71249d02013b42b9b044b42dd4c7c37a" ,
290
+ "MemoryLimitInMB" : 512 ,
291
+ "RemainingTime" : " 00:00:29.9972216"
292
+ }
273
293
}
274
294
```
275
295
@@ -279,7 +299,8 @@ Check the logs in CloudWatch Logs to see the structured logs created by Powertoo
279
299
280
300
### Trimming Considerations
281
301
282
- Native AOT uses aggressive trimming, which can cause issues with reflection-based code. Here are tips to avoid common problems:
302
+ Native AOT uses aggressive trimming, which can cause issues with reflection-based code. Here are tips to avoid common
303
+ problems:
283
304
284
305
1 . ** Using DynamicJsonSerializer** : If you're encountering trimming issues with JSON serialization, add a trimming hint:
285
306
@@ -291,7 +312,8 @@ public class MyRequestType
291
312
}
292
313
```
293
314
294
- 2 . ** Logging Objects** : When logging objects with structural logging, consider creating simple DTOs instead of complex types:
315
+ 2 . ** Logging Objects** : When logging objects with structural logging, consider creating simple DTOs instead of complex
316
+ types:
295
317
296
318
``` csharp
297
319
// Instead of logging complex domain objects:
@@ -305,18 +327,20 @@ Logger.LogInformation("User: {@userInfo}", userInfo);
305
327
3 . ** Handling Reflection** : If you need reflection, explicitly preserve types:
306
328
307
329
``` xml
330
+
308
331
<ItemGroup >
309
- <TrimmerRootDescriptor Include =" TrimmerRoots.xml" />
332
+ <TrimmerRootDescriptor Include =" TrimmerRoots.xml" />
310
333
</ItemGroup >
311
334
```
312
335
313
336
And in TrimmerRoots.xml:
314
337
315
338
``` xml
339
+
316
340
<linker >
317
- <assembly fullname =" YourAssembly" >
318
- <type fullname =" YourAssembly.TypeToPreserve" preserve =" all" />
319
- </assembly >
341
+ <assembly fullname =" YourAssembly" >
342
+ <type fullname =" YourAssembly.TypeToPreserve" preserve =" all" />
343
+ </assembly >
320
344
</linker >
321
345
```
322
346
@@ -341,11 +365,13 @@ aws lambda update-function-configuration \
341
365
3 . ** ARM64 Support** : For even better performance, consider using ARM64 architecture:
342
366
343
367
When creating your project:
368
+
344
369
``` bash
345
370
dotnet new lambda.NativeAOT -n PowertoolsAotLoggerDemo --architecture arm64
346
371
```
347
372
348
373
Or modify your deployment:
374
+
349
375
``` bash
350
376
aws lambda update-function-configuration \
351
377
--function-name powertools-aot-logger-demo \
@@ -370,16 +396,18 @@ fields @timestamp, coldStart, billedDurationMs, maxMemoryUsedMB
370
396
If you see errors about missing metadata, you may need to add more types to your trimmer roots:
371
397
372
398
``` xml
399
+
373
400
<ItemGroup >
374
- <TrimmerRootAssembly Include =" AWS.Lambda.Powertools.Logging" />
375
- <TrimmerRootAssembly Include =" System.Private.CoreLib" />
376
- <TrimmerRootAssembly Include =" System.Text.Json" />
401
+ <TrimmerRootAssembly Include =" AWS.Lambda.Powertools.Logging" />
402
+ <TrimmerRootAssembly Include =" System.Private.CoreLib" />
403
+ <TrimmerRootAssembly Include =" System.Text.Json" />
377
404
</ItemGroup >
378
405
```
379
406
380
407
### Build Failures on macOS/Windows
381
408
382
- If you're building directly on macOS/Windows without Docker and encountering errors, remember that Native AOT is platform-specific. Always use the cross-platform build options mentioned earlier.
409
+ If you're building directly on macOS/Windows without Docker and encountering errors, remember that Native AOT is
410
+ platform-specific. Always use the cross-platform build options mentioned earlier.
383
411
384
412
## Summary
385
413
@@ -390,7 +418,9 @@ In this tutorial, you've learned:
390
418
3 . Cross-platform build and deployment strategies for Amazon Linux 2023
391
419
4 . Performance optimization techniques specific to AOT lambdas
392
420
393
- Native AOT combined with Powertools Logger gives you the best of both worlds: high-performance, low-latency Lambda functions with rich, structured logging capabilities.
421
+ Native AOT combined with Powertools Logger gives you the best of both worlds: high-performance, low-latency Lambda
422
+ functions with rich, structured logging capabilities.
394
423
395
424
!!! tip "Next Steps"
396
- Explore using the Embedded Metrics Format (EMF) with your Native AOT Lambda functions for enhanced observability, or try implementing Powertools Tracing in your Native AOT functions.
425
+ Explore using the Embedded Metrics Format (EMF) with your Native AOT Lambda functions for enhanced observability, or try
426
+ implementing Powertools Tracing in your Native AOT functions.
0 commit comments