要約
- CloudWatch Logsに出力するときはJSON形式にすると便利
- Lambdaでは
LoggingConfigのLogFormat: JSONにするとシステムログはJSONにできる - アプリケーションのログはLambdaでは aws-lambda-powertoolsを使うと楽にいい感じのJSON形式にできる
JSON形式のメリット
CloudWatch Logsでのログ記録形式はJSONにしておくと良さそう、と最近知ったのでメモしておきます。
利点1:フィルタパターンが便利
CloudWatch Logs では特定のパターン(演算子)を使うと高度なフィルタリング(検索)ができます。
非構造化ログ(テキストログ)でも
- キーワード検索(例:
ERROR) - 完全一致検索(例:
"INTERNAL SERVER ERROR") - AND検索(例:
500 ERROR) - OR検索(例:
?500 ?ERROR)
といった検索や、正規表現での検索は可能です。
しかし、JSONだともっと高度な検索ができます。
JSONログでのフィルタパターン
$.フィールド名でJSONのプロパティを指定します。
例えば {"type": "platform.report"} というJSONがログにある場合、 { $.type = "platform.report" } というパターンで完全一致検索できます。
他には例えば次のようなことができます:
# 数値比較
{ $.record.metrics.initDurationMs >= 50 }
# ワイルドカード
{ $.sourceIPAddress != 123.123.* }
# 配列要素
{ $.arrayKey[0] = "value" }
# nullチェック
{ $.SomeObject IS NULL }
# フィールド存在チェック
{ $.SomeOtherObject NOT EXISTS }
# AND / OR の複合条件
{ ($.user.id = 1) && ($.users[0].email = "John.Doe@example.com") }
利点2:Logs Insightsが便利
ログをJSON形式で出力しておくと、Insightsが自動的にフィールドを展開してくれるため、フィールド単位での絞り込みや集計が簡単にできます。たとえばエラーログだけを抽出したり、特定のユーザーIDに絞って検索したりといった操作が、正規表現を書かずに済みます。
たとえば以下のようなJSONログを出力しているとします。
{"level": "ERROR", "message": "something went wrong", "user_id": "u-123", "duration_ms": 450}
Insights上では次のようなクエリが書けます。
fields @timestamp, level, message, user_id | filter level = "ERROR" | sort @timestamp desc | limit 20
テキストログだと@message like /ERROR/のように正規表現で検索する必要がありますが、JSON形式であればフィールド名で直接フィルタできます。parseコマンドで手動抽出する手間もなくなります。
LambdaでJSON形式のログを出す
システムログをJSON形式にする
AWS SAMやCloudFormationを使っている場合、template.yamlでLambdaのログフォーマットをJSON形式に設定できます。
AWS::Lambda::Function LoggingConfig - AWS CloudFormation
Resources: MyFunction: Type: AWS::Serverless::Function Properties: Handler: app.handler Runtime: python3.12 LoggingConfig: LogFormat: JSON
LoggingConfig で LogFormat: JSON を設定するとLambdaランタイムが出力するシステムログ(START/ENDなど)がJSON形式になります。
実行例
例えば print("hello world") を実行するだけのLambda関数があったとします。
def lambda_handler(event, context): print("hello world")
LogFormatがデフォルト値の場合 、ログは次のようになります。hello world以外はLambdaが自動的に出したシステムログです。
INIT_START Runtime Version: python:3.12.mainlinev2.v7 Runtime Version ARN: arn:aws:lambda:ap-northeast-1::runtime:e4ab553846c4e081013ff7d1d608a5358d5b956bb5b81c83c66d2a31da8f6244 START RequestId: 2f8280fc-957a-4882-bcb6-88a94f55b6d7 Version: $LATEST hello world END RequestId: 2f8280fc-957a-4882-bcb6-88a94f55b6d7 REPORT RequestId: 2f8280fc-957a-4882-bcb6-88a94f55b6d7 Duration: 2.18 ms Billed Duration: 90 ms Memory Size: 128 MB Max Memory Used: 36 MB Init Duration: 87.58 ms
LogFormat: JSON にすると、この システムログだけ がJSON形式になります。
{ "time": "2026-05-26T10:52:08.566Z", "type": "platform.initStart", "record": { "initializationType": "on-demand", "phase": "init", "runtimeVersion": "python:3.12.mainlinev2.v7", "runtimeVersionArn": "arn:aws:lambda:ap-northeast-1::runtime:e4ab553846c4e081013ff7d1d608a5358d5b956bb5b81c83c66d2a31da8f6244", "functionName": "json-log-test-StandardFunction-ilcZizfaz3Qz", "functionVersion": "$LATEST", "instanceId": "2026/05/26/[$LATEST]c3e0e327cbe5448d9336c44ee223f313", "instanceMaxMemory": 134217728 } } { "time": "2026-05-26T10:52:08.655Z", "type": "platform.start", "record": { "requestId": "8244b809-0b6a-472d-b414-f2176a798bda", "functionArn": "arn:aws:lambda:ap-northeast-1:878251971772:function:json-log-test-StandardFunction-ilcZizfaz3Qz", "version": "$LATEST" } } hello world { "time": "2026-05-26T10:52:08.664Z", "type": "platform.report", "record": { "requestId": "8244b809-0b6a-472d-b414-f2176a798bda", "metrics": { "durationMs": 2.202, "billedDurationMs": 88, "memorySizeMB": 128, "maxMemoryUsedMB": 36, "initDurationMs": 85.523 }, "status": "success" } }
そのため、ちゃんとアプリケーションのログもJSON形式にするためには、 標準出力するテキストをJSON形式にする必要があります。
アプリケーションのログをJSONにする
printでJSON形式にするには、json.dumpsを毎回呼ぶことになります
import json def lambda_handler(event, context): print(json.dumps({"level": "info", "message": "hello world"}))
こうなるとprintでは書きにくくなってきます。
そこでprintではなくログ出力用のパッケージを使う場合についてメモしておきます。
aws-lambda-powertools
Lambdaにおいては aws-lambda-powertoolsを使うのが一番楽そうでした。
次のように短く書くことができます。
from aws_lambda_powertools import Logger from aws_lambda_powertools.utilities.typing import LambdaContext logger = Logger() @logger.inject_lambda_context(log_event=False, clear_state=True) def lambda_handler(event: dict, context: LambdaContext): # info logger.info("hello world") # exception a, b = 1, 0 try: a / b except ZeroDivisionError: logger.exception("unhandled error")
infoなど通常のログはこのように出力されます。
{ "level": "INFO", "location": "lambda_handler:13", "message": "hello world", "timestamp": "2026-05-27 00:15:55,968+0000", "service": "service_undefined", "cold_start": true, "function_name": "json-log-test-PowerToolsFunction-Z9uVrmCdWemB", "function_memory_size": "128", "function_arn": "arn:aws:lambda:ap-northeast-1:878251971772:function:json-log-test-PowerToolsFunction-Z9uVrmCdWemB", "function_request_id": "fccd69f2-0642-4dea-9c35-54be43cc63ca", "xray_trace_id": "1-6a1637bb-3ada1cbd7e487706302ab048" }
exceptionは次のようにTracebackも含んだ形で、どこのstatementが問題だったのかも含めて出してくれます。
{ "level": "ERROR", "location": "lambda_handler:20", "message": "unhandled error", "timestamp": "2026-05-27 00:15:55,969+0000", "service": "service_undefined", "cold_start": true, "function_name": "json-log-test-PowerToolsFunction-Z9uVrmCdWemB", "function_memory_size": "128", "function_arn": "arn:aws:lambda:ap-northeast-1:878251971772:function:json-log-test-PowerToolsFunction-Z9uVrmCdWemB", "function_request_id": "fccd69f2-0642-4dea-9c35-54be43cc63ca", "exception": "Traceback (most recent call last):\n File \"/var/task/app.py\", line 18, in lambda_handler\n a / b\n ~~^~~\nZeroDivisionError: division by zero", "exception_name": "ZeroDivisionError", "stack_trace": { "type": "ZeroDivisionError", "value": "division by zero", "module": "builtins", "frames": [ { "file": "/var/task/app.py", "line": 18, "function": "lambda_handler", "statement": "a / b" } ] }, "xray_trace_id": "1-6a1637bb-3ada1cbd7e487706302ab048" }
loguru
次に楽に実装できそうなのは loguruでした。
JSON出力自体は logger.add(sys.stdout, serialize=True) だけで出せます。
しかしLambdaのrequestIdを入れたり、JSONに入れる項目を絞ったりなどカスタムしだすと設定が長くなってきます。
import sys from loguru import logger logger.remove() # デフォルト形式の stderr 出力を削除 logger.add(sys.stdout, serialize=True) # JSON 形式で標準出力にログを出力 def lambda_handler(event, context): request_id = event.get("requestContext", {}).get("requestId", {}) log = logger.bind(request_id=request_id) # リクエスト ID をログに含める log.debug("hello world")
出てくるログは
text:通常のログの結果record:詳細情報(「どのファイルの何行目で出たログなのか」など)
になります。bind()でLambdaのrequestIdを追加した場合、record.extraに入ります。
{ "text": "2026-05-26 23:10:31.361 | DEBUG | app:lambda_handler:12 - hello world\n", "record": { "elapsed": { "repr": "0:00:00.037514", "seconds": 0.037514 }, "exception": null, "extra": { "request_id": "3d114e44-6448-4875-9bed-775b8101a1e7" }, "file": { "name": "app.py", "path": "/var/task/app.py" }, "function": "lambda_handler", "level": { "icon": "🐞", "name": "DEBUG", "no": 10 }, "line": 12, "message": "hello world", "module": "app", "name": "app", "process": { "id": 2, "name": "MainProcess" }, "thread": { "id": 139867176759360, "name": "MainThread" }, "time": { "repr": "2026-05-26 23:10:31.361168+00:00", "timestamp": 1779837031.361168 } } }
Tracebackについて
loguruには例外のTracebackや、そのときに変数に入っていた具体的な値を詳しく教えてくれる機能があるのですが、そういった詳細なテキスト情報については全部textに入れられるようです。
# 例外を発生させてログに出力する例 a, b = 1, 0 try: a / b except ZeroDivisionError: log.exception("計算に失敗しました")
{ "text": "2026-05-26 23:10:31.361 | ERROR | app:lambda_handler:20 - 計算に失敗しました\nTraceback (most recent call last):\n\n File \"/var/runtime/bootstrap.py\", line 63, in <module>\n main()\n └ <function main at 0x7f355d33a5c0>\n\n File \"/var/runtime/bootstrap.py\", line 60, in main\n awslambdaricmain.main([os.environ[\"LAMBDA_TASK_ROOT\"], os.environ[\"_HANDLER\"]])\n │ │ │ │ │ └ environ({'AWS_LAMBDA_FUNCTION_VERSION': '$LATEST', 'AWS_EXECUTION_ENV': 'AWS_Lambda_python3.12', 'AWS_LAMBDA_METADATA_TOKEN':...\n │ │ │ │ └ <module 'os' (frozen)>\n │ │ │ └ environ({'AWS_LAMBDA_FUNCTION_VERSION': '$LATEST', 'AWS_EXECUTION_ENV': 'AWS_Lambda_python3.12', 'AWS_LAMBDA_METADATA_TOKEN':...\n │ │ └ <module 'os' (frozen)>\n │ └ <function main at 0x7f355d2f1f80>\n └ <module 'awslambdaric.__main__' from '/var/lang/lib/python3.12/site-packages/awslambdaric/__main__.py'>\n\n File \"/var/lang/lib/python3.12/site-packages/awslambdaric/__main__.py\", line 21, in main\n bootstrap.run(app_root, handler, lambda_runtime_api_addr)\n │ │ │ │ └ '169.254.100.1:9001'\n │ │ │ └ 'app.lambda_handler'\n │ │ └ '/var/task'\n │ └ <function run at 0x7f355d33a2a0>\n └ <module 'awslambdaric.bootstrap' from '/var/lang/lib/python3.12/site-packages/awslambdaric/bootstrap.py'>\n File \"/var/lang/lib/python3.12/site-packages/awslambdaric/bootstrap.py\", line 529, in run\n handle_event_request(\n └ <function handle_event_request at 0x7f355d338ea0>\n File \"/var/lang/lib/python3.12/site-packages/awslambdaric/bootstrap.py\", line 177, in handle_event_request\n response = request_handler(event, lambda_context)\n │ │ └ LambdaContext([aws_request_id=7fbc7072-1534-4944-88bf-25a631346e32,log_group_name=/aws/lambda/json-log-test-LoguruFunction-ew...\n │ └ {'resource': '/loguru', 'path': '/loguru/', 'httpMethod': 'GET', 'headers': {'Accept': 'text/html,application/xhtml+xml,appli...\n └ <function lambda_handler at 0x7f355d34fb00>\n\n> File \"/var/task/app.py\", line 18, in lambda_handler\n a / b\n │ └ 0\n └ 1\n\nZeroDivisionError: division by zero\n", "record": { "exception": { "type": "ZeroDivisionError", "value": "division by zero", "traceback": true }, ... } }
項目のカスタム
デフォルトではいろいろな情報がrecordに入っていますが、もっと必要最低限のものに絞りたい場合はsink関数をカスタムできます。
import json import sys from loguru import logger def json_sink(message): record = message.record log = { "text": str(message), "timestamp": record["time"].isoformat(), "level": record["level"].name, "logger": record["name"], "message": record["message"], "module": record["module"], "function": record["function"], "line": record["line"], **record["extra"], } if record["exception"] is not None: log["exception"] = str(record["exception"]) print(json.dumps(log, ensure_ascii=False), file=sys.stdout) logger.add(json_sink, level="INFO")
この設定のもとでlog.info("hello world")すると次のようになります。
{ "text": "2026-05-26 23:55:07.518 | INFO | app:lambda_handler:58 - hello world\n", "timestamp": "2026-05-26T23:55:07.518066+00:00", "level": "INFO", "logger": "app", "message": "hello world", "module": "app", "function": "lambda_handler", "line": 58, "request_id": "fd81b712-f1c4-451a-986d-c204665fc77e" }
















![[改訂第9版]LaTeX美文書作成入門 [改訂第9版]LaTeX美文書作成入門](https://m.media-amazon.com/images/I/414goKtaBjL._SL500_.jpg)
