首页 > 编程问答 >在python日志输出的每一行前面添加变量缩进

在python日志输出的每一行前面添加变量缩进

时间:2024-07-30 06:30:45浏览次数:16  
标签:python python-logging

我正在将日志记录构建到一个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

我目前通过使用这个装饰器包装类方法来实现我的文档:

import functools
import logging

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

这比没有好,但不完全是我想要的。如何更新它(最好不使用全局变量)以在记录器输出的每行开头处具有变量缩进?


要实现Python日志输出中每行开头的可变缩进,你可以使用一个全局变量来跟踪缩进级别,并在自定义格式化程序中使用它。

这是一个示例:

import logging
import functools

# 全局缩进级别
INDENT_LEVEL = 0

class IndentedFormatter(logging.Formatter):
    def __init__(self, fmt=None, datefmt=None, style='%'):
        super().__init__(fmt, datefmt, style)

    def format(self, record):
        # 在每行消息前面添加缩进
        message = super().format(record)
        indented_message = '\t' * INDENT_LEVEL + message
        return indented_message

def log(_func=None, *, logger=None):
    if logger is None:
        logger = logging.getLogger()  # 获取默认的根记录器

    def decorator_log(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            global INDENT_LEVEL
            INDENT_LEVEL += 1

            self = args[0]
            name = f'{self.__class__.__name__}.{func.__name__}'
            logger.debug(f"Entering {name}")
            try:
                result = func(*args, **kwargs)
            except Exception as e:
                logger.exception(f"Exception raised in {name}: {str(e)}")
                raise e
            logger.debug(f"{name} returned {result}")

            INDENT_LEVEL -= 1
            return result
        return wrapper
    if _func is None:
        return decorator_log
    else:
        return decorator_log(_func)

# 创建一个记录器并设置格式化程序
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
formatter = IndentedFormatter('%(asctime)s: %(levelname)s: %(message)s', '%Y-%m-%d %H:%M:%S')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

# 示例用法
class MyClass:
    @log(logger=logger)
    def my_method(self, arg1, arg2):
        logger.debug(f"my_method called with args: {arg1}, {arg2}")
        self.my_inner_method()

    @log(logger=logger)
    def my_inner_method(self):
        logger.debug("my_inner_method called")

# 创建类的实例并调用方法
my_class = MyClass()
my_class.my_method(1, 2)

这将产生以下输出:

2024-07-29 17:22:05: DEBUG: Entering MyClass.my_method
    2024-07-29 17:22:05: DEBUG: my_method called with args: 1, 2
    2024-07-29 17:22:05: DEBUG: Entering MyClass.my_inner_method
        2024-07-29 17:22:05: DEBUG: my_inner_method called
    2024-07-29 17:22:05: DEBUG: MyClass.my_inner_method returned None
2024-07-29 17:22:05: DEBUG: MyClass.my_method returned None

说明:

  • 我们定义了一个全局变量 INDENT_LEVEL 来跟踪当前的缩进级别。
  • 我们创建了一个自定义格式化程序 IndentedFormatter ,它使用 INDENT_LEVEL 变量在每行消息前面添加缩进。
  • log 装饰器增加了 INDENT_LEVEL ,记录消息,然后减少 INDENT_LEVEL
  • 我们将自定义格式化程序设置为记录器的处理程序。

现在,每次调用装饰的函数时,日志消息都会缩进以指示嵌套级别。

注意: 虽然全局变量在简单情况下有效,但在处理多线程应用程序时可能会导致问题。对于更健壮的解决方案,请考虑使用上下文管理器或基于线程的存储来管理缩进级别。

标签:python,python-logging
From: 78809025

相关文章

  • 使用 DQN 实现 pong,使用 python 中的特征向量而不是像素。我的 DQNA 实现代码正确吗,因
    我正在致力于使用OpenAI的Gym为Pong游戏实现强化学习(RL)环境。目标是训练人工智能代理通过控制球拍来打乒乓球。代理收到太多负面奖励,即使它看起来移动正确。具体来说,奖励函数会惩罚远离球的智能体,但这种情况发生得太频繁,即使球朝球拍移动时似乎也会发生。观察......
  • Python CDLL 无法加载两次
    我正在尝试用python创建一个密码管理器,但遇到了一个问题,一旦加载了一种类型的dll,我就无法加载不同的dll,在这个示例中,我加载了一个dll,并尝试解密加密的密码数据,它工作正常,直到我加载另一个不同的nss3.dll文件,此时它给我一个错误:“过程入口点HeapAlloc无法位于动态链......
  • 你能将 HTTPS 功能添加到 python Flask Web 服务器吗?
    我正在尝试构建一个Web界面来模拟网络设备上的静态接口,该网络设备使用摘要式身份验证和HTTPS。我想出了如何将摘要式身份验证集成到Web服务器中,但我似乎无法找到如何使用FLASK获取https,如果您可以向我展示如何实现,请评论我需要使用下面的代码做什么来实现这一点。from......
  • Python:比较 csv 文件并打印相似之处
    我需要比较两个csv文件并打印出它们的相似之处。第一个文件有名称和浓度,第二个文件就像只有名称的“最佳”列表,我需要绘制相似性图表。例如,这就是我的列表的样子:file1-old_file.csvname_id,conc_test1,conc_test2name1,####,####name2,###......
  • Python 类交叉引用
    我用Python创建了一个数独游戏。我有一个:单元格类-“保存”数字可能性单元格组-保存单元格类实例我使用这些组在数独中运行行、列和正方形功能。每个单元格包含所有组,他属于classCell:def__init__(groups):self.groups=groupscla......
  • 如何修复我的 Python Azure Function DevOps Pipeline 上的“找到 1 个函数(自定义)加载
    我正在尝试使用AzureDevOps构建管道将PythonAzureFunction部署到Azure门户。由于某种原因,代码被部署到服务器,但我在尝试访问端点时收到404错误。我收到一个错误,显示1functionsfound(Custom)0functionsloaded,以及在服务器上显示ModuleNotFound......
  • 使用 kivy 从 python 脚本的 buildozer 构建 android apk 时出错
    我想从使用kivy包构建的Python脚本构建apk为此,我使用googlecollab.这里是main.py脚本:importyoutube_dlfromkivy.appimportAppfromkivy.uix.boxlayoutimportBoxLayoutfromkivy.uix.buttonimportButtonfromkivy.uix.tex......
  • 自动解码并检索 S/MIME 加密电子邮件的正文 (python)
    我如何:用python代码连接我的邮件收件箱以自动获取未读电子邮件的加密内容;解码S/MIME加密电子邮件(我有密钥);检索电子邮件正文纯文本;检查正文(或主题)是否与某个关键字(现在为“test”)匹配,并在匹配时打印一些内容;在树莓派上使用此代码,无需手动......
  • Python 3 写入 DBF(带有 Memo 的 dBase IV)
    我需要在Python3中写入带有备注字段的dBaseIVdbf文件,但找不到合适的模块来执行此操作。我尝试过的包:Simpledbf-只读dbf-不支持dBaseIVdbfpy-不支持Python3dbfpy3-不支持dBaseIVYDbf-不支持备注字段pyshp-无法仅使用dbf文件......
  • Robin-Stocks Python 中的 order_buy_fractional_by_price 问题
    我在Robin-StocksPython包中的order_buy_fractional_by_price函数中遇到问题。在正常市场交易时间内下达以美元为基础的买入订单时,该订单被错误地设置为限价订单。对我来说看起来有问题的代码似乎是导致此问题的原因。我尝试在包管理器中本地修改或删除有问题的代码,但遇......