みーのぺーじ

みーが趣味でやっているPCやソフトウェアについて.Python, Javascript, Processing, Unityなど.

Pythonのloggingでデバッグ

Pythonのloggingはとても便利です.

logging --- Python 用ロギング機能 — Python 3.9.1 ドキュメント

logging.debug(msg, *args, **kwargs)関数には便利なキーワード引数が存在します.

stack_info

logging.info("test", stack_info=True)

これだけで,スタック情報がロギングメッセージに追加されます.例えば,興味がある関数が複数回呼び出される原因を同定したい時に便利です.

DjangoのModel.save()が複数回呼び出されてデータベースのアクセスが無駄に多い原因を調べる時は,以下のようになります.

class AppleModel(models.Model):
    def save(self, *args, **kwargs):
        logger.info("save()", stack_info=True)
        super().save(self, *args, **kwargs)

これを実行すると,以下のようになりました.

2021-02-09 15:02:16,709 INFO: save()
Stack (most recent call last):
...
File "/app/utils/views.py", line 56, in perform_create
  serializer.save(owner=owner)
File "/opt/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 205, in save
  self.instance = self.create(validated_data)
File "/app/apple/views.py", line 34, in create
  return AppleModel.objects.create(
File "/opt/venv/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
  return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/opt/venv/lib/python3.8/site-packages/django/db/models/query.py", line 447, in create
  obj.save(force_insert=True, using=self.db)
File "/app/apple/models.py", line 69, in save
  logger.info("save()", stack_info=True)
File "/app/utils/views.py", line 143, in perform_update
  serializer.save()
File "/opt/venv/lib/python3.8/site-packages/rest_framework/serializers.py", line 200, in save
  self.instance = self.update(self.instance, validated_data)
File "/app/apple/views.py", line 69, in update
  instance.save()
File "/app/apple/models.py", line 69, in save
  logger.info("save()", stack_info=True)

これをみれば,AppleModel.objects.create()関数がsave()を呼び出し,自分が作成したviewsでもsave()を呼び出しているため,2回実行されていることが分かります.

Djangoのドキュメントにも,同じことが記載されています.

QuerySet API reference | Django documentation | Django

stacklevel

1より大きい数値を指定すると,その数だけスタックがスキップされます.

この機能を使うには,loggingのフォーマットを'{asctime} {levelname} {filename}-{lineno}: {message}'と設定して,ファイル名と行番号を表示するようにします.

logger.info("save()", stacklevel=1)

とデフォルトの状態で実行すると,以下のようにlogger.info()が存在するファイル名と行番号が表示されます.

2021-02-09 15:18:33,334 INFO models.py-69: save()

stacklevelを変更すると,logger.info()を呼び出した関数のファイル名と行番号に変わりました.

logger.info("save()", stacklevel=2)
2021-02-09 15:17:48,146 INFO views.py-69: save()

呼び出し元が重要なデバッグでは,このようにstacklevelを指定することで,ソースコードを目で追いかける必要がなくなります.

これ以外にも,exc_infoextraという引数が存在し,ドキュメントに詳しく記載されています.

logging --- Python 用ロギング機能 — Python 3.9.1 ドキュメント

デバッグを効率よく行うためにも,これらの機能を積極的に活用していきたいと思います.