Как сделать чистое ведение журнала, не заставляя код выглядеть ужасно? - PullRequest
7 голосов
/ 22 апреля 2019

Я пытаюсь написать чистый код, поэтому я не хочу, чтобы мой код был загрязнен случайной регистрацией. Это для меня выглядит ужасно:

def generate_auth_token(self):
    logger.debug("Existing auth token: {}".format(self.auth_token))
    self.auth_token = uuid.uuid4()
    self.save()
    logger.debug("Updated auth token:  {}".format(self.auth_token))
    return str(self.auth_token)

Журналы выглядят так:

backend-api_1  | DEBUG: Device token: 66f4b515-c6f5-433c-885f-61c8a1f63ce5
backend-api_1  | Debug: Existing auth token: 66f4b515-c6f5-433c-885f-61c8a1f63ce5
backend-api_1  | Debug: Updated auth token: 66f4b515-c6f5-433c-885f-61c8a1f63ce5

Просто сложно читать такой код. У меня появилась идея регистрировать каждую функцию с помощью декоратора.

def log_input_output(logger_name='', func_name=None, log_input_values=True):
  logger = logging.getLogger(logger_name)

  def _log_input_output_decorator(func):
    if not func_name:
      message_prefix = f'{func.__name__}'
    else:
      message_prefix = f'{func_name}'

    @wraps(func)
    def wrapper(*args, **kwargs):
      logger.info(f'{message_prefix} started')
      if log_input_values:
        if args:
          logger.debug(f'{message_prefix} input args: {args}')
        if kwargs:
          logger.debug(f'{message_prefix} input kwargs: {kwargs}')

      try:
        result = func(*args, **kwargs)
      except Exception as e:
        logger.error(f'{message_prefix} error: {e}')
        raise e

      logger.info(f'{message_prefix} finished successfully')
      logger.debug(f'{message_prefix} finished with result: {result}')
      return result

    return wrapper
  return _log_input_output_decorator

Пример сверху, теперь выглядит намного чище

  @log_input_output(logger_name=__name__)
  def generate_auth_token(self):
        self.auth_token = uuid.uuid4()
        self.save()
        return str(self.auth_token)

но журналы менее чистые

backend-api_1  | INFO: generate_auth_token started
backend-api_1  | DEBUG: generate_auth_token input args: (<self object at 0x7fc18085d1c8>)
backend-api_1  | INFO: generate_auth_token finished successfully
backend-api_1  | DEBUG: generate_auth_token finished with result: 66f4b515-c6f5-433c-885f-61c8a1f63ce5

В этом случае нам также не хватает старого значения токена. Мне просто интересно, есть ли правильный способ ведения журнала, чтобы код не выглядел как беспорядок, но в то же время журналы были бы более читабельными?

1 Ответ

1 голос
/ 22 апреля 2019

Одним из возможных решений является добавление __repr__ в ваш класс, который будет возвращать то, что вы хотите записать:

def __repr__(self):
    return str(self.auth_token)

Это решение не является гибким, потому что если вы хотите регистрировать self.auth_token в одной функции и self.other_attribute в другой, вам необходимо регистрировать их оба каждый раз:

def __repr__(self):
    return str(self.auth_token, self.other_attribute)

Это запутает ваши логи def generate_auth_token(self):, потому что вы хотите только логи self.auth_token

Другое решение, которое я могу предложить, - использовать eval. Я знаю, что eval это зло, но, возможно, это подходящее место для его использования. Изменения в вашем коде:

1) Декоратор будет использоваться следующим образом:

@log_input_output(logger_name=__name__, log_eval='f"token is {self.auth_token}"')

2) параметр log_eval:

def log_input_output(logger_name='', func_name=None, log_input_values=True, log_eval=''):

3) if log_eval блок после if kwargs:

    if kwargs:
      logger.debug(f'{message_prefix} input kwargs: {kwargs}')
    if log_eval:
      signature = inspect.signature(func)
      bind = signature.bind(*args, **kwargs)
      custom_log = eval(log_eval, bind.arguments)
      logger.debug(f'{message_prefix} custom log: {custom_log}')

Итак, журнал будет выглядеть так:

generate_auth_token custom log: token is d0173388-f043-4e8b-8630-05634e2fd3c6

С этим решением вы можете регистрировать только self.other_attribute при выполнении другой функции:

@log_input_output(logger_name=__name__, log_eval='f"other_attribute is {self.other_attribute}"')
def other_function(self):
    pass
...