AWS Lambda Node.js で Axios を利用した通信を X-Ray でキャプチャする

2021.04.01

吉川@広島です。

AWS X-Ray を使うと AWS リソース間や外部との通信をキャプチャできます。

AWS X-Ray とは何ですか。

これにより実行時間が遅くなっている原因の特定などに役立てることができます。

AWS Lambda を AWS X-Ray に使用する
【アップデート】AWS X-Ray が AWS Lambda に対応しました(プレビュー)

Lambda に X-Ray を導入するのは非常に簡単なので 積極的に活用していきたいところです。

どのように導入するかですが、 AWS リソースに対するアクセスをキャプチャするのは

import * as AWSBase from 'aws-sdk'

const AWS = AWSXRay.captureAWS(AWSBase)

と手軽な記述で済みます。

他方、 Axios を使って外部サービスのAPIを叩くような場合はどのように書くと良いのか検証してみたので紹介します。

環境

  • TypeScript 4.1.3
  • Lambdaランタイム Node.js 14.x
  • aws-xray-sdk-core 3.2.0

カスタムサブセグメントを定義する

まずはカスタムサブセグメントを定義する方法です。

X-Ray SDK for Node.js を使用してカスタムサブセグメントを生成する

import * as AWSXRay from 'aws-xray-sdk-core'
import axios from 'axios'
import { AwesomeData } from './path/to/my-module'

const fetchAwesomeData = async (): Promise<AwesomeData> => {
  const segment = AWSXRay.getSegment()
  const subsegment = segment?.addNewSubsegment('MY_SUBSEGMENT')
  const { data } = await axios.get<AwesomeData>('https://example.com/awesome-data')
  subsegment?.close()
  return data
}

例の通りで、

const segment = AWSXRay.getSegment();
const subsegment = segment?.addNewSubsegment('MY_SUBSEGMENT');
// 処理
subsegment?.close();

で計測したい処理を挟む方法です。
非常に分かりやすいですが、キャプチャしたい通信箇所をすべて囲っていくのは少し手間な感じもします。

Lambda を実行し、 Web コンソールで確認します。

segment?.addNewSubsegment('MY_SUBSEGMENT') で指定した引数の文字列がサブセグメント名となります。

captureHTTPsGlobal() を使う

captureHTTPsGlobal() を使うと HTTP/HTTPS の通信処理をまとめてキャプチャすることができます。

X-Ray SDK for Node.js を使用してダウンストリーム HTTP ウェブサービスの呼び出しをトレースする

Axios や Superagent などのサードパーティー製の HTTP リクエストライブラリを使用する呼び出しは captureHTTPsGlobal() API を通じてサポートされ、ネイティブ http モジュールを使用する場合でもトレースされます。

リファレンスに Axios を使う場合のサンプルコードも載っていました。

// This sample code works with any promise-based HTTP client.

const AWSXRay = require('aws-xray-sdk');
AWSXRay.captureHTTPsGlobal(require('http'));
AWSXRay.capturePromise();
const AxiosWithXray = require('axios');

こちらを参考に以下のように書きました。

import * as AWSXRay from 'aws-xray-sdk-core'
import axios from 'axios'
import http from 'http'
import https from 'https'
import { AwesomeData } from './path/to/my-module'

AWSXRay.captureHTTPsGlobal(http, false)
AWSXRay.captureHTTPsGlobal(https, false)
AWSXRay.capturePromise()

const fetchAwesomeData = async (): Promise<AwesomeData> => {
  const { data } = await axios.get<AwesomeData>('https://example.com/awesome-data')
  return data
}

まず、 AWSXRay.captureHTTPsGlobal(http, false) AWSXRay.captureHTTPsGlobal(https, false) について、先述のリファレンスでは

Capture all outgoing HTTP and HTTPS requests

と説明があります。
Axios が内部で http/https モジュールを利用しているので、それをキャプチャしているのだと思います。

また、第2引数の boolean 値が何なのかですが、 JSDoc には

downstreamXRayEnabled - when true, adds a "traced:true" property to the subsegment so the AWS X-Ray service expects a corresponding segment from the downstream service.

とあります。
downstream service という言葉が出てきましたがこれは何でしょうか。

LambdaからカスタムサブセグメントをX-Rayに送信する – ClassmethodサーバーレスAdvent Calendar 2017 #serverless #adventcalendar

ダウンストリーム サービスから呼び出される別のサービス、もしくはDynamoDB、PostgreSQL、MySQLなどのミドルウェア

実際に挙動を確認したわけではないのですが、ダウンストリームとは「サービスから呼び出される別のサービス」 なので、例えばAPIを叩いて呼び出したサービスも X-Ray を導入しているような場合にそのトレースも表示するかどうかということだと思います。
自社のマイクロサービス間が通信するような場合に活用できるものであり、外部サービスのAPIを叩くような場合は対象外となるので true にしても取れる情報は変わらないないはずです。
基本的に上述の監視をしたいのでなければ false で良いのではないかと思いました。

もう一つ、 AWSXRay.capturePromise() についてですが、リファレンスでは

This will solve the issue where the subsegments within a Promise chain are attached to wrong segments or nested instead of being siblings.

とあります。
「これにより、Promiseチェーン内のサブセグメントが誤ったセグメントに接続されたり、兄弟ではなく入れ子になったりする問題が解決されます。」 (DeepL翻訳) ということなので、実行しておくのが無難でしょう。
「細かい議論はこの PR を見てね」とも書いてあるので、気になる人は覗いてみて下さい。

Lambda を実行し、 Web コンソールで確認します。

隠しており見づらく申し訳ないのですが、通信先のドメイン名がサブセグメント名となるようです (例: https://example.comexample.com)。

まとめ

2つの方法を紹介しました。
結論としては、後者の 「captureHTTPsGlobal() を使う」 方法が手間が少なく、キャプチャ記述の書き忘れによる漏れも出ないためおすすめです。

以上、参考になれば幸いです。

本文紹介以外の参考資料

axiosによる呼び出しを AWS X-Rayで分析する