좀 큰 프로그램을 짜다 보면, 특히 한번 돌고 말 것이 아닌 지속적으로 돌아야 하는 프로그램을 짜다 보면 로깅의 필요성을 아주 절실하게 느끼게 된다. 버그가 발생했다면 언제 버그가 발생했고, 어떤 버그가 어디에서 무엇이 원인으로 발생했는지를 잘 기록해 두어야 프로그램을 유지보수하기 편해진다.


 파이썬에는 로깅을 쉽고 빠르고 간편하게 처리할 수 있게 해주는 내장 모듈이 존재한다.

logging 이라는 모듈인데, 이를 사용하면 로그 포맷팅이나 크기 관리 등이 굉장히 편해진다.


https://docs.python.org/2/library/logging.html

파이썬 2에서의 logging 문서이다.

기초적인 사용법에 대해서는 여기에 적지 않겠다.



 logging 모듈을 사용하다 보면 로깅 자체는 간단하게 할 수 있어 좋지만 logger를 만드는 데에 핸들러도 만들어야 하고, 핸들러마다 포맷터도 필요하고, 포맷터를 핸들러에 등록하고 핸들러를 다시 logger에 등록하는 등 상당히 귀찮은 과정을 지나야 한다는 것을 느낄 수 있다. 그래서 보통 다음과 같은 CreateLogger 함수를 만들어서 사용한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import logging
import logging.handlers
 
def CreateLogger(logger_name):
    # Create Logger
    logger = logging.getLogger(logger_name)
    logger.setLevel(logging.DEBUG)
 
    formatter = logging.Formatter('\n[%(levelname)s|%(name)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s')
    
    # Create Handlers
    streamHandler = logging.StreamHandler()
    streamHandler.setLevel(logging.DEBUG)
    streamHandler.setFormatter(formatter)
 
    logger.addHandler(streamHandler)
    return logger
cs


 위와 같이 logger를 만들어주는 함수를 짜서 Utils.py 같은 곳에서 호출하여 사용하면 상당히 편리해진다.

그런데 위 코드에는 문제가 하나 있다.


다음과 같은 코드를 보면,


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import logging
import logging.handlersd
 
def CreateLogger(logger_name):
    # Create Logger
    logger = logging.getLogger(logger_name)
    logger.setLevel(logging.DEBUG)
 
    formatter = logging.Formatter('\n[%(levelname)s|%(name)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s')
    
    # Create Handlers
    streamHandler = logging.StreamHandler()
    streamHandler.setLevel(logging.DEBUG)
    streamHandler.setFormatter(formatter)
 
    logger.addHandler(streamHandler)
    return logger
 
def Func1():
    logger = CreateLogger("MyLogger")
    logger.info("Hello, Func1!")
 
def Func2():
    logger = CreateLogger("MyLogger")
    logger.info("Hello, Func2!")
 
if __name__ == '__main__':
    Func1()
    Func2()
    logger = CreateLogger("MyLogger")
    logger.info("Hello, Main!")
cs


기대하는 결과값은 "Hello, Func1!"과 "Hello, Func2!"와 "Hello, Main!"이 순서대로 하나씩 나오는 것이다.

하지만 이를 실행했을 때 나오는 결과값은 다음과 같다.


눈치가 빠른 사람들은 벌써 이게 왜 잘못되었는지 알아챘을 것이다.


 이런 문제가 생기는 것은 logging 모듈에서 getLogger()를 통해 새로운 logger를 받아올 때, 같은 logger 이름일 경우 새로 logger를 만들지 않고 이미 만들어진 logger를 그대로 리턴하는 싱글턴 패턴을 사용하고 있기 때문이다.


 간단하게 말해서 Func1에서 "MyLogger"라는 이름의 logger를 하나 만들었을 때와 Func2에서 "MyLogger"라는 이름의 logger를 하나 만들었을 때, 두 logger는 같은 것이 된다는 것이다. 같은 logger에 핸들러를 계속해서 붙이므로 한번 logging해도 여러 개의 핸들러가 작동하여 로그는 여러 개가 찍히는 것이다.


실제로 이를 테스트해 보면 다음과 같은 결과가 나온다.


이런 문제를 해결하기 위해서는 어떻게 해야 할까?

이를 해결하기 위해서는 싱글턴 패턴의 특성을 이용하는 것이 좋다.


getLogger()를 이용하여 가져온 logger가 이미 핸들러를 가지고 있다면, 이미 핸들러가 등록되어 있으므로 새로운 핸들러를 또 등록할 이유가 없다. 따라서 위의 잘못된 소스코드를 다음과 같이 수정하면 제대로 된 결과를 얻을 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import logging
import logging.handlers
 
def CreateLogger(logger_name):
    # Create Logger
    logger = logging.getLogger(logger_name)
 
    # Check handler exists
    if len(logger.handlers) > 0:
        return logger # Logger already exists
 
    logger.setLevel(logging.DEBUG)
 
    formatter = logging.Formatter('\n[%(levelname)s|%(name)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s')
    
    # Create Handlers
    streamHandler = logging.StreamHandler()
    streamHandler.setLevel(logging.DEBUG)
    streamHandler.setFormatter(formatter)
 
    logger.addHandler(streamHandler)
    return logger
 
logger = CreateLogger("Utils")
 
def Func1():
    logger = CreateLogger("MyLogger")
    logger.info("Hello, Func1!")
 
def Func2():
    logger = CreateLogger("MyLogger")
    logger.info("Hello, Func2!")
 
if __name__ == '__main__':
    Func1()
    Func2()
    logger = CreateLogger("MyLogger")
    logger.info("Hello, Main!")
cs



블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

댓글을 달아 주세요