はじめに
七尾百合子さん、お誕生日 193日目 おめでとうございます! nikkieです。
標準ライブラリの logging を使うことが多い私ですが、awesome なロギングライブラリの1つである structlog を使ったらどうなるのか、手を動かしてみました。
loguru 篇はこちら:
目次
HTTPX の Guide「logging」
HTTP クライアントライブラリの1つ、HTTPX1。
ドキュメントからロギングの Guide です。
import logging logging.basicConfig( format="%(levelname)s [%(asctime)s] %(name)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S", level=logging.DEBUG )
HTTPX は logging.getLogger("httpx").info() のようにロギングを実装しています。
Guide では logging.basicConfig() でルートロガーをDEBUGレベルに設定し、httpxロガー(やhttpcoreロガー)からの propagate を使ってロギングしています。
では structlog を使った場合はどう実装するのでしょうか?
structlog
vinta/awesome-python の Logging より2
structlog - Structured logging made easy.
integration についてのドキュメントから
structlog の ProcessorFormatter を利用する
標準ライブラリの logging との integration のドキュメントには、いくつか実装例が載っていました。
logging.getLogger("httpx").info() のようなライブラリのロギングもサポートする例は「Rendering using structlog-based formatters within logging」と理解しました。
ここでやりたいことは2つあります。
- structlog によるロギングの設定(HTTPXのログを出すケースでは必須ではないですが、取り組むことにしました)
- logging によるロギングの設定
見なよ...
structlog 側の設定はshared_processorsの末尾にstructlog.stdlib.ProcessorFormatter.wrap_for_formatterを追加。
On the structlog side, the processor chain must be configured to end with
structlog.stdlib.ProcessorFormatter.wrap_for_formatter()as the renderer.
It converts the processed event dictionary into something that ProcessorFormatter understands.
logging 向けにstructlog.stdlib.ProcessorFormatterインスタンスを作成。
processors引数では、structlog が追加するログレコード属性を削除し、コンソール出力する指定foreign_pre_chain引数にshared_processorsを指定
The
ProcessorFormatterhas aforeign_pre_chainargument which is responsible for adding properties to events from the standard library
このフォーマッタをルートロガーのハンドラに設定しています。

ルートロガーのハンドラにフィルタを設定して、httpxロガーによるログだけにできています(コメントアウト部分)
ドキュメント確認メモ
shared_processorsの要素について、ドキュメントを確認します。
そもそも structlog の Processor とは
The true power of structlog lies in its combinable log processors. A log processor is a regular callable (略)
structlog.stdlib.add_logger_name
https://www.structlog.org/en/stable/api.html#structlog.stdlib.add_logger_name
Add the logger name to the event dict.
structlog.stdlib.add_log_level
https://www.structlog.org/en/stable/api.html#structlog.stdlib.add_log_level
Add the log level to the event dict under the level key.
structlog.stdlib.PositionalArgumentsFormatter
https://www.structlog.org/en/stable/api.html#structlog.stdlib.PositionalArgumentsFormatter
Apply stdlib-like string formatting to the
eventkey.
structlog.processors.TimeStamper
https://www.structlog.org/en/stable/api.html#structlog.processors.TimeStamper
Add a timestamp to
event_dict.
structlog.processors.StackInfoRenderer
https://www.structlog.org/en/stable/api.html#structlog.processors.StackInfoRenderer
Add stack information with key
stackifstack_infoisTrue.
structlog.processors.format_exc_info
https://www.structlog.org/en/stable/api.html#structlog.processors.format_exc_info
Replace an
exc_infofield with anexceptionstring field using Python’s built-in traceback formatting.
structlog.processors.UnicodeDecoder
https://www.structlog.org/en/stable/api.html#structlog.processors.UnicodeDecoder
Decode byte string values in
event_dict.
structlog.processors.CallsiteParameterAdder
https://www.structlog.org/en/stable/api.html#structlog.processors.CallsiteParameterAdder
Adds parameters of the callsite that an event dictionary originated from to the event dictionary.
processor を組合せるというのは私も結構好きで、過去に他の OSS で見た実装を思い出しました。
宿題事項としては、filter_by_levelがよく分かっていないかも
https://www.structlog.org/en/stable/api.html#structlog.stdlib.filter_by_level
先頭に入れたのですが、例外送出。なんでだろ〜
終わりに
HTTPX に仕込まれた標準ライブラリ logging のロガーのログを structlog で出力してみました。
structlog.configureにもstructlog.stdlib.ProcessorFormatterにも共通の processor たちを設定します
structlog.configure:shared_processorsの末尾にstructlog.stdlib.ProcessorFormatter.wrap_for_formatterを追加structlog.stdlib.ProcessorFormatter:foreign_pre_chain引数にshared_processorsを指定- 標準ライブラリ logging のフォーマッタとして設定
小さな processor がいくつも登場して新規事項の量に圧倒されていますが、structlog のロガーも logging のロガーも共通の設定をして実現するというのが印象的でした。