[Python] 데코레이터(장식물)를 사용하여 Logging 기능을 공통화하려고 합니다.

12480 단어 Python3Python
파이톤의 데코레이터를 사용하시나요?
그것은 상당히 편리하다.
Decorator의 사용법을 복습한다는 뜻을 담고 있으며, Decorator를 사용하여 Logging 기능을 공통화하는 방법을 쓴다.

컨디션

  • MacOS 10.14
  • Python 3.7
  • 소스 코드

    test1.py를 모듈로 decorder를 제작하고test2.py에서 test1.pydecorder를 가져와 이런 구성을 실시한다.
    test1.py
    import inspect
    import logging
    from functools import wraps
    
    
    class CustomFilter(logging.Filter):
        """logger用のユーザー定義フィルター"""
    
        def filter(self, record):
            """呼び出し元のファイル名、関数名、行番号が表示されるようにする関数
            これでフィルタリングしないとデコレーターを使用した関数(呼び出し元)に関する情報ではなく、
            test1.pyの後述のlog関数を元にした情報が出力される
    
            Returns:
                True: 常にフィルターをパスする
            """
    
            record.real_filename = getattr(record,
                                           'real_filename',
                                           record.filename)
            record.real_funcName = getattr(record,
                                           'real_funcName',
                                           record.funcName)
            record.real_lineno = getattr(record,
                                         'real_lineno',
                                         record.lineno)
            return True
    
    
    def get_logger():
        """logging.Loggerの作成
    
        Returns:
            logger (logging.Logger): logging.Loggerのインスタンス
        """
    
        log_format = '[%(asctime)s] %(levelname)s\t%(real_filename)s' \
                     ' - %(real_funcName)s:%(real_lineno)s -> %(message)s'
        logging.basicConfig(format=log_format, level=logging.INFO)
        logger = logging.getLogger(__name__)
        logger.addFilter(CustomFilter())
        return logger
    
    
    
    def log(logger):
        """デコレーターでloggerを引数にとるためのラッパー関数
    
        Args:
            logger (logging.Logger)
    
        Returns:
            _decoratorの返り値
        """
    
        def _decorator(func):
            """デコレーターを使用する関数を引数とする
    
            Args:
                func (function)
    
            Returns:
                wrapperの返り値
            """
    
            # funcのメタデータを引き継ぐ
            @wraps(func)
            def wrapper(*args, **kwargs):
                """実際の処理を書くための関数
    
                Args:
                    *args, **kwargs: funcの引数
    
                Returns:
                    funcの返り値
                """
    
                func_name = func.__name__
                # loggerで使用するためにfuncに関する情報をdict化
                extra = {
                    'real_filename': inspect.getfile(func),
                    'real_funcName': func_name,
                    'real_lineno': inspect.currentframe().f_back.f_lineno
                }
    
                logger.info(f'[START] {func_name}', extra=extra)
    
                try:
                    # funcの実行
                    return func(*args, **kwargs)
                except Exception as err:
                    # funcのエラーハンドリング
                    logging.error(err, exc_info=True, extra=extra)
                    logging.error(f'[KILLED] {func_name}', extra=extra)
                else:
                    logging.info(f'[END] {func_name}', extra=extra)
    
            return wrapper
        return _decorator
    
    test2.py
    from test1 import log, get_logger
    
    
    logger = get_logger()
    
    
    def raise_error():
        raise Exception('Error!!!!')
    
    
    @log(logger)
    def main():
        """デコレーターの呼び出し元
    
        @log(logger)
        def main()       <=====>    log(logger)(main)()
        """
        logger.info('Hello world')
        return raise_error()
    
    
    if __name__ == '__main__':
        main()
    

    실행 결과


    상기 원본 코드를 실행하면 다음과 같은 결과를 얻을 수 있다.
    $ python test2.py
    
    [2019-09-11 04:49:48,719] INFO  test2.py - main:23 -> [START] main
    [2019-09-11 04:49:48,719] INFO  test2.py - main:18 -> Hello world
    [2019-09-11 04:49:48,719] ERROR test2.py - main:23 -> Error!!!!
    Traceback (most recent call last):
      File "~/test/test1.py", line 90, in wrapper
        return func(*args, **kwargs)
      File "test2.py", line 19, in main
        return raise_error()
      File "test2.py", line 8, in raise_error
        raise Exception('Error!!!!')
    Exception: Error!!!!
    [2019-09-11 04:49:48,720] ERROR test2.py - main:23 -> [KILLED] main
    
    Custom Filter로 처리하지 않으면 KeyError가 되거나 원하는 로그 정보를 제대로 얻지 못할 수 있으므로 주의해야 합니다.

    끝말


    장식물을 사용하면 간단하게 유니버설 처리를 할 수 있을 뿐만 아니라 간단하게 진행할 수 있을 거라고 생각합니다.개인적으로 소스 코드가 시원해서 너무 좋아요.
    이것을 만드는 것은 장식물에 대한 참고가 아니라 logging — Logging facility for Pythoncpython/Lib/logging/__init__.py를 읽었다.logging 주위가 더 똑똑해질지도 몰라요.좋은 방법이 있으면 댓글로 남겨주세요

    Reference

  • PEP 318 -- Decorators for Functions and Methods
  • 좋은 웹페이지 즐겨찾기