[Python] Singleton Pattern을 이용한 Logger 만들기

2021. 4. 27. 18:56 Python/Python 프로그래밍

들어가며

  이번에 설명할 내용은 metaclass를 이용해 SingletonType의 class를 생성하고, 생성한 패턴을 이용해 실제로 사용하는 방법에 대해서 설명을 하려고 합니다. 일단 Singleton이라하면 두개 이상의 객체를 만드는 것을 제한하여, 한 클래스가 하나의 객체를 생성하는 것을 말한다. 다른 클래스에서는 하나의 객체를 이용하기 때문에 어디서든지 사용 가능한 객체를 만드는 경우에 사용한다. 저 같은 경우에는 가장 많이 쓰는 패턴이 아닌가 싶습니다. 안드로이드에서도 예를 들면 ListView를 구현할때 데이터를 관리하기 위해서 Singleton을 사용하기도 했습니다. 하지만 가장 많이 써본 경험은 하나의 역할을 하는데, 그 역할이 어느 곳에서든 사용할 수 있도록 하기 위함입니다.

 

Logger같은 경우에는 최초 처음 init을 해준 뒤에, 어느 곳에서 사용이 가능해야 합니다. 아래 예제는 Logger를 Singleton으로 구현하고, 사용하는 방법에 대해서 설명했습니다.

 

1. metaclass를 이용해 SingletonType Class 생성

  앞서 설명한 type이라는 class의 자식으로 SingletonType의 Class를 생성합니다. CustomLogger는 metaclass로 SingletonType을 갖고, 사용자가 최초 __call__()을 하게 되면 새로운 instance를 만들고, CustomLogger의 init의 함수를 호출하게 됩니다. 이렇게 되면 CustomLogger에서는 하나의 instance가 생성이 되었고, 그 instance는 __init__()에 있는 설정값대로 초기화가 이루어집니다. 아래 logger에 대한 초기화 내용은 [파이썬 로깅모듈에 대한 설명 바로가기]에서 더 자세하게 확인하실 수 있습니다.

import logging                                                                                                                                                                                                                                                                              
import os

class SingletonType(type):
    def __call__(cls, *args, **kwargs):
        try:
            return cls.__instance
        except AttributeError:
            cls.__instance = super(SingletonType, cls).__call__(*args, **kwargs)
            return cls.__instance

class CustomLogger(object):
    __metaclass__ = SingletonType
    _logger = None

    def __init__(self):
        self._logger = logging.getLogger("crumbs")
        self._logger.setLevel(logging.DEBUG)
        formatter = logging.Formatter('[%(levelname)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s')

        import datetime
        now = datetime.datetime.now()
        import time
        timestamp = time.mktime(now.timetuple())

        dirname = './log'
        if not os.path.isdir(dirname):
            os.mkdir(dirname)
        fileHandler = logging.FileHandler(dirname + "/Aries_"+now.strftime("%Y-%m-%d %H:%M:%S")+".log")
        streamHandler = logging.StreamHandler()

        fileHandler.setFormatter(formatter)
        streamHandler.setFormatter(formatter)

        self._logger.addHandler(fileHandler)
        self._logger.addHandler(streamHandler)

    def get_logger(self):
        return self._logger

 

2. Singleton 객체 생성 및 사용

  앞서 1.에서 생성한 CustomLogger를 사용하기 위해서는 __call__()의 메소드를 실행하면 됩니다. 여기서 눈여겨 보셔야할 사항은 __call()__을 여러번 호출해도 SingletonType의 class가 metaclass이기 때문에 최초 한번만 "Generate new instance"가 출력되고, CustomLogger의 __init__이 호출 됩니다. 그 이후의 __call__()에서는 객체는 생성, __init__()이 호출되지 않고, 기존에 생성된 최초의 instance를 가져올 수 있습니다. 이렇게 되면 어느 클래스에서도 사용이 가능한 CustomLogger가 완성이 됩니다. 

from CustomLogger import CustomLogger
# 최초 한번 실행될 경우에는 __call__의 'Generate new instance' 가 출력되고 CustomLogger의 __init__이 호출된다. 
logger = CustomLogger.__call__().get_logger()
logger.info("start logging...")

# 첫번째를 제외한 그 이후에는 'return instance'가 출력되고, init은 호출되지 않는다. (SingletonType class가 객체를 두개 이상 생성하는 것을 제한) 
logger = CustomLogger.__call__().get_logger()
logger = CustomLogger.__call__().get_logger()

 

더 좋은 의견이 있으시거나, 추가적으로 조언을 주시면 수정하겠습니다. 도움이 되셨다면, 작은 표시 하나 남겨주시면 더 좋은 글로 보답하겠습니다. :) 감사합니다.

 

출처 : ourcstory.tistory.com/105?category=630693