はてなの金次郎

Pythonエンジニアの技術系ブログ

【Python】loggingのerror()とexception()の違い

loggingのerror()とexception()は同じ ERROR レベルのロギング関数なのですが、どういった違いがあるのでしょうか?

結論はPython公式ドキュメントの「Logging HOWTO」に記載されています。

Logger.exception() は Logger.error() と似たログメッセージを作成します。違いは Logger.exception() がスタックトレースを一緒にダンプすることです。例外ハンドラでだけ使うようにしてください。

docs.python.org

実際にコーディングして確かめてみます。

例外処理をまずはerror()でログ出力してみます。 ZeroDivisionError の例外が投げられるようなコーディングをします。

import logging

logger = logging.getLogger('example')
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

try:
    result = 1 / 0
except ZeroDivisionError as e:
    logger.error(f'{e}')

これの出力は以下のようになりました。

2019-01-24 14:27:39,163 - example - ERROR - division by zero

続いて、exception()です。

try:
    result = 1 / 0
except ZeroDivisionError as e:
    logger.exception(f'{e}')

この出力は以下のようになりました。

2019-01-24 14:27:39,175 - example - ERROR - division by zero
Traceback (most recent call last):
  File "<ipython-input-2-6a7fc51b65bc>", line 2, in <module>
    result = 1 / 0
ZeroDivisionError: division by zero

上記のように、exception() のログにはスタックトレースが出力されているのに対し、error() のログには確認できません。

実際に試してみると非常にシンプルな違いですね。

例外処理のとき、exception() でログを出力するとスタックトレースとともに詳細なログが出力されます。 よって、例外処理のときは exception() を、それ以外のときは error() を使うと良いということでした。

上記のスクリプトGitHubでも公開していますので、よろしければご確認ください。

github.com

f:id:gyuuuutan:20190124145333p:plain
error()の場合

f:id:gyuuuutan:20190124145402p:plain
exception()の場合

GitLab 11.6の新機能「Suggest Changes」が便利なのでオススメ

「Suggest Changes」は一言でいうと、レビューと修正がブラウザで完結しちゃう機能です。具体的には、

  1. レビュワーはソースコードに対して変更の提案ができる
  2. デベロッパーは提案をApplyできる

ということができます。GitHubでも同じような機能がリリースされています。

f:id:gyuuuutan:20190122200050p:plain

使い方は insert suggestion というボタンを押して修正したいソースコードに書き換えるだけです。

f:id:gyuuuutan:20190122200107p:plain

f:id:gyuuuutan:20190122200124p:plain

詳細は以下のドキュメントをご確認ください。

gitlab.ssl.iridge.jp

タイポなどの細かい修正を提案したり複数の修正を提案したりする時に便利ですが、提案したコードでパイプラインが失敗したらかっこ悪いので用法用量にはお気をつけください。

あと、既知のバグかはわかりませんがハイライトのテーマがブラック系(特にMonokai)だと該当コードが見えにくいという罠があるので注意してください。

f:id:gyuuuutan:20190122200544p:plain

f:id:gyuuuutan:20190122200558p:plain

追記:
上記のバグですが、11.8で修正されるみたいです。 id:tnir さんにご指摘いただきました。

DjangoのINSTALLED_APPSの順番がめちゃくちゃ重要だった話

TL;DR

  • INSTALLED_APPS は最初にリストされているアプリケーションのソースコードが優先される
  • INSTALLED_APPSローカルアプリケーションサードパーティアプリケーションDjangoアプリケーション という順番にしたほうがよい
INSTALLED_APPS = [
    # Local apps
    'polls.apps.PollsConfig',

    # Third party apps
    'rest_framework',

   # Django apps
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
]
  • 上記の順番にするとテンプレートや静的ファイル、マネジメントコマンドを上書きする変更が反映される

INSTALLED_APPS

Django環境変数INSTALLED_APPS という環境変数があります。

これはDjangoインスタンスの中で有効化されているすべてのDjangoアプリケーションの名前を保持するリストの環境変数です。

django-admin startproject で作成されたDjangoアプリケーションの INSTALLED_APPS はデフォルトで以下のアプリケーションを保持しています。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

この INSTALLED_APPS を編集するタイミングが主に2パターンあると思います。ひとつは django-admin startapp でローカルアプリケーションを追加した場合。もうひとつはサードパーティのライブラリをインストールした場合です。

django-admin startapp でローカルアプリケーションを作成したら、そのアプリケーションおよびモデルを有効化するために INSTALLED_APPS に追加する必要があります。例えば、 polls というローカルアプリケーションを作成した場合以下のように追加します。

INSTALLED_APPS = [
    ...
    'polls.apps.PollsConfig',
    ...
]

※ ちなみに、単に polls と追加するのはタブーです。

Your code should never access INSTALLED_APPS directly. Use django.apps.apps instead.

FYI: https://docs.djangoproject.com/en/2.1/ref/settings/#installed-apps

また、サードパーティのライブラリをインストールしたら INSTALLED_APPS にアプリケーションを追加する必要がある場合があります。例えば Django REST Frameworkは以下のように rest_framework を追加する必要があります。

INSTALLED_APPS = [
    ...
    'rest_framework',
    ...
]

INSTALLED_APPSの順番

INSTALLED_APPS に関する順番に関しては特に公式ドキュメントには記載されていません。
また、さまざまなサードパーティREADME やドキュメント、Djangoに関する記事を見てきましたが本当にバラバラで多種多様です。

私自身も1週間前までは特に順番を意識することなく、Djangoアプリケーション・サードパーティアプリケーション・ローカルアプリケーションの順番でなんとなくまとめているという状況でした。

順番が重要だと気付いたのは、Django管理画面の既存のテンプレートや静的ファイルをカスタマイズしたい・上書きしたいという状況の時でした。ドキュメント通りに変更を加えたつもりが一切変更は反映されなかったのです。

このときはじめて INSTALLED_APPS に関するドキュメントを読んで、最初にリストされるアプリケーションのリソース(テンプレートや静的ファイル、マネジメントコマンド、翻訳)が優先される ということを知りました。

When several applications provide different versions of the same resource (template, static file, management command, translation), the application listed first in INSTALLED_APPS has precedence.

FYI: https://docs.djangoproject.com/en/2.1/ref/settings/#installed-apps

上記の仕様があるため、サードパーティDjangoの既存のリソースを上書きするような場合、上書きしたリソースがきちんと反映されるためにはそのリソースが含まれるアプリケーション(ローカルアプリケーションであることがほとんどだと思いますが)が上書きしたいリソースのアプリケーション(サードパーティDjango)より優先されていなければなりません。

つまり、サードパーティDjangoの既存のリソースを上書きするような場合、以下のような順番にする必要があります。

INSTALLED_APPS = [
    # Local apps
    'polls.apps.PollsConfig',

    # Third party apps
    'rest_framework',

   # Django apps
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
]

最も反映させたいのは言わずもがなローカルアプリケーションです。次にサードパーティアプリケーションです。サードパーティDjangoのリソースを上書きする場合があるためです。

カスタマイズは必要なくて上書きもしない場合は特にこれを意識する必要はありませんが、私のようにいざそのシチュエーションがきたとき、 INSTALLED_APPS が適切な順番でなかったら変更が反映されず混乱することになります。エラーが何か出力されるわけではないので原因の特定にもかなり苦労しました。

そんなことがないように INSTALLED_APPS の順番には意味があることをぜひ知っておいていただきたいです。余裕があれば自分のプロジェクトの INSTALLED_APPS の順番を見直してみてください。