概要
前回はローカルでMongoを動かした。
今回はローカルで実行しているlambdaにデバッグ実行を試してみる。
ソースコード
環境
- Windows 10
- aws-cdk 1.95.1
- SAM CLI. 1.20.0
- VSCode 1.54.3
やること
- attachしてデバッグ実行
- launch.jsonの自動生成
フォルダ階層
- .vscode
- launch.json # VSCodeのデバッガ設定
- cdk # cdk用の設定フォルダ
- lib
- sample-stack.ts # デバッグ用の設定
- cdk.out
- assets.hoge
- index.js
- index.map.js
- template.yaml
- cdk.json
- package.json
- src
- handlers
- tsconfig.json
準備
ソースマップ
デバッグ実行でTypescriptとバンドルされたjsを紐づけるためのソースマップが必要。
const echoFunction = new NodejsFunction(this, 'echo', {
runtime: lambda.Runtime.NODEJS_14_X,
entry: `${entryHandlerDir}/get-echo.ts`,
functionName: 'get-echo',
handler: 'echoHandler',
+ bundling: { sourceMap: true },
});
new NodejsFunction(this, 'test', {
runtime: lambda.Runtime.NODEJS_14_X,
entry: `${entryHandlerDir}/test.ts`,
functionName: 'kotatest',
bundling: {
+ sourceMap: true,
externalModules: [
'aws-sdk', // Use the 'aws-sdk' available in the Lambda runtime
'date-fns', // Layrerに入れておきたいモジュール
],
define: { // Replace strings during build time
'process.env.API_KEY': JSON.stringify(`\\"${'xxx-xxx'}\\"`), // バグってそう.エスケープしないとInvalid define valueのエラー
},
},
layers: [nodeModulesLayer],
});
デバッガ設定
dockerのコンテナ上のソースと、ローカルに出力したソースマップの紐づけがデバッガに必要。
cdk synth > cdk.out/template.yaml
コマンドでソースを出力した後、紐づけの設定を行う。
以下はAPI用に一つ、invokeで呼び出すように一つ、合計2つの設定をしている状態
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "api echo ",
"port": 5858,
"address": "localhost",
"localRoot": "${workspaceFolder}/cdk/cdk.out/asset.eb94322643dddde1077f91e199ef7ddd442fb0064b028e1c9196f8b5130ec4e1",
"remoteRoot": "/var/task",
"protocol": "inspector",
},
{
"type": "node",
"request": "attach",
"name": "invoke test ",
"port": 5858,
"address": "localhost",
"localRoot": "${workspaceFolder}/cdk/cdk.out/asset.ff62102abf256369f4067436df86939e80532114601e39c34faa7edd1c438306",
"remoteRoot": "/var/task",
"protocol": "inspector",
}
]
}
デバッグ実行設定
-d ポート番号
オプションでデバッグ実行ができるので、スクリプトにしておく。
"localinvoke": "cd cdk.out && sam.cmd local invoke testAF53AC38 --no-event",
+ "debuginvoke": "cd cdk.out && sam.cmd local invoke -d 5858 testAF53AC38 --no-event",
"dev-serve": "cd cdk.out && sam.cmd local start-api -n ../env.json",
+ "debug-serve": "cd cdk.out && sam.cmd local start-api -d 5858 -n ../env.json"
ブレークポイント設定
確認したい箇所にブレークポイントを入れておく。
実行
invoke
npm run debuginvoke
で起動。
サイドバーのデバッグから、「invoke test」を選択。実行ボタン|>を押す。
ブレークポイントで止めることができた。 ここからステップ実行可能となる。
API
APIも同様。
npm debug-serve
。
デバッグしたいAPI(今回の場合はecho)を叩くと、For help, see: https://nodejs.org/en/docs/inspector
が表示されるので、「api echo」を選択。実行ボタン|>を押す。
アタッチされるとjsファイルでいったん止まる。続行する。
launch.jsonの自動作成
毎回、assetsのパスを確認するのは大変。
なので、バンドルと同時にlaunch.jsonを更新するようにした。
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { SampleStack } from '../lib/sample-stack';
import { bundleNpm } from '../lib/process/setup';
+ import { createLaunch } from '../lib/process/after';
// pre-process
bundleNpm();
// // create app
const app = new cdk.App();
const stack = new SampleStack(app, 'SampleStack2021');
+ createLaunch(stack)
- 愚直にtemplate.jsonから得られる情報をlaunch.jsonに書き込んでいる。
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs';
import * as fs from 'fs';
import * as path from 'path'
import { SampleStack } from '../sample-stack';
const launchFilePath = path.resolve(process.cwd(), '..', '.vscode', 'launch.json');
const assetIndex = 1;
const debugPort = 5858;
const configuration = {
"type": "node",
"request": "attach",
"name": "api echo ",
"port": debugPort,
"address": "localhost",
"localRoot": "${workspaceFolder}/cdk/cdk.out/ASSET_PATH",
"remoteRoot": "/var/task",
"protocol": "inspector",
}
export const createLaunch = async (stack: SampleStack) => {
const confs = stack.node.children.filter((item): item is NodejsFunction => item instanceof NodejsFunction)
.map((item: NodejsFunction) => ({
...configuration,
// @ts-ignore
name: `lambda ${item.physicalName} ${item.permissionsNode.uniqueId.replace(stack.stackName, '')}`, // uniqueIdはdeprecatedだが現在templateに出力されているので使う
// @ts-ignore
localRoot: configuration.localRoot.replace('ASSET_PATH', item.node.children[assetIndex].assetPath)
}))
const launch = {
"version": "0.2.0",
"configurations": confs
}
fs.writeFileSync(launchFilePath, JSON.stringify(launch))
}
作成されたlaunch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "lambda kotahello hello25C2F4BF",
"port": 5858,
"address": "localhost",
"localRoot": "${workspaceFolder}/cdk/cdk.out/asset.005313309c1a04502205e8341511c4f2d861dd96521f4d707c2b9b49b9c9cd81",
"remoteRoot": "/var/task",
"protocol": "inspector"
},
{
"type": "node",
"request": "attach",
"name": "lambda kotatest test2F9CF384",
"port": 5858,
"address": "localhost",
"localRoot": "${workspaceFolder}/cdk/cdk.out/asset.ff62102abf256369f4067436df86939e80532114601e39c34faa7edd1c438306",
"remoteRoot": "/var/task",
"protocol": "inspector"
},
{
"type": "node",
"request": "attach",
"name": "lambda get-echo echoD896FE0C",
"port": 5858,
"address": "localhost",
"localRoot": "${workspaceFolder}/cdk/cdk.out/asset.eb94322643dddde1077f91e199ef7ddd442fb0064b028e1c9196f8b5130ec4e1",
"remoteRoot": "/var/task",
"protocol": "inspector"
},
{
"type": "node",
"request": "attach",
"name": "lambda get-user getUserD5985AD3",
"port": 5858,
"address": "localhost",
"localRoot": "${workspaceFolder}/cdk/cdk.out/asset.d8b09a14625edbffe651afd290e80d8e761c109eb9f36bf78118877a0887ef4c",
"remoteRoot": "/var/task",
"protocol": "inspector"
},
{
"type": "node",
"request": "attach",
"name": "lambda post-user postUser43EF2B65",
"port": 5858,
"address": "localhost",
"localRoot": "${workspaceFolder}/cdk/cdk.out/asset.b7ec805956444f4fc600442e05c20ccdb9d8eb8d075babcb68381db04d468b3e",
"remoteRoot": "/var/task",
"protocol": "inspector"
}
]
}
リファクタリング
uniqueIdがdeprecateのため、jsonから必要な情報を読み込むようにリファクタリング
import * as fs from 'fs';
import * as path from 'path'
import { SampleStack } from '../sample-stack';
const launchFilePath = path.resolve(process.cwd(), '..', '.vscode', 'launch.json');
const assetPlaceHolder = 'ASSET_PATH';
const debugPort = 5858;
const configuration = {
"type": "node",
"request": "attach",
"name": "",
"port": debugPort,
"address": "localhost",
"localRoot": "${workspaceFolder}/cdk/cdk.out/" + assetPlaceHolder,
"remoteRoot": "/var/task",
"protocol": "inspector",
}
type Config = typeof configuration
export const createLaunch = async (stack: SampleStack) => {
const buf = fs.readFileSync(path.join(process.cwd(), 'cdk.out', stack.templateFile))
const template = JSON.parse(buf.toString());
const resources = template.Resources;
const confs: Config[] = [];
for (const prop in resources) {
if (resources[prop].Type !== 'AWS::Lambda::Function') continue
confs.push({
...configuration,
name: `lambda ${prop}`,
localRoot: configuration.localRoot.replace(assetPlaceHolder, resources[prop].Metadata['aws:asset:path'])
})
}
const launch = {
"version": "0.2.0",
"configurations": confs
}
fs.writeFileSync(launchFilePath, JSON.stringify(launch))
}
エラー
前のローカル実行のゴミが残っていたりすると以下のエラーになることがある。
requests.exceptions.HTTPError: 500 Server Error: Internal Server Error for url: http+docker://localnpipe/v1.35/containers/9a7c6cc76fd0736fe91333a05070a69c4c92b6cd52343a438fd7e626af7d1a5e/start
以下のように、いったんdockerを消してやるとよい。
docker stop $(docker ps -q)
# 全てのdockerコンテナを削除
docker rm -f $(docker ps -aq)
参考
AWS - Step-through debugging Node.js functions locally
AWS - サーバーレスアプリケーションをデバッグするための設定オプション
Stack Over Flow - How to debug in VS Code a local AWS Lambda function with API Gateway written in TypeScript?
msdn - Visual Studio で JavaScript アプリまたは TypeScript アプリをデバッグする
AWS - sam local start-api
3step
設定なしでデバッグ実行
esbuild