LoginSignup
12
11

More than 5 years have passed since last update.

Pythonのロギング標準ライブラリでファイル出力をログレベル毎にする

Posted at

背景

・Pythonでログレベル毎にログファイルを分けたいという話があった。
・そもそもloggingライブラリをやっつけで使っていてきちんと理解していなかった。

では、手を動かして整理しつつ課題を解決してみよう。

おさらい

import logging

logging.basicConfig(format='%(asctime)s   %(message)s', datefmt='%m/%d/%Y', filename = './basic.log')
logging.warning('basic.logに日付と共に記録される。')
logging.basicConfig(filename = './not-appeared.log')
logging.warning('basicConfigは起動後最初の一回しか機能しないので、not-appeared.logは生成されない。')

root_logger = logging.getLogger('')
root_logger.setLevel(logging.ERROR)
root_logger.error('ERROR レベル以上を記録するroot loggerが設定された。basic.logには引き続き現れる。ハンドラーを指定していないので、他には出ない。')

root_logger.addHandler(logging.StreamHandler())

root_logger.error('これでコンソールに現れる。basicConfigで指定したフォーマットは無視される。')
root_logger.warning('ログレベルが低いのでコンソールには出ない。無視されたらbasic.logにも出ない。')

critical_handler = logging.FileHandler('./critical.log')
critical_handler.setLevel(logging.CRITICAL)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
critical_handler.setFormatter(formatter)
root_logger.addHandler(critical_handler)

root_logger.critical('CRITICALのみ記録するハンドラーを追加。日付やレベル名も追加。')
root_logger.error('こちらはコンソールに出るものの、ログレベルが低いのでcritical.logには出ない。')

second_logger = logging.getLogger('second')
second_logger.setLevel(logging.WARNING)
second_logger.addHandler(logging.StreamHandler())

second_logger.warning('ロガーを追加してコンソールに出すように。basic.logに加え、rootロガーとこれでコンソールに二度出る。')

second_logger.propagate = False

second_logger.warning('propagateを否にすると上流には伝わらなくなり、こちらはsecond_loggerの一回分しか出ない。')

ふりかえりと検討

・ルートロガーを元に多段階に継承できる。
・それぞれにハンドラーを複数設定できる。
・ロガーとハンドラーにはそれぞれログレベルを設定できる。
・但しいずれも下方の閾値なので、要件のように特定のログのみフィルタリングする事は出来ない。

解法

フィルターを使えば、ロギングの都度ログレコード(ログメッセージのインスタンス)を引数に呼ばれて真偽値を返す事でフィルタリング出来る。

こちらのStackOverflowの投稿を参考にした。#1の方の言うように、特定のレベルだけ抜き出しても、人間が読めるようなものにはならないというのは同意するけど。

python logging specific level only

log_file_by_level.py
import logging

class LoggingFilter(object):
    """特定のログレベルのみ残すフィルター"""
    def __init__(self, level):
        self.__level = level

    def filter(self, logRecord):
        #handlerへのレベル設定を外してここは== self.__levelとするのでも可のはず。
        return logRecord.levelno <= self.__level

def set_handler(loglevel, filename):
    #共通のログフォーマット
    log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
    handler = logging.FileHandler(filename)
    handler.setLevel(loglevel)
    handler.setFormatter(log_format)
    handler.addFilter(LoggingFilter(loglevel))
    logger.addHandler(handler)

logger = logging.getLogger(__name__)
#アプリで記録すべき最低限のレベルを指定。
logger.setLevel(logging.DEBUG)
set_handler(logging.WARN, './warning.log')
set_handler(logging.INFO, './info.log')
main.py
from log_file_by_level import logger

logger.warning('I warn you.')
logger.info('Please be informed that this is just a test.')
logger.error('Found error on your smile.')

参考

python logging specific level only
Logging HOWTO — Python 3.6.1 ドキュメント

12
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
11