AWS Lambda Powertools for TypeScript を使ってLambdaにAWS X-Rayを簡単に導入しよう
システム開発部のkiwiです。
AWS Lambda Powertools(以下Powertools)とは、AWS Lambdaのトレース、ログ、メトリクスなどの可観測性を高めるためのツール群です。2022年7月15日より、このPowertoolsのTypeScript版が一般提供されました(リリース)。PowertoolsはPython版、Java版が提供されていましたが、これに続いて3つ目の言語での対応となります。
Powertoolsで提供される機能のうち、分散トレースはAWS X-Rayとの連携機能です。これまでNode.jsのLambdaからX-Rayを呼び出す場合、X-Ray SDK for Node.jsを読み込んで利用していましたが、PowertoolsにはこのSDKのラッパーが含まれています。さらに、TypeScriptのデコレータを使ってより簡単にメソッドの呼び出しを追跡する機能なども追加されています。
この記事では、Powertoolsを使ってNode.jsのLambdaでトレース機能(X-Ray)を利用する方法について見ていきたいと思います。
目次
AWS LambdaのTypeScript対応状況について
Powertoolsを使ってみる前に、まずLambdaにおけるTypeScriptの対応状況について整理します。記事執筆時点では、TypeScriptで記述されたLambdaをデプロイする方法は以下の3種類です(サードパーティー製のツールを除く)。
2022/09/02 追記
記事執筆時点ではベータ機能だったAWS SAM CLIでのesbuildサポートが一般公開されました。
- TypeScriptを自分でトランスパイルし、生成されたjsファイルをデプロイする
- AWS CDK v2を使ってデプロイする
- AWS SAM CLIのビルド
(ベータ)を使ってデプロイする
普段はAWS SAMを使ってLambdaをデプロイする機会が多いので、今回は(まだベータ機能ですが)AWS SAM CLIを使ったビルドを試してみようと思います。
公式サンプルをデプロイしてみる
まずは AWS Lambda Powertools for TypeScript の公式ドキュメントに記載されているExample(https://github.com/awslabs/aws-lambda-powertools-typescript)を実際にデプロイして、挙動を確認してみましょう。
READMEを参考に、以下のコマンドを実行します(SAM CLIのバージョン1.53.0を使用しています)。今回はデプロイコマンドにsam syncを使いました。
2022/09/02 追記
esbuildによるTypeScript対応が一般公開されたため、--beta-features
フラグに関する記述を削除しました。
cd /path/to/aws-lambda-powertools-typescript/examples/sam
npm i
sam build
sam sync --stack-name stack-test-powertools-ts --watch --profile sandbox
今回はsandboxという名前でローカルに登録しているプロファイルを使ってデプロイを実行しています。
デプロイされたDBに適当にレコードを追加し、getByIdのエンドポイントを実行した時のトレース結果をX-Rayで確認してみましょう。
Lambda自体の実行時間に加えて、Lambda内からDynamoDBを参照したため、そのAPI呼び出しにかかった時間についても計測されています。
Powertools for TypeScriptの3つの実装方法
Powertools for TypeScriptを導入する場合、npmを使ってインストールをした後、計測する内容に応じてコードに手を入れる必要があります。以下の3種類の実装方法が用意されています。公式ドキュメントには3パターンそれぞれの実装サンプルが記載されていますので参考にしてください。
Middyミドルウェアとして導入する
Middyとは、AWS Lambda向けのミドルウェアエンジンです。LambdaでよくあるbodyのJSONパースやエラーレスポンスなどの共通処理をミドルウェアとして管理することができるので、開発者はビジネスロジックに集中できるようになります。
Powertools for TypescriptはこのMiddyのミドルウェアとしても提供されているため、ほかのミドルウェアと同様に use()
メソッドをチェインさせることで簡単に導入することが可能です。以下はexample-function.MyFunctionWithMiddy.tsに記載されているサンプルコードです。
export const handler = middy(lambdaHandler)
.use(captureLambdaHandler(tracer))
.use(
logMetrics(metrics, {
captureColdStartMetric: true,
throwOnEmptyMetrics: true,
defaultDimensions: { environment: 'example', type: 'withDecorator' },
})
)
.use(injectLambdaContext(logger));
TypeScriptのデコレータ機能を使う
TypeScriptの実験的機能として提供されているデコレータ機能を使い、処理部分に手を入れずにPowertoolsを導入することができます。CDKのサンプルには、以下のようにデコレーションを使った例が記述されています(example-function.MyFunctionWithDecorator.ts)。
export class MyFunctionWithDecorator implements LambdaInterface {
// We decorate the handler with the various decorators
@tracer.captureLambdaHandler()
@logger.injectLambdaContext()
@metrics.logMetrics({
captureColdStartMetric: true,
throwOnEmptyMetrics: true,
defaultDimensions: { environment: 'example', type: 'withDecorator' },
})
public async handler(event: typeof Events.Custom.CustomEvent, context: Context): Promise<unknown> {
// ...
この例では、@tracer.captureLambdaHandler()
デコレータを使ってLambdaのハンドラとなるメソッドのトレースを有効にしています。Lambdaのライフサイクルやコールドスタートの取り扱いを自動で行ってくれます。
現在デコレータ機能はTypeScriptのクラスベースで記述している場合のみに使うことができます。そのため、ハンドラを通常の関数定義で記述している場合、まずはクラスベースでの記述に直す必要があります。移行が難しい場合、Middyのようなミドルウェアとして導入するか、後述する手動実行での導入となります。
手動実行する
ハンドラなどの処理を行なっているコードのうち、トレースしたい箇所で自分でメソッド呼び出しを記述する方法です。クラスに記述し直すなどの手間はないのですが、X-Ray SDKを直接導入する場合と同程度の追加実装が必要になります。
export const handler = async (event: unknown, context: Context): Promise<void> => {
// Since we are in manual mode we need to create the handler segment (the 4 lines below would be done for you by decorator/middleware)
// we do it at the beginning because we want to trace the whole duration of the handler
const segment = tracer.getSegment(); // This is the facade segment (the one that is created by AWS Lambda)
// Create subsegment for the function & set it as active
const handlerSegment = segment.addNewSubsegment(`## ${process.env._HANDLER}`);
tracer.setSegment(handlerSegment);
// Annotate the subsegment with the cold start & serviceName
tracer.annotateColdStart();
tracer.addServiceNameAnnotation();
// ...
ミドルウェアやデコレータで自動で行なってくれていた annotateColdStart()
などのトレースを自分で記述しています。
AWS APIの呼び出しやHTTP通信をトレースする
Lambdaの実行をトレースする上でぜひ観測できるようにしておきたいのが、AWS SDKを使って呼び出す各種API(例えばDymamoDBの読み書きなど)や、外部のHTTP通信の様子です。(以下コードは公式ドキュメントからの引用)
AWS SDKの通信をトレースする場合、SDKの通信をキャプチャする設定を1行追加するだけで、X-Rayでのトレースを有効にできます。
const client = tracer.captureAWSv3Client(new S3Client({}));
AWS SDK v2を使う場合、AWSクラスのオブジェクトを丸ごとトレースするか、サービスごとのクラスをトレースするか、いずれかで設定します。
const AWS = tracer.captureAWS(require('aws-sdk'));
HTTP通信については、追加の設定を入れなくても自動でキャプチャされます。X-Ray SDKではキャプチャするための設定(captureHTTPsGlobal
メソッドなど)を記述する必要がありました。自動でキャプチャしたくない場合には環境変数やクライアントの設定で無効化することも可能です。
さいごに
AWS Lambda Powertools for TypeScriptを使って、Lambdaの実行をトレースする機能についてご紹介しました。弊社のプロダクトでもサーバーレス環境を中心にLambdaが多く利用されていますが、標準のログ出力による動作確認が中心で、パフォーマンスなどの観測まで行えていないものも多いです。
ただ、現在弊社のNode.jsランタイムのLambdaは、そのほとんどがJavaScriptで記述されています。TypeScriptへの移行によって開発効率を向上しつつ、Powertoolsの活用によって可観測性を高め、より安定したサービスを提供していきたいと思います。
掲載内容は、記事執筆時点の情報をもとにしています。