Lambdaって、lambda_function.py以外にもファイル使うことありますよね。
そんな場合でもAWS Lambda Powertoolsを使うと簡単にログを取ってくれます。
が。
ちょっとこれどうなんだろな?
というところがあったので確認してみました。
目次
外部ファイルに定義したクラスのログを出す
とりあえずこんな感じで書きました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# 必要なモジュールをインポート from aws_lambda_powertools import Logger import inspect # aws_lambda_powertoolsのLogger apt_logger = Logger(service='Loggerテスト') def deco_exception(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: apt_logger.error(e) apt_logger.exception(e) return wrapper # デコレータで例外処理 @deco_exception class ChildLog: def __init__(self) -> None: apt_logger.info(f'{self.__class__.__name__}.{inspect.currentframe().f_code.co_name}実行') self.function01() self.function02() def function01(self): apt_logger.info(f'{self.__class__.__name__}.{inspect.currentframe().f_code.co_name}実行') def function02(self): apt_logger.info(f'{self.__class__.__name__}.{inspect.currentframe().f_code.co_name}実行') raise ValueError(f'{self.__class__.__name__}.{inspect.currentframe().f_code.co_name}で例外エラー') |
これをlambda_handler()の中でインスタンス化してログ出力を実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 必要なモジュールをインポート from aws_lambda_powertools import Logger from aws_lambda_powertools.utilities.typing import LambdaContext import inspect from ChildLog import ChildLog # aws_lambda_powertoolsのLogger apt_logger = Logger( service='Loggerテスト', use_rfc3339=True) # aws_lambda_powertoolsでLoggerを使うためのデコレータ @apt_logger.inject_lambda_context def lambda_handler(event, context: LambdaContext): apt_logger.info(f'{inspect.currentframe().f_code.co_name}実行') # インスタンス化 child = ChildLog() |
クラス側に同じservice名を指定
ドキュメントによれば、serviceに同じ名前を付ければロガーを使いまわせると。
ということで、両方のファイルに
1 |
service='Loggerテスト' |
を書いておきました。
デコレータはlambda_function.pyにだけ付けます。
これを実行すると…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# lambda_function.pyのログ { "level": "INFO", "location": "lambda_handler:15", "message": "lambda_handler実行", "timestamp": "2023-03-12 12:09:50,230+0000", "service": "Loggerテスト", "cold_start": true, "function_name": "hello_world", "function_memory_size": "128", "function_arn": "arn:aws:lambda:ap-northeast-1:475391661122:function:hello_world", "function_request_id": "96d6f0f4-0b24-439a-9670-c740194bab04", "xray_trace_id": "1-640dc10d-5f8eacb1078478970f29ce74" } # ChildLogのログ { "level": "INFO", "location": "__init__:21", "message": "ChildLog.__init__実行", "timestamp": "2023-03-12 12:09:50,230+0000", "service": "Loggerテスト", "cold_start": true, "function_name": "hello_world", "function_memory_size": "128", "function_arn": "arn:aws:lambda:ap-northeast-1:475391661122:function:hello_world", "function_request_id": "96d6f0f4-0b24-439a-9670-c740194bab04", "xray_trace_id": "1-640dc10d-5f8eacb1078478970f29ce74" } |
- service
- function_name
- function_arn
- function_request_id
- xray_trace_id
に同じものが設定されています。
function_request_idがLambdaのrequest IDになるので、こいつを引き継いでくれれば流れを追えますね。
serviceは環境変数に入れておけば共通化できるし簡単。
ただ…
なんでまたUTCになってんだよ?!
前回JSTにしたじゃんか。
ChildLog.pyはともかく、なんでlambda_function.py側もUTCなのよ。。。
-
-
AWS Lambda PowertoolsのtimestampをJSTにする
最近、仕事でAWS Lambdaを使ってます。 Python書くの楽しい。 プロジェクトのメイン言語はPHPだけど、PHPは書いててストレス溜まるからもうポイしたいw ということで、Lambdaのログ ...
続きを見る
ちなみにChildLogのインスタンス化をやめたらちゃんとJSTで出ました。
クラス側にもuse_rfc3339を指定する
serviceだけ指定してもダメみたいなので、class側にもlambda_function.pyと同じようにuse_rfc3339を設定。
1 2 3 |
apt_logger = Logger( service='Loggerテスト', use_rfc3339=True) |
これだとちゃんとJSTになりました。
serviceを指定したとしてもlambda_function.py(一番最初に作成したLogger)と同じ設定が引き継がれるわけじゃないみたいです。
例外はexception()で
デコレータで処理してた例外のログ。
error()とexception()の両方で出してみましたが、errorだとTracebackが出力されませんでした。
locationはLoggerの実行行になるので、直接try/exceptionで囲んでるならともかくデコレータ経由だと発生個所の特定ができない。。。
levelはどちらの場合でもERRORが設定されますし、素直にexception()を使えばいいんじゃないかなぁと。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
# error(e) { "level": "ERROR", "location": "wrapper:15", "message": "ChildLog.function02で例外エラー", "timestamp": "2023-03-12T22:08:24.814+09:00", "service": "Loggerテスト", "cold_start": true, "function_name": "hello_world", "function_memory_size": "128", "function_arn": "arn:aws:lambda:ap-northeast-1:475391661122:function:hello_world", "function_request_id": "08b4c107-2f4b-42e9-95d5-a54c7c077d6e", "xray_trace_id": "1-640dcec8-612eb39d4e24bfb91a55795c" } # exception(e) { "level": "ERROR", "location": "wrapper:16", "message": "ChildLog.function02で例外エラー", "timestamp": "2023-03-12T22:08:24.814+09:00", "service": "Loggerテスト", "cold_start": true, "function_name": "hello_world", "function_memory_size": "128", "function_arn": "arn:aws:lambda:ap-northeast-1:475391661122:function:hello_world", "function_request_id": "08b4c107-2f4b-42e9-95d5-a54c7c077d6e", "exception": "Traceback (most recent call last):\n File \"/var/task/ChildLog.py\", line 13, in wrapper\n return func(*args, **kwargs)\n File \"/var/task/ChildLog.py\", line 25, in __init__\n self.function02()\n File \"/var/task/ChildLog.py\", line 32, in function02\n raise ValueError(f'{self.__class__.__name__}.{inspect.currentframe().f_code.co_name}で例外エラー')\nValueError: ChildLog.function02で例外エラー", "exception_name": "ValueError", "xray_trace_id": "1-640dcec8-612eb39d4e24bfb91a55795c" } |
もう1つ階層を追加しても問題なし
ChildLogをコピーしてGrandChildLogとして新ファイルに。
ChildLog内でGrandChildLogを呼び出すようにしました。
特に問題なさそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# lambda_function.py { "level": "INFO", "location": "lambda_handler:15", "message": "lambda_handler実行", "timestamp": "2023-03-12T22:38:21.209+09:00", "service": "Loggerテスト", "cold_start": true, "function_name": "hello_world", "function_memory_size": "128", "function_arn": "arn:aws:lambda:ap-northeast-1:475391661122:function:hello_world", "function_request_id": "40e02d91-82fd-481c-bbbe-693d8e66a73e", "xray_trace_id": "1-640dd5cc-738186544d36def46d9627a7" } # ChildLog.py { "level": "INFO", "location": "__init__:24", "message": "ChildLog.__init__実行", "timestamp": "2023-03-12T22:38:21.209+09:00", "service": "Loggerテスト", "cold_start": true, "function_name": "hello_world", "function_memory_size": "128", "function_arn": "arn:aws:lambda:ap-northeast-1:475391661122:function:hello_world", "function_request_id": "40e02d91-82fd-481c-bbbe-693d8e66a73e", "xray_trace_id": "1-640dd5cc-738186544d36def46d9627a7" } # GrandChildLog.py { "level": "INFO", "location": "__init__:23", "message": "GrandChildLog.__init__実行", "timestamp": "2023-03-12T22:38:21.210+09:00", "service": "Loggerテスト", "cold_start": true, "function_name": "hello_world", "function_memory_size": "128", "function_arn": "arn:aws:lambda:ap-northeast-1:475391661122:function:hello_world", "function_request_id": "40e02d91-82fd-481c-bbbe-693d8e66a73e", "xray_trace_id": "1-640dd5cc-738186544d36def46d9627a7" } |
Logger(child=True)じゃダメなの?
もう1つ、child=Trueというパラメータを使う方法があります。
ドキュメントによれば
Tip: Prefer Logger Reuse feature over inheritance unless strictly necessary, see caveats.
(ヒント 厳密に必要でない限り、継承よりもロガー再利用機能を優先してください(注意事項を参照)。)
とあり、推奨はされていないみたいですが。
途中から標準モジュールのloggingになった?
すべてで以下を設定している場合は
1 2 3 |
apt_logger = Logger( service='Loggerテスト', use_rfc3339=True) |
こう。
3つのファイルでちゃんと出てます。
ChildLogとGrandChildLogのLoggerをchild=Trueにしました。
他はそのままです。
1 |
apt_logger = Logger(child=True) |
これを実行すると…
- ChildLogとGrandChildLogのinfo()が出なくなった
- というか途中から標準モジュールのloggingに変わった?
という謎動作に。
なぜこうなったのかは深く調べてないですが、まぁ、
素直にservice統一でいいんじゃないか
って。
推奨もされてないしね。
ただこれで動いている記事も見かけるので、今回の実装には合わないってことですかね。
コード最終形
こんな感じ。
GrandChildLogはChildLogと中身一緒なので省略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# 必要なモジュールをインポート from aws_lambda_powertools import Logger from aws_lambda_powertools.utilities.typing import LambdaContext import inspect from ChildLog import ChildLog # aws_lambda_powertoolsのLogger apt_logger = Logger( service='Loggerテスト', use_rfc3339=True) # aws_lambda_powertoolsでLoggerを使うためのデコレータ @apt_logger.inject_lambda_context def lambda_handler(event, context: LambdaContext): apt_logger.info(f'{inspect.currentframe().f_code.co_name}実行') # インスタンス化 child = ChildLog() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# 必要なモジュールをインポート from aws_lambda_powertools import Logger import inspect from GrandChildLog import GrandChildLog # aws_lambda_powertoolsのLogger apt_logger = Logger( service='Loggerテスト', use_rfc3339=True) def deco_exception(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: apt_logger.exception(e) return wrapper # デコレータで例外処理 @deco_exception class ChildLog: def __init__(self) -> None: apt_logger.info(f'{self.__class__.__name__}.{inspect.currentframe().f_code.co_name}実行') self.function01() self.function02() grand_child = GrandChildLog() def function01(self): apt_logger.info(f'{self.__class__.__name__}.{inspect.currentframe().f_code.co_name}実行') def function02(self): apt_logger.info(f'{self.__class__.__name__}.{inspect.currentframe().f_code.co_name}実行') # raise ValueError(f'{self.__class__.__name__}.{inspect.currentframe().f_code.co_name}で例外エラー') |
継承なしであればこれで問題なさそう。
ただGrandChildLogにChildLogを継承させて…としようとしたらエラー出たので確認中。
自分の実装が間違ってるだけならいいんだけどなー。
Logクラス作りたいってなってもうまく動くんだろうか…。
関連記事
-
-
AWS Lambda PowertoolsのtimestampをJSTにする
最近、仕事でAWS Lambdaを使ってます。 Python書くの楽しい。 プロジェクトのメイン言語はPHPだけど、PHPは書いててストレス溜まるからもうポイしたいw ということで、Lambdaのログ ...
続きを見る
-
-
AWS Lambda Powertools Loggerのlocationをformatする
こいつをやってて。 locationってformatできないのかなぁ。 毎回Messageにクラス名やら関数名やら書くのは面倒だぞ。。。 と思っていたらできました。 ドキュメントには(たぶん)書いてな ...
続きを見る