如何在不使代码看起来糟糕的情况下进行干净的日志记录?

Daw*_* K. 7 python django optimization logging

我正在尝试编写干净的代码,所以我不希望我的代码被随机记录污染。这对我来说看起来很糟糕:

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)
Run Code Online (Sandbox Code Playgroud)

日志如下所示:

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
Run Code Online (Sandbox Code Playgroud)

很难读取这样的代码。我想到了使用装饰器记录所有功能的想法。

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
Run Code Online (Sandbox Code Playgroud)

上面的例子,现在看起来更干净了

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
Run Code Online (Sandbox Code Playgroud)

但是原木不太干净

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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们还缺少旧的令牌值。我只是想知道,是否有适当的日志记录方式,因此代码看起来不会像混乱,但同时日志更易读?

san*_*ash 1

一种可能的解决方案是添加__repr__到您的类中,该类将返回您想要记录的内容:

def __repr__(self):
    return str(self.auth_token)
Run Code Online (Sandbox Code Playgroud)

这种解决方案并不灵活,因为如果您想登录self.auth_token一个函数并self.other_attribute登录另一个函数,则每次都需要登录它们:

def __repr__(self):
    return str(self.auth_token, self.other_attribute)
Run Code Online (Sandbox Code Playgroud)

它会弄乱你的日志,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}"')
Run Code Online (Sandbox Code Playgroud)

2)参数log_eval

def log_input_output(logger_name='', func_name=None, log_input_values=True, log_eval=''):
Run Code Online (Sandbox Code Playgroud)

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}')
Run Code Online (Sandbox Code Playgroud)

因此,日志将如下所示:

generate_auth_token custom log: token is d0173388-f043-4e8b-8630-05634e2fd3c6
Run Code Online (Sandbox Code Playgroud)

使用此解决方案,您只能self.other_attribute在执行其他函数时记录:

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