[アップデート] .NET Lambda のオブザーバビリティを簡単に向上出来る「AWS Lambda Powertools for .NET」が GA になりました

2023.03.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

いわさです。

本日 AWS Lambda Powertools for .NET が GA になりました。
半年ほど前からプレビューでは使えていたのですが、今回 GA となりプロダクション環境へも採用しやすくなりました。

今回は Lambda Powertools for .NET を使わない場合と使う場合を比較してみたので紹介します。

AWS Lambda Powertools とは

AWS Lambda Powertools 自体は他のランタイムと同じもので主にオブザーバビリティ(可観測性)を向上させるための実装を簡単に導入出来るライブラリです。
DevelopersIO で詳しく紹介された記事もあります。

当時は Python と Java がサポートされていて、半年ほど前に TypeScript (NodeJS) もサポートに追加されています。

今回この .NET 版がついに GA となったという形です。

機能

主な機能は他のランタイムと同じで Tracing / Logger / Metrics となっています。

簡単にまとめると、以下を独自で準備しなくても Powertools の仕組みを使うだけで簡単に利用することが出来るようになります

  • X-Ray のサブセグメントごとに計測
  • CloudWatch Logs Insights で扱いやすい状態でログ出力
  • カスタムメトリクスを出力

Powertools なし

ではまずは Powertools なしでログやトレースを有効化してみます。
いくつかヘルパーライブラリなども存在はしますが、公式ドキュメントの以下を使ってみます。

カスタムメトリクスに関してはかなり準備が面倒なのでやりませんでした。

デプロイと実行

Amazon.Lambda.Templates の lambda.EmptyFunction あたりに上記を組み込んでいきます。
この記事では以下を使って Lambda 関数のスケルトン作成からデプロイと Invoke までやっていきます。

まずは初期化します。

% dotnet new lambda.EmptyFunction                   
The template "Lambda Empty Function" was created successfully.

実装は適当ですが、こんな感じにしました。

Function.cs

using Amazon.Lambda.Core;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace hoge0301nonpowertool;

public class Function
{   
    public string FunctionHandler(string input, ILambdaContext context)
    {
        var upperCaseString = UpperCaseString(input);

        LambdaLogger.Log($"Uppercase of '{input}' is {upperCaseString}");

        return input.ToUpper();
    }

    private static string UpperCaseString(string input)
    {
        try
        {
            //hogehoge   //カスタムメトリクス出力したかった
            return input.ToUpper();
        }
        catch (Exception ex)
        {
            LambdaLogger.Log(ex.ToString());
            throw;
        }
    }
}

dotnet builkd -> dotnet lambda deploy-functionでそのままデプロイします。このあたりは Amazon.Lambda.Tools によって非常にスムーズで良い体験です。
デプロイ後に Lambda コンソール上からアクティブトレースを有効化しています。X-Ray を使いためです。

では実行してみましょう。

% dotnet lambda invoke-function hoge0301nonpowertool --payload "fuga"
Amazon Lambda Tools for .NET Core applications (5.6.3)
Project Home: https://github.com/aws/aws-extensions-for-dotnet-cli, https://github.com/aws/aws-lambda-dotnet

Payload:
"FUGA"

Log Tail:
START RequestId: aafd79ea-1b19-4c7f-a8a6-bc8ab7e89893 Version: $LATEST
2023-02-28T22:04:16.325Z        aafd79ea-1b19-4c7f-a8a6-bc8ab7e89893    Uppercase of 'fuga' is FUGA
END RequestId: aafd79ea-1b19-4c7f-a8a6-bc8ab7e89893
REPORT RequestId: aafd79ea-1b19-4c7f-a8a6-bc8ab7e89893  Duration: 6.12 ms       Billed Duration: 7 ms   Memory Size: 256 MB     Max Memory Used: 62 MB
XRAY TraceId: 1-63fe7a60-19a4e3b13b2ed24932d82530       SegmentId: 6770c68e7e96e750     Sampled: true

問題なく実行されました。

観測してみる

では各サービスからどのように確認が出来るのか見てみましょう。

CloudWatch Logs

CloudWatch Logs はLambdaLogger.Log()で出力した内容がそのまま確認出来ます。
期待どおりといえば期待どおりです。

X-Ray

続いて X-Ray コンソールからトレースマップを確認してみましょう。
こちらも期待どおりで、Invocation サブセグメントで関数ハンドラの実行時間を確認出来ます。
Overhead サブセグメントはランタイムが応答を送信してから次の呼び出しのシグナルを送信するまでの間に発生するフェーズを指しています。

Powertools あり

デプロイと実行

では、続いて Powertools を使って先程に近いことを実現してみましょう。

Lambda Powertools for .NET のリポジトリは以下です。

README にも記載がありますが、Logging/Metrics/Tracing それぞれで個別の NuGet パッケージをインストールする必要があります。
逆にいえば一部だけ使うことも可能ということですね。

そして、以下が Lambda Powertools for .NET を使う形で実装されたクラスです。
ハイライト部分に注目してください。

Function.cs

using Amazon.Lambda.Core;
using AWS.Lambda.Powertools.Logging;
using AWS.Lambda.Powertools.Metrics;
using AWS.Lambda.Powertools.Tracing;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace hoge0301powertool;

public class Function
{
    [Logging(LogEvent = true)]
    [Tracing]
    public string FunctionHandler(string input, ILambdaContext context)
    {
        var upperCaseString = UpperCaseString(input);

        Logger.LogInformation($"Uppercase of '{input}' is {upperCaseString}");

        return upperCaseString;
    }

    [Metrics(CaptureColdStart = true)]
    [Tracing(SegmentName = "UpperCaseString Method")]
    private static string UpperCaseString(string input)
    {
        try
        {
            Metrics.AddMetric("UpperCaseString_Invocations", 1, MetricUnit.Count);

            return input.ToUpper();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex);
            throw;
        }
    }
}

Logging

Logging(LogEvent = true)では着信イベントをログに記録するように Logger に指示するオプションです。機密情報がログに記録されないようにデフォルトではオフになっています。今回は開発環境なのでこちらをオンにしました。

Logger.LogInformationあるいはLogger.LogErrorなどを使うことで、JSON の構造化された形式でログを出力することが出来ます。この方法でログ出力される時にはいくつかの追加の情報(ログレベルなど)も自動で追加されるので Insights などからフィルタリングする際にも利用することが出来ます。

今回は使いませんでしたが、AppendKeysなどを使うことで追加のカスタム属性をログに含めることがも出来るので、マルチテナントアプリケーションでテナントコンテキストを埋め込むなど、色々なシーンで使うことが出来そうです。

なお、ログ出力にあたっていくつかの環境変数を指定する必要があります。

変数名 役割
POWERTOOLS_SERVICE_NAME ログのトレースに使用されるサービス名
POWERTOOLS_LOG_LEVEL ログレベル

Metrics

メトリクスは主にMetrics.AddMetricで出力することが出来ます。
仕組みですが、実体としては CloudWatch Logs へのログ出力で EMF(Embedded Metrics Format) に準拠した出力となるのでそのままカスタムメトリクスとして扱うことが出来るようになっています。

またデフォルトはオフになっているのですが、[Metrics(CaptureColdStart = true)]を指定することでコールドスタートの回数をメトリクスとして取得することが出来ます。
これはだいぶ便利かもしれない。

なお、カスタムメトリクス発行にあたっていくつかの環境変数を指定する必要があります。

変数名 役割
POWERTOOLS_SERVICE_NAME メトリクスのディメンションに使用されるサービス名
POWERTOOLS_METRICS_NAMESPACE メトリクスの名前空間

Tracing

[Tracing]を使うことで X-Ray トレースが有効化されます。
また[Tracing(SegmentName = "UpperCaseString Method")]のように任意のメソッドで別のカスタムサブセグメント名としてキャプチャされるように指定することも出来ます。

なお、X-Ray トレースの利用にあたって以下の環境変数を指定する必要があります。

変数名 役割
POWERTOOLS_SERVICE_NAME X-Ray 名前空間に使用されるサービス名

別の環境変数としてPOWERTOOLS_TRACE_DISABLEDもあり、トレースの無効化を環境変数から設定することも出来ます。

実行して観察してみる

いくつかの Lambda 環境変数を指定して、X-Ray アクティブトレースを有効化し、関数を実行してみましょう。

% dotnet lambda invoke-function hoge0301powertool --payload "hoge"
Amazon Lambda Tools for .NET Core applications (5.6.3)
Project Home: https://github.com/aws/aws-extensions-for-dotnet-cli, https://github.com/aws/aws-lambda-dotnet

Payload:
"HOGE"

Log Tail:
START RequestId: 516f9001-0634-4459-92b8-8f8757a1a271 Version: $LATEST
2023-02-28T21:14:49.990Z        516f9001-0634-4459-92b8-8f8757a1a271    info    {"ColdStart":true,"XrayTraceId":"1-63fe6ec9-13c7c57773c5262647ebd25f","FunctionName":"hoge0301powertool","FunctionVersion":"$LATEST","FunctionMemorySize":256,"FunctionArn":"arn:aws:lambda:ap-northeast-1:123456789012:function:hoge0301powertool","FunctionRequestId":"516f9001-0634-4459-92b8-8f8757a1a271","Timestamp":"2023-02-28T21:14:49.9293294Z","Level":"Information","Service":"PowertoolsFunction","Name":"AWS.Lambda.Powertools.Logging.Logger","Message":"hoge"}
2023-02-28T21:14:50.229Z        516f9001-0634-4459-92b8-8f8757a1a271    info    {"_aws":{"Timestamp":1677618890068,"CloudWatchMetrics":[{"Namespace":"powertools_function","Metrics":[{"Name":"ColdStart","Unit":"Count"}],"Dimensions":[["FunctionName"],["Service"]]}]},"FunctionName":"hoge0301powertool","Service":"PowertoolsFunction","ColdStart":1}
2023-02-28T21:14:50.229Z        516f9001-0634-4459-92b8-8f8757a1a271    info    {"_aws":{"Timestamp":1677618890229,"CloudWatchMetrics":[{"Namespace":"powertools_function","Metrics":[{"Name":"UpperCaseString_Invocations","Unit":"Count"}],"Dimensions":[["Service"]]}]},"Service":"PowertoolsFunction","UpperCaseString_Invocations":1}
2023-02-28T21:14:50.229Z        516f9001-0634-4459-92b8-8f8757a1a271    info    {"ColdStart":true,"XrayTraceId":"1-63fe6ec9-13c7c57773c5262647ebd25f","FunctionName":"hoge0301powertool","FunctionVersion":"$LATEST","FunctionMemorySize":256,"FunctionArn":"arn:aws:lambda:ap-northeast-1:123456789012:function:hoge0301powertool","FunctionRequestId":"516f9001-0634-4459-92b8-8f8757a1a271","Timestamp":"2023-02-28T21:14:50.2296548Z","Level":"Information","Service":"PowertoolsFunction","Name":"AWS.Lambda.Powertools.Logging.Logger","Message":"Uppercase of 'hoge' is HOGE"}
END RequestId: 516f9001-0634-4459-92b8-8f8757a1a271
REPORT RequestId: 516f9001-0634-4459-92b8-8f8757a1a271  Duration: 1188.11 ms    Billed Duration: 1189 ms        Memory Size: 256 MB     Max Memory Used: 77 MB
XRAY TraceId: 1-63fe6ec9-13c7c57773c5262647ebd25f       SegmentId: 329b8f764796bef2     Sampled: true

うまく実行出来ています。
では各サービスを確認してみましょう。

CloudWatch Logs

CloudWatch Logs には以下のような形式で出力されていました。
コードから埋め込んだ内容はMessageとして設定されていますね。

JSON 形式になっているので、CloudWatch Logs Insights から利用する際は Parse 不要でそのまま属性を指定出来ます。
フラットでプレーンなテキストよりも分析しやすくてかなり良いですね。

CloudWatch メトリクス

まず CloudWatch Logs で次のようにカスタムメトリクスが埋め込まれた EMF のログが出力されていることを確認しました。

CloudWatch のメトリクス機能で確認してみましょう。
次のようにコードから指定したカスタムメトリクスを確認することが出来ています。

また、コールドスタートメトリクスについても同様にカスタムメトリクスとして取得出来るようになっていました。

X-Ray

X-Ray の出力自体は Powertools を使う前も簡単に有効化出来ていましたが、今回はカスタムサブセグメントを指定しているので、ハンドラーの更に内訳を分析することが出来ます。

これはすごく良いですね。
いつも泥臭くデバッグログみたいなの埋め込むこと多かったのですが、こんな簡単に導入出来るとは...。

Amazon.Lambda.Templates でもサポート

今回の Lambda Powertools for .NET ですが、既に Amazon.Lambda.Templates の最新版でもデフォルトで実装されたテンプレートが使えるようになっています。
気になる方はバージョン 6.10 以上をインストールして使ってみてください。

% dotnet new install Amazon.Lambda.Templates::6.10.0
The following template packages will be installed:
   Amazon.Lambda.Templates::6.10.0

Amazon.Lambda.Templates (version 6.9.0) is already installed, it will be replaced with version 6.10.0.
Amazon.Lambda.Templates::6.9.0 was successfully uninstalled.
Success: Amazon.Lambda.Templates::6.10.0 installed the following templates:
Template Name                                                                         Short Name                                    Language  Tags                            
------------------------------------------------------------------------------------  --------------------------------------------  --------  --------------------------------
Empty Top-level Function                                                              lambda.EmptyTopLevelFunction                  [C#]      AWS/Lambda/Serverless           

:

Lambda Function project configured for deployment using .NET 7's Native AOT feature.  lambda.NativeAOT                              [C#],F#   AWS/Lambda/Function             
Lambda Function with Powertools                                                       lambda.Powertools                             [C#]      AWS/Lambda/Function/Powertools  

:

さいごに

本日は AWS Lambda Powertools for .NET が GA になったので使ってみました。

AWS Lambda Powertools が強力すぎて驚きました。
これは必須で使っていきたいですね。

導入することによってコールドスタートの時間が長くなるとかデメリットがあったりするのかという点は確認しておきたいですね。
また、Java の SnapStart や .NET の Native AOT との相性がまだよくわかってないのでそのあたりも少し気にしたいところです。