これまで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 モジュールの使い方を習得し、積極的に利用していきたいと思います。
コメント