Slide 1

Slide 1 text

#pyconjp_3 Loggingモジュールではじめる ログ出力入門 PyCon JP 2021 2021-10-16

Slide 2

Slide 2 text

#pyconjp_3 堤 利史 / Toshifumi Tsutsumi GMO Pepabo, inc. Twitter: @tosh2230 #風来のシレン #囲碁 #pyconjp_3 2

Slide 3

Slide 3 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 4. まとめ 3

Slide 4

Slide 4 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 4. まとめ 4

Slide 5

Slide 5 text

#pyconjp_3 ゴール - 利用するかどうかの判断基準を知る - 主な構成要素を知る - ログ出力方法と実装例を知る - ソースコードは GitHub にあります - https://github.com/tosh223/pycon-jp-2021 5

Slide 6

Slide 6 text

#pyconjp_3 スコープ - Python 3.9 アプリケーション・ライブラリを対象とします - ログを出力する話をします - ログを受け取ったあとの話はほとんどしない予定です 出力 >> [収集, 蓄積, 加工] >> 分析 6 このあたり

Slide 7

Slide 7 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要 2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 7

Slide 8

Slide 8 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要 2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 8

Slide 9

Slide 9 text

#pyconjp_3 - Python に標準で搭載されている - アプリケーションやライブラリで発生した イベントを記録するための道具 - Five Ws (who, what, when, where, why) Logging モジュールとは 9

Slide 10

Slide 10 text

#pyconjp_3 - 処理が (開始|終了) した - 処理の待ち合わせに成功した - 注意すべき条件分岐に入った - 期待した状態になっていない - エラーになった イベント例 10

Slide 11

Slide 11 text

#pyconjp_3 >>> from logging import getLogger >>> logger = getLogger(__name__) >>> logger.error('This is an error!') This is an error! 11 (print でよいのでは...?) シンプルに出力してみる

Slide 12

Slide 12 text

#pyconjp_3 - プログラムとログのライフタイムが短い - 局所的・バッチ的なテキスト出力 - 目的がイベントの記録以外 - デバッグ - ちょっとした挙動の確認 print でも良いケース 12

Slide 13

Slide 13 text

#pyconjp_3 - イベント時刻・発生箇所の記録 - ログレベルの指定 - 出力フォーマットの統一 - 柔軟な出力設定 - スレッドセーフ Logging モジュールの便利ポイント 13

Slide 14

Slide 14 text

#pyconjp_3 標準ライブラリモジュールとしてログ記録 API が提供される利点 は、すべての Python モジュールがログ記録に参加できることで あり、これによってあなたが書くアプリケーションのログにサー ドパーティーのモジュールが出力するメッセージを含ませること ができます。 最大のメリット 14 https://docs.python.org/ja/3/library/logging.html

Slide 15

Slide 15 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要 2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 15

Slide 16

Slide 16 text

#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 16 propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() 主な要素は 6つ

Slide 17

Slide 17 text

#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 17 propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ① Logger

Slide 18

Slide 18 text

#pyconjp_3 - ログを出力する主体 - メッセージを LogRecord に変換する - Handler を使って、LogRecord を指定した書式の文字列に変 換して出力する - propagate という仕組みがある (後述) ① Logger 18

Slide 19

Slide 19 text

#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 19 propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ② Level

Slide 20

Slide 20 text

#pyconjp_3 - Logger や Handler に設定する - ログメッセージ出力時にイベントの重要度に応じて指定する - 以下の条件が True のときにログが出力される (Message level) >= (Logger level) and (Message level) >= (Handler level) ② Level 20

Slide 21

Slide 21 text

#pyconjp_3 21 番号 名称 イベントの目安 0 NOTSET 上位ロガーに依存する。NOTSET 以外のレベルをもつ上位ロガーを 見つけるか、ルートロガーに到達するまで辿っていく。 10 DEBUG おもに問題を診断するときにのみ関心があるような、詳細な情報。 20 INFO 想定された通りのことが起こったことの確認。 30 WARNING 想定外のことが起こった、または問題が近く起こりそうである (例 えば、'disk space low') ことの表示。 40 ERROR より重大な問題により、ソフトウェアがある機能を実行できないこ と。 50 CRITICAL プログラム自体が実行を続けられないことを表す、重大なエラー。 https://docs.python.org/ja/3/howto/logging.html

Slide 22

Slide 22 text

#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 22 propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ③ LogRecord

Slide 23

Slide 23 text

#pyconjp_3 - Logger に渡されたメッセージを変換したオブジェクト - メッセージだけでなく、イベントが発生したソースコードや 実行環境に関するコンテキストを保持する - ログに出力される時刻は、LogRecord が作成された時刻 ③ LogRecord 23 https://docs.python.org/ja/3/library/logging.html#logrecord-attributes

Slide 24

Slide 24 text

#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 24 propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ④ Filter

Slide 25

Slide 25 text

#pyconjp_3 - 出力対象 Level と判定された LogRecord が対象 - 設定対象によってフィルタリングのタイミングが異なる - Logger : LogRecord が できた直後 - Handler : Handler Level 判定の後 ④ Filter 25

Slide 26

Slide 26 text

#pyconjp_3 26 from logging import Filter class CredentialsFilter(Filter): # Filter クラスを継承 def __init__(self): pass def filter(self, record) -> bool: # filter() の返り値で出力判定 return self.__check_message(record.getMessage()) @staticmethod def __check_message(message) -> bool: return not message.startswith('Credentials') # 除外するログを False にする 👉

Slide 27

Slide 27 text

#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 27 propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ⑤ Handler

Slide 28

Slide 28 text

#pyconjp_3 - LogRecord を特定の方法・書式で出力するための道具 - 多様な Handler が実装されている - StreamHandler: ストリームへの出力 - FileHandler: ファイルへの出力 - ひとつの Logger に複数の Handler を設定できる ⑤ Handler 28

Slide 29

Slide 29 text

#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 29 propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ⑥ Formatter

Slide 30

Slide 30 text

#pyconjp_3 - LogRecord を任意の文字列・日付書式に変更する - タイムゾーンは Formatter.converter で設定 - デフォルトは time.localtime - UTC に変更するには time.gmtime を設定 - メッセージフォーマットの Style は 3種類ある(次ページ) ⑥ Formatter 30

Slide 31

Slide 31 text

#pyconjp_3 - ‘%’: %()s 形式の置換文字列 - dict key で指定できるのは LogRecord 属性 - ‘{’: str.format() 互換 - ‘$’: string.Template.substitute() 互換 Message format style 31 https://docs.python.org/ja/3/howto/logging.html#formatters

Slide 32

Slide 32 text

#pyconjp_3 32 >>> from logging import Formatter, StreamHandler, getLogger >>> fmt = '%(asctime)s.%(msecs)03d %(filename)s:%(funcName)s:%(lineno)d [%(levelname)s]%(message)s' >>> datefmt = '%Y-%m-%d %H:%M:%S' >>> formatter = Formatter(fmt=fmt, datefmt=datefmt, style='%') >>> sh = StreamHandler() >>> sh.setFormatter(formatter) # ハンドラにフォーマッタ設定 >>> logger = getLogger() # ロガーの参照取得 >>> logger.addHandler(sh) # ロガーにハンドラ設定 >>> logger.error('This is an error!') # ロガーで記録 2021-10-16 13:50:00.000 ::1 [ERROR]This is an error! 👉

Slide 33

Slide 33 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要 2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 33

Slide 34

Slide 34 text

#pyconjp_3 - Logger はモジュールごとにつくるとよい - logger = getLogger(__name__) とすると、モジュールの完 全修飾名と同じ名前の Logger をつくれる - Logger がわかる = イベントがどこで起きたのかわかる - モジュールやパッケージと同じく階層構造がある Logger の作成と命名 34 https://docs.python.org/ja/3/library/logging.html#logger-objects

Slide 35

Slide 35 text

#pyconjp_3 app 35 Logger の階層構造 root lib app.main lib.db lib.util

Slide 36

Slide 36 text

#pyconjp_3 36 root logger - すべての Logger の祖先 - logging モジュールが読み込まれたタイミングで自動生成 - logging.error など logging 直接記録は root logger を使用 - 記録時に Handler がなければ、logging.basicConfig (後述) が呼ばれる

Slide 37

Slide 37 text

#pyconjp_3 37 階層構造を活かしたイベントの伝播 root lib lib.util StreamHandler lib.util で メッセージを記録すると root まで伝播する (すべての下位 Logger が propagate=true である場合) Handler 未設定 FileHandler logger.error()

Slide 38

Slide 38 text

#pyconjp_3 38 root lib lib.util Handler 未設定 StreamHandler 下位 Logger に Handler がなくても、同じく上位に伝播する Handler 未設定 logger.error() 階層構造を活かしたイベントの伝播

Slide 39

Slide 39 text

#pyconjp_3 - エントリポイントで、root logger に Logger 全体に適用した い設定 (=デフォルト設定) を行う - getLogger(__name__) で作成した個別の Logger は “propagate=true” にする - 特定のLogger で挙動を変えたいときだけ、個別に設定する 39 設定は root logger にまとめると便利

Slide 40

Slide 40 text

#pyconjp_3 上位 Logger (root 含む) Handler 40 注意: Propagate 対象は Handler のみ 上位 Logger 自体に設定された Level, Filter は作用しない 下位 Logger Handler Formatter Filter Filter Level Level Log Record Log Record

Slide 41

Slide 41 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要 2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 41

Slide 42

Slide 42 text

#pyconjp_3 設定方法は4種類 42 いずれかを エントリポイント付近で一度だけ 行う 1. コードで直接指定 2. logging.basicConfig 3. logging.config.dictConfig 4. logging.config.fileConfig (非推奨のため今回は省略)

Slide 43

Slide 43 text

#pyconjp_3 sh_fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' default_fmt = Formatter(fmt=sh_fmt, style='%') # Formatter default_fmt.converter = gmtime # 時刻をUTC表示に統一 sh = StreamHandler() # Handler 作成 sh.setFormatter(default_fmt) # Formatter を設定 logger = getLogger() # ルートロガー参照を取得 logger.setLevel(DEBUG) # Level を設定 logger.addHandler(sh) # Handler を設定 方法1: コードで直接指定 43

Slide 44

Slide 44 text

#pyconjp_3 - root logger に対する基本設定ができる - デフォルトでは、root logger に何らかの Handler 設定がな されている場合は反映されない(引数 force で変更可) 方法2: logging.basicConfig 44 https://docs.python.org/ja/3/library/logging.html#logging.basicConfig

Slide 45

Slide 45 text

#pyconjp_3 - Dict から設定を読み込む - YAML や JSON で設定ファイルをつくることができる - デフォルトでは、呼び出す前に存在している非ルートロガー を無効化するので注意 (disable_existing_loggers=true) 方法3: logging.config.dictConfig 45

Slide 46

Slide 46 text

#pyconjp_3 version: 1 disable_existing_loggers: False root: level: WARNING handlers: [console] loggers: __main__: propagate: yes handlers: console: class: logging.StreamHandler level: DEBUG formatter: default stream: ext://sys.stdout ... 46 import os from logging import config from yaml import safe_load class DictConfig(): def __init__(self, config_file=None): self.__config_file = config_file def set(self): current_dir = os.path.dirname(__file__) with open(current_dir + '/' + self.__config_file) as file: _, ext = os.path.splitext(self.__config_file) if ext in ['.yaml', '.yml']: conf = safe_load(file) config.dictConfig(conf) 👉 Load a yaml file

Slide 47

Slide 47 text

#pyconjp_3 from logging import getLogger logger = getLogger(__name__) # ルートロガーに設定済なのでこれだけ logger.debug('Debug') logger.info('Info') logger.warning('Warning') logger.error(exception) # logger.exception(e, exc_info=False) と同じ logger.exception(exception) # logger.error(e, exc_info=True) と同じ 設定したら Logger を作って出力 47

Slide 48

Slide 48 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要 2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 48

Slide 49

Slide 49 text

#pyconjp_3 - Handler/Level/Formatter/Filter の選択権は 呼び出し側のアプリケーションにある - ライブラリが決めるのは タイミング と メッセージ内容 ライブラリでは詳細設定をしない 49

Slide 50

Slide 50 text

#pyconjp_3 - ライブラリのトップレベル Logger に NullHandler のみを設 定するのがお作法 - NullHandler は、何もせず上位 Logger に伝播させる - この仕組みにより 3rd party ライブラリもログ出力対象に加 えることができる NullHandler を設定 50 https://docs.python.org/ja/3/howto/logging.html#configuring-logging-for-a-library

Slide 51

Slide 51 text

#pyconjp_3 # 設定 from logging import getLogger, NullHandler logger = getLogger(__name__) logger.addHandler(NullHandler()) ライブラリでの設定・出力例 51 # 出力 logger.warning('Warning') 👉

Slide 52

Slide 52 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 3.1. 実行環境とその背景 3.2. Amazon Web Services 3.3. Google Cloud Platform 4. まとめ 52

Slide 53

Slide 53 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 3.1. 実行環境とその背景 3.2. Amazon Web Services 3.3. Google Cloud Platform 4. まとめ 53

Slide 54

Slide 54 text

#pyconjp_3 - パブリッククラウドサービスでの PaaS / FaaS では、あらか じめロギング設定がなされている - Python の Logging モジュールを応用している - 凝ったことをするには Logging モジュールの知識が不可欠 実行環境は用意されている 54

Slide 55

Slide 55 text

#pyconjp_3 - モダンなアプリケーションとは、を述べたもの - ステートレス かつ シェアードナッシング を志向 - Factor Ⅸ: “ログをイベントストリームとして扱う” - ログはローカルに出さず 標準出力に書き出して収集 前提: The Twelve-Factor App 55 https://12factor.net/ja/logs

Slide 56

Slide 56 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 3.1. 実行環境とその背景 3.2. Amazon Web Services 3.3. Google Cloud Platform 4. まとめ 56

Slide 57

Slide 57 text

#pyconjp_3 - サーバーやアプリケーションのログを収集・保存できるサー ビス - AWS サービスが標準出力すると、多くは CloudWatch Logs に書き込みにいく - AWSの各サービスがロギング設定を隠蔽している Amazon CloudWatch Logs 57

Slide 58

Slide 58 text

#pyconjp_3 - root logger に LambdaLoggerHandler という カスタムハンドラが 設定されている - Formatter で aws_request_id を指定できる - Filter で LogRecord の属性を追加するという技 - https://github.com/aws/aws-lambda-python-runtime-interface-client/blob/mai n/awslambdaric/bootstrap.py AWS Lambda (Zip/Container) 58

Slide 59

Slide 59 text

#pyconjp_3 - Logging モジュールに則った設定になっているので、これまで お話しした方法で変更できる - root logger に伝播させると(propagate=true)、前述の aws_request_id 出力を維持できるのでちょっと便利 Lambda の設定変更は標準と同じ 59

Slide 60

Slide 60 text

#pyconjp_3 60 def set_logger(): level = logging.getLevelName(os.environ.get('LOG_LEVEL')) FMT = '%(asctime)s.%(msecs)03d\t%(aws_request_id)s\t%(filename)s:%(funcName)s: %(lineno)d\t[%(levelname)s]%(message)s' # フォーマット変更 DATE_FMT = '%Y-%m-%d %H:%M:%S' fmt = logging.Formatter(fmt=FMT, datefmt=DATE_FMT, style='%') fmt.converter = time.gmtime root_logger = logging.getLogger() # ルートロガー取得 root_logger.setLevel(level) root_logger.handlers[0].setLevel(level) # handlers[0] でハンドラを指定 root_logger.handlers[0].setFormatter(fmt) 👉

Slide 61

Slide 61 text

#pyconjp_3 2021-10-01 13:22:26.84618a54970-32ad-4484-9650-167ed4c7b777 lambda_function.py:main:25 [INFO]{'key1': 'value1', 'key2': 'value2', 'key3': 'value3'} 2021-10-01 13:22:26.84618a54970-32ad-4484-9650-167ed4c7b777 lambda_function.py:main:26 [INFO]{'aws_request_id': '18a54970-32ad-4484-9650-167ed4c7b777', 'log_group_name': '/aws/lambda/pycon-jp-2021_test_logging', 'log_stream_name': '2021/10/01/[$LATEST]0f2d935e588b4cf1be2a734049dca4e5', 'function_name': 'pycon-jp-2021_test_logging', 'memory_limit_in_mb': '128', 'function_version': '$LATEST', 'invoked_function_arn': 'arn:aws:lambda:ap-northeast-1:123456789012:function:pycon-jp-2021_test_logging', 'client_context': None, 'identity': <__main__.CognitoIdentity object at 0x7f4836e986d0>, '_epoch_deadline_time_in_ms': 1633094556845} 61 def main(event, context): logger = logging.getLogger(__name__) logger.info(event) logger.info(vars(context)) # コンテキストを出力して aws_request_id を確認 👉

Slide 62

Slide 62 text

#pyconjp_3 62 Lambda powertools python https://awslabs.github.io/aws-lambda-powertools-python/latest/core/logger/ - Logging モジュールでは物足りない人に - context を含めた形で JSON で出力できる - xray_trace_id を簡単に出力できる - CloudWatch Logs Insights と相性がよい

Slide 63

Slide 63 text

#pyconjp_3 - Spark Job ではないほう - ログ出力先が二箇所に分かれている - /aws-glue/python-jobs/output - /aws-glue/python-jobs/error - ログが複数行まとまって出力されることがある AWS Glue: Python Script 63

Slide 64

Slide 64 text

#pyconjp_3 - /aws-glue/python-jobs/output - print() で出力するメッセージ - /aws-glue/python-jobs/error - logging モジュールで出力するメッセージ - スタックトレース Glue python script のデフォルト設定 64

Slide 65

Slide 65 text

#pyconjp_3 - /aws-glue/python-jobs/output - print() で出力するメッセージ - logging モジュールで出力するメッセージ - /aws-glue/python-jobs/error - スタックトレース こうしたくなる気持ち 65

Slide 66

Slide 66 text

#pyconjp_3 args = getResolvedOptions(sys.argv, ['log_level']) level = logging.getLevelName(args['log_level']) FMT = '%(asctime)s.%(msecs)03d %(filename)s:%(funcName)s:%(lineno)d [%(levelname)s]%(message)s' DATE_FMT = '%Y-%m-%d %H:%M:%S' fmt = logging.Formatter(fmt=FMT, datefmt=DATE_FMT, style='%') sh = logging.StreamHandler(stream=sys.stdout) # stdout に向いた StreamHandler sh.setLevel(level) # Level を変更 sh.setFormatter(fmt) # Formatter も変更 root_logger = logging.getLogger() root_logger.handlers.clear() # stderr に向いているハンドラを削除 root_logger.setLevel(level) root_logger.addHandler(sh) 66 👉

Slide 67

Slide 67 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 3.1. 実行環境とその背景 3.2. Amazon Web Services 3.3. Google Cloud Platform 4. まとめ 67

Slide 68

Slide 68 text

#pyconjp_3 - サーバーやアプリケーションのログを収集・保存できるサー ビス - Cloud Logging クライアントライブラリを使って出力 - https://github.com/googleapis/python-logging - サービスに応じて対応する Handler が異なる - GCPの各サービスを拡張するツールを提供している Cloud Logging 68

Slide 69

Slide 69 text

#pyconjp_3 - Python 標準の logging モジュールそのまま使うと Cloud Logging 独自のログレベルである ‘Severity’ を設定できない - それをカバーするためのライブラリ - 使い方は2通り、直接使用 か Logging モジュールに接続 - デフォルトのレベルは INFO googleapis/python-logging 69

Slide 70

Slide 70 text

#pyconjp_3 from google.cloud.logging import Client client = Client() client.setup_logging() # 実行サービスに応じたハンドラを ルートロガーにセットしてくれる デフォルト設定で出力してみる 70 from logging import getLogger logger = logging.getLogger(__name__) logger.info(‘Information!’) # INFO 以上のログが出力対象 👉

Slide 71

Slide 71 text

#pyconjp_3 71 驚きの長さ(1/3) { "textPayload": "2021-10-02 14:33:29.246 main.py:handler:30 [INFO]Information!", "insertId": "000001-6b9ed058-409f-4178-947b-444b580997c1", "httpRequest": { "requestMethod": "POST", "requestUrl": "http://5caf1872csaljdhnrk8d3e68398bad9e25-dot-pcb9e111e60bda6d2p-tp.appspot.com/", "userAgent": "Go-http-client/1.1", "protocol": "HTTP/1.1" },

Slide 72

Slide 72 text

#pyconjp_3 72 驚きの長さ(2/3) "resource": { "type": "cloud_function", "labels": { "function_name": "test_logging", "project_id": "my-project-id", "region": "us-east1" } }, "timestamp": "2021-10-02T14:33:29.246Z", "severity": "INFO",

Slide 73

Slide 73 text

#pyconjp_3 "labels": { "execution_id": "ayhzxzby8ee1" }, "logName": "projects/my-project-id/logs/cloudfunctions.googleapis.com%2Fcloud-functions", "trace": "projects/my-project-id/traces/5cb0d2b4e7997590659930dcfdd4c001", "sourceLocation": { "file": "/workspace/main.py", "line": "30", "function": "handler" }, "receiveTimestamp": "2021-10-02T14:33:39.442665753Z" } 73 驚きの長さ(3/3)

Slide 74

Slide 74 text

#pyconjp_3 - それでも Level や Formatter にこだわりたい あなたへ 74 デフォルトでいいじゃん

Slide 75

Slide 75 text

#pyconjp_3 import os from logging import Formatter, getLevelName, getLogger from google.cloud.logging.handlers import StructuredLogHandler, setup_logging FMT = '%(asctime)s.%(msecs)03d\t%(filename)s:%(funcName)s:%(lineno)d\t[%(levelname)s]%(messag e)s' DATE_FMT = '%Y-%m-%d %H:%M:%S' fmt = Formatter(fmt=FMT, datefmt=DATE_FMT, style='%') level = getLevelName(os.environ.get('LOG_LEVEL')) handler = StructuredLogHandler() handler.setLevel(level) handler.setFormatter(fmt) setup_logging(handler, log_level=level) # ルートロガーへのハンドラ設定(詳細は次ページ) 75 👉

Slide 76

Slide 76 text

#pyconjp_3 76 def setup_logging( handler, *, excluded_loggers=EXCLUDED_LOGGER_DEFAULTS, log_level=logging.INFO ): all_excluded_loggers = set(excluded_loggers + EXCLUDED_LOGGER_DEFAULTS) logger = logging.getLogger() if detect_resource().type in _CLEAR_HANDLER_RESOURCE_TYPES: logger.handlers.clear() # ルートロガーの既存ハンドラをクリア logger.setLevel(log_level) logger.addHandler(handler) # 新しいハンドラを追加 for logger_name in all_excluded_loggers: # google.cloud.logging 関連のログ伝播を止める logger = logging.getLogger(logger_name) logger.propagate = False https://github.com/googleapis/python-logging/blob/main/google/cloud/logging_v2/handler s/handlers.py (当スライドではコメントを一部編集) 👉

Slide 77

Slide 77 text

#pyconjp_3 - requirements.txt に google-cloud-logging を指定 Cloud Functions 77 def handler(request): request_json = request.get_json(silent=True) logger = getLogger(__name__) logger.info(f'Info {request_json}') try: raise ValueError('Error test') except Exception as e: traceback_str = traceback.format_exc().splitlines() err_msg = json.dumps({ "errorType": e.__class__.__name__, "errorMessage": e.__str__(), "stackTrace": traceback_str }) logger.error(err_msg) return err_msg

Slide 78

Slide 78 text

#pyconjp_3 - requirements.txt に google-cloud-logging を指定 - Flask を使う場合は app.logger.xxx 経由で出力 Cloud Run 78 @app.errorhandler(InternalServerError) def error_handler(e): return 'InternalServerError has occured.', e.code @app.route("/") def test_logging(): app.logger.debug('Debug') try: raise InternalServerError('Error test') except Exception as e: traceback_str = traceback.format_exc().splitlines() err_msg = json.dumps({ "errorType": e.__class__.__name__, "errorMessage": e.__str__(), "stackTrace": traceback_str }) app.logger.error(err_msg) abort(e.code)

Slide 79

Slide 79 text

#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 4. まとめ 79

Slide 80

Slide 80 text

#pyconjp_3 伝えたかったこと 80 - 利用するかどうかの判断基準を知る - 書き捨てでなければ使うと便利 - 構成要素がわかる - 主要な要素を図にまとめました - 出力方法と実装例を知る - アプリケーション と ライブラリ でお作法が異なる - AWS と GCP で思想が異なる

Slide 81

Slide 81 text

#pyconjp_3 - 目的が明確である - 伝えている内容が正確な事実である - 流量が適切である - ネクストアクションを促している こんな風にログを出したい 81

Slide 82

Slide 82 text

#pyconjp_3 - python.org, logging --- Python 用ロギング機能 - python.org, logging HOWTO - Adam Wiggins, The Twelve-Factor App - @amedama, ログ出力のための print と import logging はやめてほしい, Qiita - @yomon8, AWS Glue Python Shellでloggingを使ったログ出力につ いて - DevelopersIO, Lambda + Python3.7のログをJSON形式で出力して みる(行ごとに分割されない形で) 参考資料 82

Slide 83

Slide 83 text

#pyconjp_3 Thank you! 83

Slide 84

Slide 84 text

#pyconjp_3 (おまけ) Loggingモジュール関連の バージョン別変更点 まとめ 84

Slide 85

Slide 85 text

#pyconjp_3 - logging.Logger - pickle 化と unpickle 化ができるようになった - logging.disable - パラメータ level のデフォルト値が CRITICAL になった - logging.StreamHandler - stream の Setter である setStream が追加された Python 3.7 85

Slide 86

Slide 86 text

#pyconjp_3 - logging.Formatter - パラメータ validate が追加された (style と fmt のチェック) - logging.basicConfig - 引数 force が追加された Python 3.8 86

Slide 87

Slide 87 text

#pyconjp_3 - logger.Formatter - default_msec_format に None を指定できるようになった - logging.basicConfig - 引数 encoding, errors が追加された - logging.FileHandler (子クラスも対象) - パラメータ errors が追加された Python 3.9 87