はじめに
こんにちは。
最近re:Inventもあってか、AWSのアップデートがものすごいことになってますね。
そんな中、11/16(木) にLambdaのログ周りに関する下記アップデートが発表されました。
概要としては下記の通りです。
JSONでのログ出力をサポート
今まではテキスト形式のみでしたが、今回のアプデでJSON形式も標準でサポートしました。
これでメトリクスフィルタやAmazon Athenaなどでのフィルタ、解析がやりやすくなるのではないかと思います。
これまではJSON出力するにはアプリ側でコードを書くかAWS Powertoolsを使うしかなかったですが、Lambdaの設定だけでJSON出力が行えるようになりました。
ただAWS PowertoolsにはJSON出力以外にも便利な機能が多数ありますので、標準でJSONがサポートされたからといって不要になるなんてことは一切ありません。(by AWS Powetools大好きユーザー)
ログレベルの制御をサポート
INFO, WARN, ERROR などのログレベルの制御を、Lambdaの設定で扱えるようになりました。(開発は全部出力、本番はERRORのみ...など)
今まではアプリ側で制御用のコードを書く必要がありましたが、それが不要になりました。
出力先のCloudWatchロググループを指定可能
今までは /aws/lambda/<Lambda関数名>
で固定だった 出力先のCloudWatchロググループが、任意に指定可能になりました。
例えば類似処理を行っているLambdaのログを集約することで管理がしやすくなる(かも)といったことがあります。
その他、ロググループを集約することでCloudWatchアラームも1つのロググループにまとめることができ、コスト削減につながるかもしれません。
ただし「どのLambda関数のログなのか」を識別できるようにする情報(context.functionName
など)をログに加えないと分からなくなるので、その辺は注意が必要です。
なおこのロググループ集約に関しては、クラスメソッドの若槻さんがブログに記載されているので、詳しく知りたい方はそちらもご参照ください
試してみる
という訳で、実際に上記アップデートを試してみます。
なお、Lambdaのリソース作成&設定はAWS CDKで行い、API Gateway経由でそのLambdaを動かしてみます。
また、今回3つのLambda関数を作成しますが、その中身は全て共通で下記のソースとなっています。(重要なのはconsole.xxx
でのログ出力)
import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda"; export const handler = async (event: APIGatewayEvent, context: Context ): Promise<APIGatewayProxyResult> => { console.info(`This is ${context.functionName} INFO log`); console.info('This is info message'); console.warn('This is warning message'); console.error('This is error message'); const result: APIGatewayProxyResult = { statusCode: 200, headers: { contentType: 'application/json', }, body: JSON.stringify({ status: 'success', }), }; return result; };
AWS CDK
まずはAWS CDKの定義から。
AWS CDKの定義は下記の通りです。
肝心なのは applicationLogLevel
, systemLogLevel
, logFormat
, logGroup
の 4つです
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { NodejsFunction, LogLevel } from 'aws-cdk-lib/aws-lambda-nodejs'; import { RetentionDays, LogGroup } from 'aws-cdk-lib/aws-logs'; import { RestApi, LambdaIntegration } from 'aws-cdk-lib/aws-apigateway'; import path from 'path'; export class CdkStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const customLogGroup = new LogGroup(this, 'CustomLogGroup', { logGroupName: '/custom/jsconf/lambda/bun', removalPolicy: cdk.RemovalPolicy.DESTROY, }); const nodejsLambda = new NodejsFunction(this, 'JsConfNodeJsFunction', { entry: path.resolve(__dirname, '../../lambda', 'index.ts'), handler: 'handler', timeout: cdk.Duration.seconds(30), environment, functionName: 'JsConfNodeJsFunction', logRetention: RetentionDays.ONE_WEEK, // ログレベルはどちらもINFO、フォーマットはJSON applicationLogLevel: 'INFO', systemLogLevel: 'INFO', logFormat: 'JSON', }); const nodejsLambda2 = new NodejsFunction(this, 'JsConfNodeJsFunction2', { entry: path.resolve(__dirname, '../../lambda', 'index.ts'), handler: 'handler', timeout: cdk.Duration.seconds(30), environment, functionName: 'JsConfNodeJsFunction2', // ログレベルは未指定、フォーマットはText(下記注意点を参照) // logRetention: RetentionDays.ONE_WEEK, // applicationLogLevel: 'WARN', // systemLogLevel: 'WARN', logFormat: 'Text', logGroup: customLogGroup }); const nodejsLambda3 = new NodejsFunction(this, 'JsConfNodeJsFunction3', { entry: path.resolve(__dirname, '../../lambda', 'index.ts'), handler: 'handler', timeout: cdk.Duration.seconds(30), environment, functionName: 'JsConfNodeJsFunction3', // logRetention: RetentionDays.ONE_WEEK, // アプリログはERROR、システムログはDEBUG、フォーマットはJSON applicationLogLevel: 'ERROR', systemLogLevel: 'DEBUG', logFormat: 'JSON', logGroup: customLogGroup }); const restApi = new RestApi(this, 'JsConfRestApi', { restApiName: 'JsConfRestApi' }); const nodejs = restApi.root.addResource('nodejs'); nodejs.addMethod('GET', new LambdaIntegration(nodejsLambda)); const nodejs2 = restApi.root.addResource('nodejs2'); nodejs2.addMethod('GET', new LambdaIntegration(nodejsLambda2)); const nodejs3 = restApi.root.addResource('nodejs3'); nodejs3.addMethod('GET', new LambdaIntegration(nodejsLambda3)); } }
applicationLogLevel
, systemLogLevel
, logFormat
, logGroup
の 説明は以下となります。
項目 | 説明 | 設定可能な値 | デフォルト値 |
---|---|---|---|
applicationLogLevel | アプリログ(console.xxxなどで出力するログ)の出力レベル設定 | TRACE/DEBUG/INFO/WARN/ERROR/FATAL | INFO |
systemLogLevel | システムログ(INIT_START, REPORTなどLambda側で自動するログ)の出力レベル設定 | DEBUG/INFO/WARN | INFO |
logFormat | ログのフォーマット | Text/JSON | Text |
logGroup | ログ出力先のCloudWatchロググループ | ILogGroup(LogGroupクラスインスタンスなど) | /aws/lambda/<Lambda関数名> |
注意点としては下記の通りです。
logFormat
に Textを設定した場合、applicationLogLevel
およびsystemLogLevel
は指定できません。logGroup
とlogRetention
を両方指定することはできません。(どちらか一方のみ)applicationLogLevel
およびsystemLogLevel
は上のコードのように文字列で直指定してください。
実行結果
上記設定を行った各Lambdaを実行した結果のログは以下の通りです。
JsConfNodeJsFunction
- ログがJSONで出力されています。
- アプリログ(console.xxx)に関して、INFO/WARN/ERRORが出力されています。(INFO以上)
- システムログについて「platform.initStart」「platform.start」「platform.report」が出力されています。
- ロググループは
/aws/lambda/<Lambda関数名>
です。
JsConfNodeJsFunction2
- ログがテキストで出力されています。
- ロググループは(logGroupで指定した)
custom/jsconf/lambda/bun
です。
JsConfNodeJsFunction3
- ログがJSONで出力されています。
- アプリログ(console.xxx)に関して、ERRORのみが出力されています。
- システムログについて「platform.initStart」「platform.start」「platform.report」の他、「platform.initRuntimeDone」「platform.initReport」「platform.runtimeDone」も出力されています。
- ロググループは(logGroupで指定した)
custom/jsconf/lambda/bun
です。
いずれのLambda関数も、ちゃんとAWS CDKの定義通りに動作していますね。
まとめ
AWS Lambdaについて、ログ周りの機能が強化されたことで、CloudWatchとの連携(メトリクスフィルタ、アラームなど)やログレベル制御がやりやすくなりました。
開発だけではなく、運用・監視にも大いに役立ちそうな機能ですね。
では、今回はこの辺で