aws-powertools / powertools-lambda-dotnet

Powertools is a developer toolkit to implement Serverless best practices and increase developer velocity.
https://docs.powertools.aws.dev/lambda/dotnet/
MIT No Attribution
158 stars 25 forks source link

Feature request: Support Native AOT #212

Open hjgraca opened 1 year ago

hjgraca commented 1 year ago

Use case

Historically, .NET Lambda functions have cold-start times which impact user experience, system latency, and usage costs of your serverless applications. With .NET 8, Microsoft adds support for Native AOT compilation, which speeds up performance by compiling .NET code directly to machine and operating system native code, eliminating Just-in-Time (JIT) compiling at runtime. With .NET 8 Native AOT compilation, you can improve cold-start times of your Lambda functions. To learn more about Native AOT for .NET 8, see Using Native AOT in the Dotnet GitHub repository.

Solution/User Experience

Currently Native AOT is supported in .NET 8 but only a limited number of libraries are fully compatible with native AOT in .NET 8. Lambda Powertools is not fully compatible with native AOT because of some libraries it uses (for example System.Text.Json.Serializer) which is not supported in native AOT

Proposal

All the work for this release will happen in a separate branch aot-support, this will allow us to publish alpha releases to Nuget. This will allow customers to get early access to the new features and optionally start applying the required changes or simply give feedback on the new items.

Quick summary

The work for AOT support will be done in multiple phases beginning on supporting Logging and Metrics and continuing to the remaining utilities. We believe these two utilities will have the most work and that is the reason we are starting from there.

Item Issue Status Code change required Notes
.NET 8 support #542
Create automation for .NET 8 release multiple target frameworks #542
Logging AOT support #628 Yes
Logging tackle AOT warnings #628 100+ warnings
Metrics AOT support #602 Yes
Metrics tackle AOT warnings #602 Yes 10+ warnings
Tracing AOT support #607 Yes
Tracing tackle AOT warnings #607 Yes 10+ warnings
Create guide in docs 🗓
Idempotency AOT support 🗓️ Yes
Idempotency tackle AOT warnings Yes 10+ warnings
Batch AOT support 🗓️ Yes
Batch tackle AOT warnings Yes 10+ warnings
Parameters AOT support 🗓️ Yes
Parameters tackle AOT warnings Yes 10+ warnings

Legend for Status column:

Potential challenges

Tackling AOT trim warnings will be the most time consuming tasks, for this reason if some unforeseen blocker comes up we might decide to reprioritize and exclude some issues from the milestone.

Acknowledgment

jeastham1993 commented 1 year ago

@hjgraca PR raised for the first pass on native AOT support. I expect this will need some discussion, especially around logging. We can't easily support Exceptions in their entirety, byte[], streams or any anoymous types. Some of the unit tests will fail based on this missing functionality but I thought I would leave it that way as a means of discussion

https://github.com/aws-powertools/powertools-lambda-dotnet/pull/341

The crux of the work is adding a new IPowerToolsSerializer, initially with 2 implementations. A default one that uses System.Text.Json, and then a source genereted one that allows users to add a custom serialization context.

This is the same way it works for the Lambda runtime itself.

hjgraca commented 8 months ago

We have released alpha versions for Logging and Metrics. They are now available to download from Nuget.

These are very early release versions and not recommended for production workloads, we have done our best to support all features, but you will see some warnings when trimming for AOT deployment.

andy-potter-evotix commented 6 months ago

We are using .NET8 AOT based lambda in development at the moment with the 1.6.0-alpha version of the PowerTools logging. We are due to go to production in around 4-6 weeks time. Do you have plans to release a production ready version of PowerTools logging with AOT support?

hjgraca commented 6 months ago

Hi @andy-potter-evotix thanks for using Powertools, the full support of AOT is top priority and we are working as fast as we can to ship it as soon as possible, unfortunately I cannot guarantee delivery dates, but in the next weeks we will be releasing another alpha and more stable versions. Hopefully we can get something by that time that we are confident that can go to production. Keep an eye on this issue we will update whenever we have news. If you want to reach us directly use can use the email aws-lambda-powertools-feedback@amazon.com

Again thanks for using Powertools 👍

Euclidite commented 1 month ago

@hjgraca I wanted to confirm - Is the Logging/Metric support for trimming officially completed/supported? I'm having some issue getting it working on my end even with the recommended changes.

hjgraca commented 1 month ago

@Euclidite yes it is implemented in both Logging and Metrics, Logging > 1.6.0 and Metrics > 1.7.1. What is the issue/erro you are seeing?

Euclidite commented 1 month ago

Here's the exception I get in the logs (which is telling me "I didn't configure something right"):

10-Oct-2024 9:34:07 AM  2024-10-10T13:34:07.448Z 9bc362e6-76bc-4f22-b004-8c5622ca8078 fail System.InvalidOperationException: Reflection-based serialization has been disabled for this application. Either use the source generator APIs or explicitly configure the 'JsonSerializerOptions.TypeInfoResolver' property.
   at System.Text.Json.ThrowHelper.ThrowInvalidOperationException_JsonSerializerIsReflectionDisabled()
   at System.Text.Json.JsonSerializerOptions.ConfigureForJsonSerializer()
   at System.Text.Json.JsonSerializer.GetTypeInfo(JsonSerializerOptions, Type)
   at System.Text.Json.JsonSerializer.GetTypeInfo[T](JsonSerializerOptions)
   at System.Text.Json.JsonSerializer.Serialize[TValue](TValue, JsonSerializerOptions )
   at AWS.Lambda.Powertools.Logging.Serializers.PowertoolsLoggingSerializer.Serialize(Object, Type)
   at AWS.Lambda.Powertools.Logging.Internal.PowertoolsLogger.Log[TState](LogLevel, EventId, TState, Exception, Func`3)
   at Microsoft.Extensions.Logging.LoggerExtensions.Log(ILogger , LogLevel, EventId, Exception, String, Object[] )
   at Microsoft.Extensions.Logging.LoggerExtensions.LogInformation(ILogger, String , Object[] )
   at submit_feedback.Function.FunctionHandlerAsync(SubmitFeedbackRequest request, ILambdaContext context) in ./Function.cs:line 63
   at Amazon.Lambda.RuntimeSupport.HandlerWrapper.<>c__DisplayClass14_0`1.<<GetHandlerWrapper>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Amazon.Lambda.RuntimeSupport.LambdaBootstrap.InvokeOnceAsync(CancellationToken )

Here are the relevant lines of what I have in my csproj file (Let me know if you need more context):

<PropertyGroup>
    <PublishReadyToRun>true</PublishReadyToRun>
    <StripSymbols>true</StripSymbols>
    <PublishTrimmed>true</PublishTrimmed>
    <TrimMode>partial</TrimMode>
</PropertyGroup>
<ItemGroup>
    <!-- Exclude EF Core assemblies from being trimmed (not supported) -->
    <TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore" />
    <TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Relational" />
    <TrimmerRootAssembly Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
    <TrimmerRootAssembly Include="EFCore.NamingConventions" />
</ItemGroup>

And here's how I've been able to minimally reproduce this:

public class Function
{
    private static async Task Main()
    {
        await LambdaBootstrapBuilder.Create<SubmitFeedbackRequest>(
                                        FunctionHandlerAsync,
                                        new PowertoolsSourceGeneratorSerializer<LambdaFunctionJsonContext>())
            .Build()
            .RunAsync();
    }

   [Logging]
   public static async Task FunctionHandlerAsync(SubmitFeedbackRequest request, ILambdaContext context)
   {
       Logger.LogInformation("Starting up!");
    }
}

[JsonSerializable(typeof(SubmitFeedbackRequest))]
public partial class LambdaFunctionJsonContext : JsonSerializerContext { }

For reference, here are my package versions: Image

hjgraca commented 1 month ago

Not sure if it makes a difference but I usually build the handler in the main like so.

 Func< SubmitFeedbackRequest, ILambdaContext, Task> handler = FunctionHandler;
        await LambdaBootstrapBuilder.Create(handler, new PowertoolsSourceGeneratorSerializer< LambdaFunctionJsonContext >())
            .Build()
            .RunAsync();
Euclidite commented 1 month ago

I've updated my main to this:

    private static async Task Main()
    {
        Console.WriteLine("Starting up");
        Func<SubmitFeedbackRequest, ILambdaContext, Task> handler = FunctionHandlerAsync;
        await LambdaBootstrapBuilder.Create(handler, new PowertoolsSourceGeneratorSerializer<LambdaFunctionJsonContext>())
            .Build()
            .RunAsync();

        Console.WriteLine("Shutting down");
    }

Unfortunately, I still get the same exception logged.

Let me know if you'd like a new ticket, or if you want to dig into it a bit first 🙂

hjgraca commented 1 month ago

Please open an issue, from my initial tests is because the Lambda is not returning any values. Thank you!

Euclidite commented 1 month ago

Created https://github.com/aws-powertools/powertools-lambda-dotnet/issues/668!