Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Loggingモジュールではじめるログ出力入門 / Introduction to Pytho...
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Toshifumi Tsutsumi
October 15, 2021
Programming
17k
33
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Loggingモジュールではじめるログ出力入門 / Introduction to Python Logging
PyCon JP 2021 登壇資料:
https://2021.pycon.jp/time-table/?id=272259
Toshifumi Tsutsumi
October 15, 2021
More Decks by Toshifumi Tsutsumi
See All by Toshifumi Tsutsumi
ModuleNotFoundErrorの傾向と対策:仕組みから学ぶImport / Unpacking ModuleNotFoundError
tosh2230
3
6.6k
CDCデータパイプラインを止めないために / One Stream of the CDC
tosh2230
0
1.7k
ニアリアルタイム分析の実現に向けたChange Data Captureの導入 / Change data capture for near realtime analytics
tosh2230
3
2.5k
データリネージの組織導入事例と今後の戦略 / Introduction to an example of data lineage in GMO Pepabo
tosh2230
0
1.2k
SQLクエリ解析によるE2Eデータリネージの実現 / E2E-data-lineage
tosh2230
0
4.2k
データ抽出基盤 Yeti をつくっている話 / Yeti - Yet another Extract-Transfer Infrastructure
tosh2230
1
5.6k
データ基盤チームの設立と直近の取り組み / the-establishment-of-pepabo-data-platform-team
tosh2230
5
4.8k
Other Decks in Programming
See All in Programming
TypeScript+Orvalで実現する型安全かつ堅牢でスケーラブルなマルチチャネル通知基盤 / TSKaigi Night talks ~after conference~
d0riven
0
340
「なぜそう決めたのか」を残し続ける仕組み ― Notion AI カスタムエージェント × Slack連携による設計判断の自動記録 - NIKKEI Tech Talk #47
niftycorp
PRO
0
180
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
790
さぁV100、メモリをお食べ・・・
nilpe
0
140
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
570
AI 時代のソフトウェア設計の学び方
masuda220
PRO
29
13k
AIだと陥りがちなJakarta EE最新技術への移行時の落とし穴と解決策
tnagao7
0
110
その問い、本当に正しいですか?AI時代のエンジニアに必要な哲学と認知科学 / ai-philosophy-cognitive-science
minodriven
11
5.7k
エージェンティックRAGにAWSで入門しよう!
har1101
8
1.6k
Lemonade + Foundry Toolkit でお手軽アプリ開発
seosoft
1
340
TAKTでAI駆動開発の品質を設計する
j5ik2o
7
1.3k
CSC307 Lecture 17
javiergs
PRO
0
320
Featured
See All Featured
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
17k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
How to make the Groovebox
asonas
2
2.2k
Designing for Performance
lara
611
70k
Side Projects
sachag
455
43k
Darren the Foodie - Storyboard
khoart
PRO
3
3.4k
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
170
How People are Using Generative and Agentic AI to Supercharge Their Products, Projects, Services and Value Streams Today
helenjbeal
1
210
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.5k
Fireside Chat
paigeccino
42
4k
Transcript
#pyconjp_3 Loggingモジュールではじめる ログ出力入門 PyCon JP 2021 2021-10-16
#pyconjp_3 堤 利史 / Toshifumi Tsutsumi GMO Pepabo, inc. Twitter:
@tosh2230 #風来のシレン #囲碁 #pyconjp_3 2
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 4.
まとめ 3
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 4.
まとめ 4
#pyconjp_3 ゴール - 利用するかどうかの判断基準を知る - 主な構成要素を知る - ログ出力方法と実装例を知る - ソースコードは
GitHub にあります - https://github.com/tosh223/pycon-jp-2021 5
#pyconjp_3 スコープ - Python 3.9 アプリケーション・ライブラリを対象とします - ログを出力する話をします - ログを受け取ったあとの話はほとんどしない予定です
出力 >> [収集, 蓄積, 加工] >> 分析 6 このあたり
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要
2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 7
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要
2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 8
#pyconjp_3 - Python に標準で搭載されている - アプリケーションやライブラリで発生した イベントを記録するための道具 - Five Ws
(who, what, when, where, why) Logging モジュールとは 9
#pyconjp_3 - 処理が (開始|終了) した - 処理の待ち合わせに成功した - 注意すべき条件分岐に入った -
期待した状態になっていない - エラーになった イベント例 10
#pyconjp_3 >>> from logging import getLogger >>> logger = getLogger(__name__)
>>> logger.error('This is an error!') This is an error! 11 (print でよいのでは...?) シンプルに出力してみる
#pyconjp_3 - プログラムとログのライフタイムが短い - 局所的・バッチ的なテキスト出力 - 目的がイベントの記録以外 - デバッグ -
ちょっとした挙動の確認 print でも良いケース 12
#pyconjp_3 - イベント時刻・発生箇所の記録 - ログレベルの指定 - 出力フォーマットの統一 - 柔軟な出力設定 -
スレッドセーフ Logging モジュールの便利ポイント 13
#pyconjp_3 標準ライブラリモジュールとしてログ記録 API が提供される利点 は、すべての Python モジュールがログ記録に参加できることで あり、これによってあなたが書くアプリケーションのログにサー ドパーティーのモジュールが出力するメッセージを含ませること ができます。
最大のメリット 14 https://docs.python.org/ja/3/library/logging.html
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要
2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 15
#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 16
propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() 主な要素は 6つ
#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 17
propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ① Logger
#pyconjp_3 - ログを出力する主体 - メッセージを LogRecord に変換する - Handler を使って、LogRecord
を指定した書式の文字列に変 換して出力する - propagate という仕組みがある (後述) ① Logger 18
#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 19
propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ② Level
#pyconjp_3 - Logger や Handler に設定する - ログメッセージ出力時にイベントの重要度に応じて指定する - 以下の条件が
True のときにログが出力される (Message level) >= (Logger level) and (Message level) >= (Handler level) ② Level 20
#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
#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 22
propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ③ LogRecord
#pyconjp_3 - Logger に渡されたメッセージを変換したオブジェクト - メッセージだけでなく、イベントが発生したソースコードや 実行環境に関するコンテキストを保持する - ログに出力される時刻は、LogRecord が作成された時刻
③ LogRecord 23 https://docs.python.org/ja/3/library/logging.html#logrecord-attributes
#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 24
propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ④ Filter
#pyconjp_3 - 出力対象 Level と判定された LogRecord が対象 - 設定対象によってフィルタリングのタイミングが異なる -
Logger : LogRecord が できた直後 - Handler : Handler Level 判定の後 ④ Filter 25
#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 にする 👉
#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 27
propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ⑤ Handler
#pyconjp_3 - LogRecord を特定の方法・書式で出力するための道具 - 多様な Handler が実装されている - StreamHandler:
ストリームへの出力 - FileHandler: ファイルへの出力 - ひとつの Logger に複数の Handler を設定できる ⑤ Handler 28
#pyconjp_3 ① Logger ⑤ Handler ⑤ Handler ⑤ Handler 29
propagate ⑥ Formatter ④ Filter ④ Filter ② Level ② Level ③ Log Record Required Optional String Message String String logger.error() ⑥ Formatter
#pyconjp_3 - LogRecord を任意の文字列・日付書式に変更する - タイムゾーンは Formatter.converter で設定 - デフォルトは
time.localtime - UTC に変更するには time.gmtime を設定 - メッセージフォーマットの Style は 3種類ある(次ページ) ⑥ Formatter 30
#pyconjp_3 - ‘%’: %(<dict key>)s 形式の置換文字列 - dict key で指定できるのは
LogRecord 属性 - ‘{’: str.format() 互換 - ‘$’: string.Template.substitute() 互換 Message format style 31 https://docs.python.org/ja/3/howto/logging.html#formatters
#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 <stdin>:<module>:1 [ERROR]This is an error! 👉
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要
2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 33
#pyconjp_3 - Logger はモジュールごとにつくるとよい - logger = getLogger(__name__) とすると、モジュールの完 全修飾名と同じ名前の
Logger をつくれる - Logger がわかる = イベントがどこで起きたのかわかる - モジュールやパッケージと同じく階層構造がある Logger の作成と命名 34 https://docs.python.org/ja/3/library/logging.html#logger-objects
#pyconjp_3 app 35 Logger の階層構造 root lib app.main lib.db lib.util
#pyconjp_3 36 root logger - すべての Logger の祖先 - logging
モジュールが読み込まれたタイミングで自動生成 - logging.error など logging 直接記録は root logger を使用 - 記録時に Handler がなければ、logging.basicConfig (後述) が呼ばれる
#pyconjp_3 37 階層構造を活かしたイベントの伝播 root lib lib.util StreamHandler lib.util で メッセージを記録すると
root まで伝播する (すべての下位 Logger が propagate=true である場合) Handler 未設定 FileHandler logger.error()
#pyconjp_3 38 root lib lib.util Handler 未設定 StreamHandler 下位 Logger
に Handler がなくても、同じく上位に伝播する Handler 未設定 logger.error() 階層構造を活かしたイベントの伝播
#pyconjp_3 - エントリポイントで、root logger に Logger 全体に適用した い設定 (=デフォルト設定) を行う
- getLogger(__name__) で作成した個別の Logger は “propagate=true” にする - 特定のLogger で挙動を変えたいときだけ、個別に設定する 39 設定は root logger にまとめると便利
#pyconjp_3 上位 Logger (root 含む) Handler 40 注意: Propagate 対象は
Handler のみ 上位 Logger 自体に設定された Level, Filter は作用しない 下位 Logger Handler Formatter Filter Filter Level Level Log Record Log Record
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要
2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 41
#pyconjp_3 設定方法は4種類 42 いずれかを エントリポイント付近で一度だけ 行う 1. コードで直接指定 2. logging.basicConfig
3. logging.config.dictConfig 4. logging.config.fileConfig (非推奨のため今回は省略)
#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
#pyconjp_3 - root logger に対する基本設定ができる - デフォルトでは、root logger に何らかの Handler
設定がな されている場合は反映されない(引数 force で変更可) 方法2: logging.basicConfig 44 https://docs.python.org/ja/3/library/logging.html#logging.basicConfig
#pyconjp_3 - Dict から設定を読み込む - YAML や JSON で設定ファイルをつくることができる -
デフォルトでは、呼び出す前に存在している非ルートロガー を無効化するので注意 (disable_existing_loggers=true) 方法3: logging.config.dictConfig 45
#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
#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
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 2.1. Logging モジュールの概要
2.2. Logging モジュールの構成要素 2.3. Logger の性質 2.4. Logger の設定と出力 (アプリケーション編) 2.5. Logger の設定と出力 (ライブラリ編) 3. パブリッククラウドサービスでのログ出力 4. まとめ 48
#pyconjp_3 - Handler/Level/Formatter/Filter の選択権は 呼び出し側のアプリケーションにある - ライブラリが決めるのは タイミング と メッセージ内容
ライブラリでは詳細設定をしない 49
#pyconjp_3 - ライブラリのトップレベル Logger に NullHandler のみを設 定するのがお作法 - NullHandler
は、何もせず上位 Logger に伝播させる - この仕組みにより 3rd party ライブラリもログ出力対象に加 えることができる NullHandler を設定 50 https://docs.python.org/ja/3/howto/logging.html#configuring-logging-for-a-library
#pyconjp_3 # 設定 from logging import getLogger, NullHandler logger =
getLogger(__name__) logger.addHandler(NullHandler()) ライブラリでの設定・出力例 51 # 出力 logger.warning('Warning') 👉
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 3.1.
実行環境とその背景 3.2. Amazon Web Services 3.3. Google Cloud Platform 4. まとめ 52
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 3.1.
実行環境とその背景 3.2. Amazon Web Services 3.3. Google Cloud Platform 4. まとめ 53
#pyconjp_3 - パブリッククラウドサービスでの PaaS / FaaS では、あらか じめロギング設定がなされている - Python
の Logging モジュールを応用している - 凝ったことをするには Logging モジュールの知識が不可欠 実行環境は用意されている 54
#pyconjp_3 - モダンなアプリケーションとは、を述べたもの - ステートレス かつ シェアードナッシング を志向 - Factor
Ⅸ: “ログをイベントストリームとして扱う” - ログはローカルに出さず 標準出力に書き出して収集 前提: The Twelve-Factor App 55 https://12factor.net/ja/logs
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 3.1.
実行環境とその背景 3.2. Amazon Web Services 3.3. Google Cloud Platform 4. まとめ 56
#pyconjp_3 - サーバーやアプリケーションのログを収集・保存できるサー ビス - AWS サービスが標準出力すると、多くは CloudWatch Logs に書き込みにいく
- AWSの各サービスがロギング設定を隠蔽している Amazon CloudWatch Logs 57
#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
#pyconjp_3 - Logging モジュールに則った設定になっているので、これまで お話しした方法で変更できる - root logger に伝播させると(propagate=true)、前述の aws_request_id
出力を維持できるのでちょっと便利 Lambda の設定変更は標準と同じ 59
#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) 👉
#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 を確認 👉
#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 と相性がよい
#pyconjp_3 - Spark Job ではないほう - ログ出力先が二箇所に分かれている - /aws-glue/python-jobs/output -
/aws-glue/python-jobs/error - ログが複数行まとまって出力されることがある AWS Glue: Python Script 63
#pyconjp_3 - /aws-glue/python-jobs/output - print() で出力するメッセージ - /aws-glue/python-jobs/error - logging
モジュールで出力するメッセージ - スタックトレース Glue python script のデフォルト設定 64
#pyconjp_3 - /aws-glue/python-jobs/output - print() で出力するメッセージ - logging モジュールで出力するメッセージ -
/aws-glue/python-jobs/error - スタックトレース こうしたくなる気持ち 65
#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 👉
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 3.1.
実行環境とその背景 3.2. Amazon Web Services 3.3. Google Cloud Platform 4. まとめ 67
#pyconjp_3 - サーバーやアプリケーションのログを収集・保存できるサー ビス - Cloud Logging クライアントライブラリを使って出力 - https://github.com/googleapis/python-logging
- サービスに応じて対応する Handler が異なる - GCPの各サービスを拡張するツールを提供している Cloud Logging 68
#pyconjp_3 - Python 標準の logging モジュールそのまま使うと Cloud Logging 独自のログレベルである ‘Severity’
を設定できない - それをカバーするためのライブラリ - 使い方は2通り、直接使用 か Logging モジュールに接続 - デフォルトのレベルは INFO googleapis/python-logging 69
#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 以上のログが出力対象 👉
#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" },
#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",
#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)
#pyconjp_3 - それでも Level や Formatter にこだわりたい あなたへ 74 デフォルトでいいじゃん
#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 👉
#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 (当スライドではコメントを一部編集) 👉
#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
#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)
#pyconjp_3 Contents📝 1. ゴールとスコープ 2. Logging モジュール探訪 3. パブリッククラウドサービスでのログ出力 4.
まとめ 79
#pyconjp_3 伝えたかったこと 80 - 利用するかどうかの判断基準を知る - 書き捨てでなければ使うと便利 - 構成要素がわかる -
主要な要素を図にまとめました - 出力方法と実装例を知る - アプリケーション と ライブラリ でお作法が異なる - AWS と GCP で思想が異なる
#pyconjp_3 - 目的が明確である - 伝えている内容が正確な事実である - 流量が適切である - ネクストアクションを促している こんな風にログを出したい
81
#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
#pyconjp_3 Thank you! 83
#pyconjp_3 (おまけ) Loggingモジュール関連の バージョン別変更点 まとめ 84
#pyconjp_3 - logging.Logger - pickle 化と unpickle 化ができるようになった - logging.disable
- パラメータ level のデフォルト値が CRITICAL になった - logging.StreamHandler - stream の Setter である setStream が追加された Python 3.7 85
#pyconjp_3 - logging.Formatter - パラメータ validate が追加された (style と fmt
のチェック) - logging.basicConfig - 引数 force が追加された Python 3.8 86
#pyconjp_3 - logger.Formatter - default_msec_format に None を指定できるようになった - logging.basicConfig
- 引数 encoding, errors が追加された - logging.FileHandler (子クラスも対象) - パラメータ errors が追加された Python 3.9 87