본문으로 바로가기

Python의 decorator로 log 남기기

category Project/gist 2019. 5. 17. 18:00

Python으로 프로젝트를 진행하면서, API 요청에 대한 Request, Response에 대한 info를 로그(log)로 남기고 싶다면 decorator를 사용한다면 편리하다. 

decorator란

함수를 감싸고 있는 함수라고 생각하면 된다. @를 사용하여 wrapping 하고 싶은 함수 위에 적어주면, python의 모든 객체는 1급 객체이므로 해당 함수가 데코레이터의 인자로 전달될 수 있다. 이때 클로저(내부 함수가 외부 함수의 인자를 기억하고 있는 것)라는 개념을 이해하고 있어야 하는데, 데코레이터의 인자로 전달된 함수는 외부에서 전달되었음에도 외부 함수의 인자값들을 모두 기억하고 있다. 아래의 logger_decorator_with_params 메소드는 decorator로 활용되었다. 해당 함수를 실행하기 전, 후에 대해서 어떠한 작업을 해야할 필요가 있는 경우에 활용하면 좋다.

decorator의 활용

데코레이터는 다음과 같은 경우에 활용된다.

  • log를 남겨야 하는 경우

  • 프로그램 성능을 위한 테스트 목적

  • 경우에 따라서 exception handling을 위해서 활용

하기도 하는 것 같다. 내 프로젝트에는 log를 남기기 위해서 아래와 같이 decorator를 구현하였다.

파이썬 코드 - decorator 메소드 logger_decorator_with_params 구현

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
import os
import studi
import logging
from flask import request
 
def gen_logger(module_name):
    logger = logging.getLogger(module_name)
    logger.setLevel(logging.DEBUG)
    logger_handler = logging.FileHandler(os.path.join(studi.app.config['LOG_DIR'], module_name + '.log'))
    logger_handler.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    logger_handler.setFormatter(formatter)
    logger.addHandler(logger_handler)
    return logger
 
 
def logger_decorator_with_params(logger):
    def wrapper(func):
        def decorator(*args, **kwargs):
            logger.info('REQUEST, {0}, params : {1}'.format(request.environ['werkzeug.request'], kwargs))
            result = func(*args, **kwargs)
            logger.info("RESPONSE, {0}".format(result))
            return result
        return decorator
    return wrapper
 
logger = gen_logger('studi')
cs

파이썬 코드 - decorator로 활용된 logger_decorator_with_params() 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
class Note(Resource):
 
    def __init__(self):
        pass
 
    @log.logger_decorator_with_params(log.logger)
    def get(self):
        try:
            result = sqlalchemy_orm.get_all_data_from_db(sqlalchemy_orm.Notes)
        except sqlalchemy.exc.SQLAlchemyError:
            error_message = util.traceback_custom_error()
            response = custom_error.SQLAlchemyError(error_message).to_dict()
            return response, 500
        except:
            error_message = util.traceback_custom_error()
            response = custom_error.UnExpectedError(error_message).to_dict()
            return response, 500
        else:
            if result:
                return {'notes' : result}, 200
            return {'notes': []}, 201
cs