Python プログラミング

【Python】loggingの正しい使い方

2021年4月14日

開発効率やメンテナンス性の観点から、ログを残すことは非常に重要です。

Pythonにはログ出力用に「logging」モジュールが用意されています。
loggingを使うことで、簡単にログ管理をすることができます。

loggingとは?

loggingの目的・存在意義

loggingは、その名の通りログを出力するために存在します。
loggingには、出力先の設定やログレベル(出力する閾値)などの管理ができるような便利な機能があります。

printとの違い

printは最も簡単な出力方法です。
しかし、出力先がコンソールに限定されているため、ログの出力としては適切ではありません。

loggingは、printよりかは設定が複雑ですが、誰にどのような情報を伝えたいのかに応じて、適切な出力設定をすることができます。

loggingの主な機能

ログレベル

ログレベルというのを設定することができます。
ログレベルは、ログを出力する閾値のことです。

例えば
「デバッグの時には出力したいけど、お客さんへのデモの時は出力したくない」
「致命的なエラーだけを出力したい」
こういった、用途別にログを出力することができるような機能です。

ログレベルには、CRITICALからNOTSETまで6段階あります。
基本的には、ERROR,INFO,DEBUGの3つを使い分けることになると思います。

出力先設定

ログの出力先を設定することができます。

コンソールへの出力はもちろんのこと、ファイルへの出力だけでなく、
メール送信や、ネットワークソケットにまで、多岐に渡ります。

これらが、logging(あるいはHandler)の設定で簡単に行うことができます。

フォーマット設定

ログの出力フォーマットを簡単に設定することができます。

ログを出力すると一言で言っても、何を出力するかも重要です。
日付、ログレベル、実行プログラム、伝えたいことなどあります。
これらのフォーマットを簡単に設定することができます。

日付の場合は、%(asctime)s。
ログレベルの場合は、%(levelname)s。
実行プログラムは、%(filename)s。
伝えたいことは、%(message)s。

これらだけでなく、フォーマットに使用できるものは多岐に渡ります。

loggingの使い方

モジュールごとにimportし、自身を設定する

まずは、loggingをimportしてしましょう。
標準で用意されていると思いますので、pip installなどは不要です。

import logging

loggerを作成する

次に、loggerを作成しましょう。
loggerというのは、loggingの子と思ってください。

後述のアンチパターンに記載しますが、loggingを直接編集・設定して使い回すことは推奨されていません。
では、早速loggerを作成しましょう。

logger = logging.getLogger(__name__)

これで、loggerの作成は完了です。
__name__と記載している通り、実行しているモジュール名を与えることが多いです。

getLoggerで名前をつけているだけのようなものですので、__name__ではなく、任意の文字列を与えることも可能です。

Handlerを作り、loggerに設定をする

次に、loggingのHandlerを作りましょう。
Handlerでは、出力の種類を設定します。

例えば、ログをファイルに出力する場合は、以下のように設定します。

filehandler = logging.FileHandler('test.log')

コンソールに出力する場合は、以下のように設定します。

streamhandler = logging.StreamHandler()

Handlerの作成はこれで完了です。
作成したHandlerをloggerに設定してあげましょう。

logger.addHandler(filehandler)
logger.addHandler(streamhandler)

これで、loggerにHandlerの設定が完了しました。
このloggerを使って、ログ出力をするときは、コンソールとファイルへの出力がされます。

このように、1つのloggerに対して、複数のHandlerを設定することが可能です。

ログレベルを設定する

ログレベル(出力の閾値)を設定しましょう。

logger.setLevel(logging.DEBUG)

これで、loggerにログレベルを設定することが完了しました。
こちらのケースでは、logger単位でログレベルを設定しました。

ユースケースとしては少ないと思いますが、ログレベルはHendler単位で設定することも可能です。
その場合は、以下のように記述します。

streamhandler.setLevel(logging.DEBUG)

このように記述することで、streamhandler(先程設定した、コンソールに出力するためのHandler)にログレベルが設定されました。
Handlerに設定する場合は、loggerにHandlerを登録するよりも前に、Handlerのログレベルを設定するように注意しましょう。

ログの出力

では、ここまで設定したら、実際にログの出力をしてみましょう。

logger.debug('testlog_debug')
logger.info('testlog_info')
logger.error('testlog_error')

こちらの記述で、ログの出力が可能になります。

ログレベルでDEBUGを指定したので、今回の場合は全て出力されます。
しかし、例えばログレベルをINFOに指定した場合は、"testlog_debug"は出力されません。

ここまでのサンプルコード

対話型ですが、ここまでの内容のコードです。
自由に組み替えて遊んでみてください。

>>> import logging
>>>
>>> logger = logging.getLogger(__name__)
>>>
>>> filehandler = logging.FileHandler('test.log')
>>> streamhandler = logging.StreamHandler()
>>>
>>> logger.addHandler(filehandler)
>>> logger.addHandler(streamhandler)
>>>
>>> logger.setLevel(logging.DEBUG)
>>>
>>> logger.debug('testlog_debug')
testlog_debug
>>> logger.error('testlog_info')
testlog_info
>>> logger.error('testlog_error')
testlog_error

生成されたtest.logの中身を記載しておきます。

testlog_debug
testlog_info
testlog_error

loggingを効果的に使うテクニック

ログのformatを設定する

出力するログのフォーマットを設定することができます。
前章のフォーマット設定でも説明したのですが、%(levelname)s:などの形式でログに出力する情報を簡単に設定できます。

使い方は、出力する文字列のStringを作成します。
下の例ですと、時間とログレベルとメッセージを表示します。

formatter = logging.Formatter('[%(asctime)s][%(levelname)s][%(message)s]')

日付などの一部の情報については、データフォーマットを指定することができます。
データフォーマットの設定は任意です。
上の例でデータフォーマットを指定する場合は、以下のようにします。

format = '[%(asctime)s][%(levelname)s][%(message)s]'
datefmt='%Y/%m/%d %I:%M:%S'
formatter = logging.Formatter(format, datefmt)

これらの設定を終えたら、このフォーマットをHandlerに設定しましょう。

streamhandler.setFormatter(formatter)
filehandler.setFormatter(formatter)

これで、コンソールへのログ出力とファイルへのログ出力でフォーマットを指定することができました。
コンソールへの出力は、以下のようになりました。

>>> logger.debug('testlog_debug')
[2021/04/14 12:25:31][DEBUG][testlog_debug]

configファイルで設定をする

loggingの設定は、pythonコード内だけでしか設定できないことはありません。
.confをはじめ、.jsonや.ymlでもconfigファイルを作成することができます。

configファイルで設定しておくメリットは、複数モジュールで同じlog設定を使用する場合に、有効です。
各モジュールでのlogging設定が2,3行でできるので、簡潔になります。

今回は、ごく一般的に使用される、yaml形式のconfigファイルを読み込む方法を紹介します。
(yamlは標準でインストールされていないので、pip install pyyamlなどのコマンドでインストールをする必要があります。)

yamlを読み込む場合は、python側では、以下のようにして呼び出します。

import logging
import yaml

with open('config.yml') as file:
    logging.config.dictConfig(yaml.safe_load(file.read()))
logger = logging.getLogger('sample')

#以降、通常のloggerと同じように使用する。

このように記述することで、loggerにyamlファイルで設定した内容が反映されます。
厳密に言えば、yamlファイルに記述された"sample"という名前のloggerの設定が、pythonコードないのloggerにコピーされました。

(余談ですが、ファイルオープンにはwith句を使用することで、ファイルを安全に開くことができます。)

では、肝心のyamlファイルの中身ですが、以下のように設定します。

version: 1
formatters:
  fmt1:
    format: '[%(asctime)s][%(name)s][%(levelname)s][%(message)s]'
handlers:
  streamhandler:
    class: logging.StreamHandler
    formatter: fmt1
    stream: ext://sys.stdout
  filehandler:
    class: logging.FileHandler
    formatter: fmt1
    filename: sample.log
loggers:
  sample:
    level: DEBUG
    handlers: [streamhandler, filehandler]
    propagate: no
    qualname: sample
root:
  level: DEBUG

こちらの場合は、"sample"という名前のloggerに対して、コンソール出力用のStreamHandlerとファイル出力用のFileHandlerを設定しました。
yamlの場合は、Handlerを設定するときは、親クラスからきちんと書くようにしましょう。

ログレベルは、いずれもDEBUGにしています。
出力のフォーマットは、StreamHandlerもFileHandlerも同じフォーマットを設定しました。

先頭に記述してある"version"ですが、こちらは2021年4月現在では、"1"しか設定できません。
現状では、特に意識する必要のない項目です。

loggerの中に設定しているpropagateは、"yes"か"no"を選択できます。
"yes"を選択した場合は、logger_rootに設定した設定を引き継ぎます。
"no"を選択した場合は、logger_rootの設定を引き継ぎません。

ちなみに.confで必要だったqualnameは、yamlでは任意となっています。
yamlの記述形式の都合上、名前は先に書いているからです。

rootの項目にもHandlerを設定してしまうと、二重にログが出力されることがあるので、注意してください。

loggingのアンチパターン

logging(root)でログレベルを操作する

実は、loggingはここまで複雑な設定をしなくても、使用することができます。
例えば、以下の通りです。

import logging

logging.basicConfig(level=logging.DEBUG)

logging.debug('Debug Message')

上記の例では、ログレベルの設定だけをしていますが、basicConfigにファイル名などを与えることで、さまざまなカスタマイズができます。

上記をみてわかる通り、loggerやhandlerを作成せずに、logの出力をしています。
こちらを実行しても、プログラムは問題なく実行されると思います。

しかしながら、こちらの方法は、開発者の中では好まれていません。
なぜならば、loggingはloggerの親のような存在であるからです。

loggingを直接設定してしまうと、loggerに影響を与えてしまう可能性があります。
複数のモジュールを使用する場合や、開発メンバーが複数人いる場合は、loggingを直接設定する方法はやめたほうが良いでしょう。

まとめ

今回は、Pythonのloggingの使い方について紹介しました。

loggingを利用することで、簡単にログ出力をカスタマイズすることができます。

使い方を簡単に復習すると、
①loggingをimportする
②loggerを作成する
③Handlerを作成し、loggerに設定する
④loggerにログレベルを設定する
⑤loggerで出力をする

以上になります。

ここまで読んでいただきありがとうございました。
参考にした教材は現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル です。

Python学習におすすめの教材

Pythonについて、しっかりと基礎から学びたい、実践的なスキルを身につけたいと考えている方向けに、おすすめの学習教材を紹介します。
これらの教材は、実際に僕がPythonの学習に使った教材になっています。

現役シリコンバレーエンジニアが教えるPython 3 入門 + 応用 +アメリカのシリコンバレー流コードスタイル

現役シリコンバレーエンジニアが教えるPython入門!応用では、データ解析、データーベース、ネットワーク、暗号化、並列化、テスト、インフラ自動化、キューイングシステム、非同期処理など盛り沢山の内容です!

ベネッセが運営する動画学習サイトUdemyで視聴することができる動画教材です。
(セールの時には¥1,500程度で買えます!)

Pythonは自由度が高くて雑に書いても動きますが、その分コードスタイルがおろそかになりがちです。
正しいコードスタイルを身に着けておかなければ、Pythonプログラマとして活躍することはあり得ません。
この教材は、グローバルスタンダードなコードスタイルを正しく学びながら、アルゴリズムやサーバー構築など幅広くPythonの活用方法を学習することができます。

Pythonの学習で間違えたくない方や、趣味レベルのスキルから一歩抜け出したい方におすすめです。

エキスパートPythonプログラミング改訂2版 Pythonプログラミングのベストプラクティスを伝授

本書は、Pythonを使って仕事をしている開発者が普段どのようなツールやテクニックを用いて仕事をしているのか、また開発者が実際に現場で用いているベストプラクティスについて解説した書籍です。本書を読むことで、先進的なPythonプログラマが日常的に使用している開発ノウハウを学ぶことができます。

こちらの書籍は、様々なユースケースに対するベストプラクティスを学習することができる書籍です。
Python2系と3系の違いについてもよく取り上げられますので、実質どちらのバージョンにも対応しているといえます。

Pythonをうまく使いこなせていない気がする方、初心者の殻を破りたい方におすすめです。

-Python, プログラミング
-, ,