Node.js Lambda トレースとバンドラーの互換性
概要
Datadog のトレーシングライブラリ (dd-trace) は、条件付きインポートなどの問題により Webpack や esbuild といったバンドラーと互換性がないことが知られています。バンドラーが dd-trace をビルドできなくても、事前ビルド済みの Datadog Lambda レイヤーに含まれる dd-trace と datadog-lambda-js ライブラリをアプリケーションで引き続き利用できます。以下の手順に従ってください。
Webpack
- Node.js 用のインストール手順 に従い、Node.js 用の Datadog Lambda レイヤーが Lambda 関数に追加されていることを確認します。 
- datadog-lambda-jsと- dd-traceを- package.jsonから削除するか、または exclude ルール を設定して除外します。これにより、バンドラーはこれらを依存関係としてビルドしなくなります (Datadog Lambda レイヤーのランタイムですでに利用可能なため)。
 
- 依存関係を externals としてマークします。これにより、バンドラーは依存関係を出力バンドルに含めず、- node_modulesにパッケージします。
 - webpack.config.js - const nodeExternals = require("webpack-node-externals");
module.exports = {
  // Use webpack-node-externals to exclude all node dependencies.
  // You can manually set the externals too.
  externals: [nodeExternals()],
  module: {
    rules: [
      {
        // Provided by the Datadog Lambda layer and the Lambda Runtime.
        exclude: [
          // AWS SDK v3
          /^@aws-sdk.*/,
          // AWS SDK v2
          /aws-sdk/,
          /datadog-lambda-js/,
          /dd-trace/
        ],
      }
    ]
  },
}
 
- serverless-webpackプラグインを使用していて、- includeModulesオプションが- false以外に設定されている場合、このプラグインでは外部モジュールは自動的に- node_modules配下へパッケージ化されます。そのため、- datadog-lambda-jsと- dd-traceを必ず除外してください。- serverless-webpackを使っていない場合、または- serverless.ymlに- includeModulesオプションが存在しない場合は、この手順をスキップできます。
 - serverless.yml - custom:
  webpack:
    # You only need the following if you already have the includeModules option configured.
    includeModules:
      # ... your existing configuration for includeModules
      forceExclude:
        # @aws-sdk for the AWS SDK v3
        - @aws-sdk
        # aws-sdk for the AWS SDK v2
        - aws-sdk
        - datadog-lambda-js
        - dd-trace
    packagerOptions:
      scripts:
        # Optional, only needed when they are included as transitive dependencies 
        - rm -rf node_modules/datadog-lambda-js node_modules/dd-trace
 
- 依存関係の取り込みをより細かく制御したい場合は、- serverless-webpack設定に- webpack.config.jsを含めることも可能です。
 - custom:
  webpack:
    forceExclude:
      # @aws-sdk for the AWS SDK v3
      - @aws-sdk
      # aws-sdk for the AWS SDK v2
      - aws-sdk
      - datadog-lambda-js
      - dd-trace
    webpackConfig: 'webpack.config.js'
 
esbuild
- Node.js 用のインストール手順 に従い、Node.js 用の Datadog Lambda レイヤーが Lambda 関数に追加されていることを確認します。 
- datadog-lambda-jsと- dd-traceを- package.jsonおよびビルドプロセスから削除します。これらは Datadog Lambda レイヤーのランタイムにすでに含まれています。
 
- 依存関係を外部としてマークします。これは、出力バンドルからそれらを除外するようバンドラーに指示します。代わりに、それらは - node_modulesにパッケージされます。
 - esbuild.config.js - const esbuild = require('esbuild');
esbuild.build({
  // ... your existing esbuild configuration
  // Same effect as manually passing each dependency to `external`
  packages: 'external'
})
 
- serverless-esbuildプラグインを使用している場合、- esbuild-node-externalsで全ての依存関係を esbuild プラグインとして外部化することが可能です。自動的に外部モジュールを- node_modulesの下にパックします。
 - serverless.yml - custom:
  esbuild:
    exclude: 
      # @aws-sdk for the AWS SDK v3
      - @aws-sdk
      # aws-sdk for the AWS SDK v2
      - aws-sdk
      - datadog-lambda-js
      - dd-trace
    plugins: plugins.js
    # You can also set the specific dependencies to externalize instead of using `plugins`
    external: [...]
 
- // plugins.js
const { nodeExternalsPlugin } = require('esbuild-node-externals')
module.exports = [nodeExternalsPlugin()]
 
AWS CDK
NodeJsFunction コンストラクトを使って Node.js Lambda 関数をデプロイしているが、esbuild や TypeScript を使用していない場合でも、Datadog を利用してサーバーレスアプリケーションを監視できます。
- Node.js 用のインストール手順に従い、Node.js 用の Datadog Lambda レイヤーが Lambda 関数に追加されていることを確認します。 
- datadog-lambda-jsと- dd-traceを- package.jsonから削除し、ビルドプロセスからも除外します。これらは Datadog Lambda レイヤーのランタイムにすでに含まれています。
 
- CDK の - NodejsFunctionコンストラクトを使用します。- entryプロパティには Lambda 関数 の ハンドラーを含むファイルのパス、- depsLockFilePathには利用しているパッケージマネージャのロックファイルのパス、- bundling.commandHooks.beforeBundlingにはすべての依存関係をインストールするコマンドを設定してください。
 - lambdaFunction.ts - const nodeFunction = new NodejsFunction(this, "test", {
  runtime: Runtime.NODEJS_20_X,
  entry: './functions/consumer/index.js', // The Javascript file for your Lambda function handler
  handler: 'handler',
  depsLockFilePath: './package-lock.json', // The path to the lock file for your respective package manager (npm, yarn etc)
  bundling: {
    commandHooks: {
      beforeBundling(inputDir: string, outputDir: string) {
        return [
          `cd ${inputDir}`,
          'npm install', // Ensure all dependencies are installed before your Javascript file is zipped and deployed
        ]
      },
      beforeInstall() {
        return []
      },
      afterBundling() {
        return []
      }
    },
    externalModules: ['@aws-sdk/client-dynamodb'] // The AWS SDK is included as part of the Node.js Lambda runtime
  }
});
 
AWS CDK & esbuild
AWS CDK の NodeJsFunction コンストラクトは内部で esbuild を使用します。デフォルト設定は Datadog のトレーシングライブラリと互換性がありませんが、CDK ではデフォルト設定を上書きし、バンドリングおよび Datadog トレーシングライブラリをサポートするカスタム esbuild ファイルを指定できます。
- Node.js 用のインストール手順に従い、Node.js 用の Datadog Lambda レイヤーが Lambda 関数に追加されていることを確認します。 
- datadog-lambda-jsと- dd-traceを- package.jsonから削除し、ビルドプロセスからも除外します。これらは Datadog Lambda レイヤーのランタイムにすでに含まれています。
 
- 各 Lambda 関数ごとに - esbuildファイルを作成します。エントリーポイントを個別に指定する必要があるため、Lambda 関数ごとに別々の- esbuildファイルが必要です。ここで- entryPointと- outfileプロパティを設定する点に注意してください。たとえば、プロジェクトに- producerという 2 つ目の Lambda 関数がある場合、- entryPointは- ./functions/producer.ts、- outfileは- /out/producer/index.jsとなります。
 - buildConsumer.js - const ddPlugin = require('dd-trace/esbuild')
const esbuild = require('esbuild')
esbuild.build({
  entryPoints: ['./functions/consumer.ts'],
  outfile: 'out/consumer/index.js',
  plugins: [ddPlugin],
  // Other esbuild configuration
  external: [
    // esbuild cannot bundle native modules
    '@datadog/native-metrics',
    // required if you use profiling
    '@datadog/pprof',
    // required if you use Datadog security features
    '@datadog/native-appsec',
    '@datadog/native-iast-taint-tracking',
    '@datadog/native-iast-rewriter',
    // required if you encounter graphql errors during the build step
    'graphql/language/visitor',
    'graphql/language/printer',
    'graphql/utilities',
    '@aws-sdk/client-sqs'
    // if you are using the package, instead of the layer
    'datadog-lambda-js'
  ]
}).catch((err) => {
  console.error(err)
  process.exit(1)
})
 
- CDK で - NodeJsFunctionを定義する際、- Code.fromCustomCommandを使用してカスタム- esbuildファイルのパスと出力フォルダーを指定します。個別の Lambda 関数ごとに、ステップ 3 で定義した- esbuildファイルを指定してください。出力フォルダーは- esbuildファイル内の- outfileと同じフォルダーにする必要があります。
 - lambdaFunction.ts - // This path will likely be different for each individual Lambda function
const pathToBuildFile = '../functions/buildConsumer.js';
// Ensure the files for each Lambda function are generated into their own directory
const pathToOutputFolder = '../out/consumer/';
const code = Code.fromCustomCommand(
  pathToOutputFolder,
  ['node', pathToBuildFile],
);
const consumerLambdaFunction = new NodejsFunction(this, props.functionName, {
  runtime: Runtime.NODEJS_20_X,
  code: code,
  handler: 'index.handler',
  memorySize: 512,
  bundling: {
    platform: 'node',
    esbuildArgs: {
      "--bundle": "true"
    },
    target: 'node20'
  }
});
 
その他の参考資料