盆暗の学習記録

データサイエンス ,エンジニアリング,ビジネスについて日々学んだことの備忘録としていく予定です。初心者であり独学なので内容には誤りが含まれる可能性が大いにあります。

[python]loggerの出力が重複するのを防ぐ

pythonのloggingを使うときのメモ

背景

loggerインスタンスにStreamHandlerやFileHandlerを設定する処理は使いまわしたいので、関数にしたい。

その際、単純にその処理を関数にまとめると、その関数が複数回呼ばれて同じ名前のloggerが複数回参照された場合はそのたびに毎回addHandlerStreamHandlerを設定してしまうため、ログ出力が重複する。

# 悪い例
import logging


def get_logger(name, level=logging.DEBUG):
    """loggerを設定する (悪い例)"""
    logger = logging.getLogger(name)
    logger.setLevel(level)
    # 毎回Handlerを設定してしまう場合
    ch = logging.StreamHandler()
    ch.setLevel(level)
    formatter = logging.Formatter(
        fmt="[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S"
    )
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    return logger


class Horse:

    def __init__(self):
        self.logger = get_logger(self.__class__.__name__)

    def scream(self):
        self.logger.debug("ヒヒーン!")


if __name__ == "__main__":
    h1 = Horse()
    h2 = Horse()
    h2.scream()

この場合、2回__init__が呼び出されたために同じloggerに2つのStreamHandlerが設定されているため、次のように2つ重複してログが出力される

[2021-01-27 08:32:59] [Horse] [DEBUG] ヒヒーン!
[2021-01-27 08:32:59] [Horse] [DEBUG] ヒヒーン!

提案

そこでhasHandlersを使ってHandlerの存在を判定して、Handlerがなければ追加するようにすればよさそう。

import logging


def get_logger(name, level=logging.DEBUG):
    logger = logging.getLogger(name)
    logger.setLevel(level)
    if not logger.hasHandlers():
        ch = logging.StreamHandler()
        ch.setLevel(level)
        formatter = logging.Formatter(
            fmt="[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S"
        )
        ch.setFormatter(formatter)
        logger.addHandler(ch)
    return logger


class Horse:

    def __init__(self):
        self.logger = get_logger(self.__class__.__name__)

    def scream(self):
        self.logger.debug("ヒヒーン!")


if __name__ == "__main__":
    h1 = Horse()
    h2 = Horse()
    h2.scream()
[2021-01-27 08:35:54] [Horse] [DEBUG] ヒヒーン!

このようにすれば重複したログ出力は避けられる。