我正在将日志记录构建到一个Python应用程序中,我希望它是人类可读的。目前,调试日志记录了调用的每个函数以及参数和返回值。这意味着实际上,嵌套函数调用的调试日志可能如下所示:
2024-07-29 16:52:26,641: DEBUG: MainController.initialize_components called with args <controllers.main_controller.MainController(0x1699fdcdda0) at 0x000001699F793300>
2024-07-29 16:52:26,643: DEBUG: MainController.setup_connections called with args <controllers.main_controller.MainController(0x1699fdcdda0) at 0x000001699F793300>
2024-07-29 16:52:26,645: DEBUG: MainController.setup_connections returned None
2024-07-29 16:52:26,646: DEBUG: MainController.initialize_components returned None
我想通过使用Python风格的缩进来嵌套事物,通过阅读日志来使这一点变得明显。所以理想的输出是这样的:
2024-07-29 16:52:26,641: DEBUG: MainController.initialize_components called with args <controllers.main_controller.MainController(0x1699fdcdda0) at 0x000001699F793300>
2024-07-29 16:52:26,643: DEBUG: MainController.setup_connections called with args <controllers.main_controller.MainController(0x1699fdcdda0) at 0x000001699F793300>
2024-07-29 16:52:26,645: DEBUG: MainController.setup_connections returned None
2024-07-29 16:52:26,646: DEBUG: MainController.initialize_components returned None
我目前正在通过使用此装饰器包装类方法来实现我的文档:
导入功能工具 导入日志记录
def log(_func=None, *, logger):
def decorator_log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if logger.handlers:
current_formatter = logger.handlers[0].formatter
current_formatter.set_tabs(current_formatter.get_tabs() + 1)
self = args[0]
name = f'{self.__class__.__name__}.{func.__name__}'
if logger.root.level < logging.DEBUG:
logger.info(f"Entering {name}")
else:
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
logger.debug(f"{name} called with args {signature}")
try:
result = func(*args, **kwargs)
except Exception as e:
logger.exception(f"Exception raised in {name}: {str(e)}")
raise e
if logger.root.level < logging.DEBUG:
logger.info(f"Leaving {name}")
else:
logger.debug(f"{name} returned {result}")
if logger.handlers:
current_formatter = logger.handlers[0].formatter
current_formatter.set_tabs(current_formatter.get_tabs() - 1)
return result
return wrapper
if _func is None:
return decorator_log
else:
return decorator_log(_func)
我可以使用
tabs
向记录器添加属性 setattr
并在装饰器的开头处递增/在装饰器末尾处递减,但这仅将选项卡应用于输出的 message
部分,如下所示:
2024-07-29 16:52:26,641: DEBUG: MainController.initialize_components called with args <controllers.main_controller.MainController(0x1699fdcdda0) at 0x000001699F793300>
2024-07-29 16:52:26,643: DEBUG: MainController.setup_connections called with args <controllers.main_controller.MainController(0x1699fdcdda0) at 0x000001699F793300>
2024-07-29 16:52:26,645: DEBUG: MainController.setup_connections returned None
2024-07-29 16:52:26,646: DEBUG: MainController.initialize_components returned None
这总比没有好,但不是我想要的。我如何更新它(最好不使用全局变量)以在记录器输出的每行开头有变量缩进?
import functools
import logging
# Custom formatter to handle indentation
class IndentFormatter(logging.Formatter):
def __init__(self, fmt=None, datefmt=None, style='%', indent_char=' '):
super().__init__(fmt, datefmt, style)
self.indent_level = 0
self.indent_char = indent_char
def format(self, record):
# Add the current level of indentation to the log message
indent = self.indent_char * self.indent_level
original_msg = super().format(record)
indented_msg = "\n".join([indent + line for line in original_msg.split('\n')])
return indented_msg
def increase_indent(self):
self.indent_level += 1
def decrease_indent(self):
if self.indent_level > 0:
self.indent_level -= 1
# Decorator to log function calls with indentation
def log(_func=None, *, logger):
def decorator_log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if logger.handlers:
current_formatter = logger.handlers[0].formatter
if isinstance(current_formatter, IndentFormatter):
current_formatter.increase_indent()
self = args[0]
name = f'{self.__class__.__name__}.{func.__name__}'
if logger.root.level < logging.DEBUG:
logger.info(f"Entering {name}")
else:
args_repr = [repr(a) for a in args]
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
logger.debug(f"{name} called with args {signature}")
try:
result = func(*args, **kwargs)
except Exception as e:
logger.exception(f"Exception raised in {name}: {str(e)}")
raise e
if logger.root.level < logging.DEBUG:
logger.info(f"Leaving {name}")
else:
logger.debug(f"{name} returned {result}")
if logger.handlers:
current_formatter = logger.handlers[0].formatter
if isinstance(current_formatter, IndentFormatter):
current_formatter.decrease_indent()
return result
return wrapper
if _func is None:
return decorator_log
else:
return decorator_log(_func)
# Example logger setup
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
formatter = IndentFormatter('%(asctime)s: %(levelname)s: %(message)s', '%Y-%m-%d %H:%M:%S')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
# Example class with decorated methods
class MainController:
@log(logger=logger)
def initialize_components(self):
self.setup_connections()
@log(logger=logger)
def setup_connections(self):
pass
if __name__ == "__main__":
controller = MainController()
controller.initialize_components()