Lambdaのログ出力の違いを調べてみた【print()、logging】

AWS

これまでLambda関数でログ出力といえば、手軽な print() 関数を活用していました。しかし最近、様々な記事やコードに触れる中で、print() 関数はほとんど見かけず、logging モジュールを使ったログ出力が主流であることに気づきました。

そこで、PythonでLambda関数からログを出力する際に、それぞれの方法にはどのような違いがあり、どのようなメリット・デメリットがあるのかを調べてみました。

print() 関数と logging モジュール:その違い

PythonでLambda関数からログを出力する主な方法は2つあります。

  • print() 関数: Pythonの基本的な出力関数で、特別な設定なしにすぐに利用できます。
  • logging モジュール: より高度なログ出力機能を提供する標準ライブラリです。

それぞれの特徴を具体的に見ていきましょう。

print() 関数のメリット・デメリット

メリット

  • 手軽で簡単、Pythonの基本機能: 特別な設定が不要で、すぐにログを出力できます。Pythonの基本的な知識があれば容易に扱えます。
  • 簡単なデバッグに便利: 変数の中身をちょっと確認したいなど、一時的なデバッグ用途には手軽です。

デメリット

  • ログの重要度がすべて同じ(ログレベルの概念がない): 全ての出力が同じ重要度として扱われるため、本番環境で大量のログが出力されると、重要な情報が埋もれてしまう可能性があります。
  • 出力形式が固定、カスタマイズが難しい: タイムスタンプなどの情報を付加するには、手動でフォーマットする必要があります。複雑な形式での出力には向きません。

使用例

コード:

import json
import datetime

def lambda_handler(event, context):
    timestamp = datetime.datetime.now(datetime.timezone.utc).isoformat()
    print(f"{timestamp} - INFO - Received event: {event}")
    name = event.get('name', 'World')
    print(f"{timestamp} - INFO - Processing name: {name}")
    try:
        age_str = event.get('age', '0')
        age = int(age_str)
        result = 10 / age
        print(f"{timestamp} - INFO - Result: {result}")
    except ValueError as e:
        print(f"{timestamp} - WARNING - Invalid age provided: {age_str}. Setting age to 1.")
        age = 1
    except ZeroDivisionError as e:
        print(f"{timestamp} - ERROR - Division by zero error: division by zero")
        return {
            'statusCode': 500,
            'body': 'Internal Server Error'
        }
    print(f"{timestamp} - INFO - Processed successfully for age: {age}")
    return {
        'statusCode': 200,
        'body': f'Hello, {name} with age {age}!'
    }

実行結果:

Status: Succeeded
Test Event Name: test1

Response:
{
  "statusCode": 500,
  "body": "Internal Server Error"
}

Function Logs:
START RequestId: 92ae28b7-8f51-4773-8c6d-0ba6a0bbbb81 Version: $LATEST
2025-04-19T23:35:11.573518+00:00 - INFO - Received event: {'key1': 'value1'}
2025-04-19T23:35:11.573518+00:00 - INFO - Processing name: World
2025-04-19T23:35:11.573518+00:00 - ERROR - Division by zero error: division by zero
END RequestId: 92ae28b7-8f51-4773-8c6d-0ba6a0bbbb81
REPORT RequestId: 92ae28b7-8f51-4773-8c6d-0ba6a0bbbb81	Duration: 2.21 ms	Billed Duration: 3 ms	Memory Size: 128 MB	Max Memory Used: 32 MB	Init Duration: 89.03 ms

Request ID: 92ae28b7-8f51-4773-8c6d-0ba6a0bbbb81

logging モジュールのメリット・デメリット

メリット

  • ログレベルの設定が可能: DEBUG, INFO, WARNING, ERROR, CRITICAL といったログレベルを設定でき、出力するログの重要度を制御できます。本番環境では不要なログを抑制し、重要な情報に焦点を当てられます。
  • 出力形式のカスタマイズが可能: Formatterを設定することで、タイムスタンプ、ログレベル、関数名、行番号など、様々な情報をログに含めることができます。

デメリット

  • 初期設定が必要: import logging だけでなく、basicConfigなどの基本的な設定を行う必要があります。
  • print() 関数より、学習コストがやや高い: ログレベルやFormatterといった概念を理解する必要があります。

使用例

コード:

import logging

# ロギングの基本的な設定
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def lambda_handler(event, context):
    name = event.get('name', 'World')
    logging.debug(f"Received event: {event}")  # デバッグレベルのログ
    logging.info(f"Processing request for: {name}") # 情報レベルのログ
    try:
        result = 10 / int(event.get('age', 0))
        logging.info(f"Result: {result}")
    except ValueError as e:
        logging.warning(f"Invalid age provided: {event.get('age')}. Setting age to 1.") # 警告レベルのログ
        age = 1
    except ZeroDivisionError as e:
        logging.error(f"Division by zero error: {e}") # エラーレベルのログ
        return {
            'statusCode': 500,
            'body': 'Internal Server Error'
        }
    logging.info(f"Processed successfully for age: {age}")
    return {
        'statusCode': 200,
        'body': f'Hello, {name} with age {age}!'
    }
    

実行結果:

Status: Succeeded
Test Event Name: test1

Response:
{
  "statusCode": 500,
  "body": "Internal Server Error"
}

Function Logs:
START RequestId: 90be4456-6a36-4efb-a7f5-53b54080211c Version: $LATEST
[ERROR]	2025-04-19T23:31:13.987Z	90be4456-6a36-4efb-a7f5-53b54080211c	Division by zero error: division by zero
END RequestId: 90be4456-6a36-4efb-a7f5-53b54080211c
REPORT RequestId: 90be4456-6a36-4efb-a7f5-53b54080211c	Duration: 1.80 ms	Billed Duration: 2 ms	Memory Size: 128 MB	Max Memory Used: 32 MB

Request ID: 90be4456-6a36-4efb-a7f5-53b54080211c

比較してみて感じたこと

今回、コードはGeminiに同じ動きをする print() 関数版と logging モジュール版をそれぞれ生成してもらいました。ぱっと見の印象では、print() 関数のほうがシンプルであるはずなのに、logging モジュールと同じレベルでログを出力しようとすると、手動でタイムスタンプやログレベルを付与する必要があるため、コードが煩雑になるように感じました。

正直なところ、まだ logging モジュールを完全に理解しきれていないため、このような感想しか出てきませんが、より詳細で意味のあるログ(特にエラーログ)を出力しようとした場合、logging モジュールの方が明らかに便利になるだろうと理解できました。これからは logging モジュールの使い方を習得し、積極的に利用していきたいと思います。

コメント

タイトルとURLをコピーしました