子类化 logging.Logger 以添加自己的功能

Subclassing logging.Logger to add own functionality

我正在为需要记录到不同位置的机器人系统编写代码,具体取决于启动期间 deployment/time 的类型/...

我希望有一个选项来创建一个基本的记录器,然后在适当的时候添加处理程序。

我有一个创建流处理程序的基本功能:

def setup_logger() -> logging.Logger:
    """Setup logging.
    Returns logger object with (at least) 1 streamhandler to stdout.

    Returns:
        logging.Logger: configured logger object
    """
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    stream_handler = logging.StreamHandler()  # handler to stdout
    stream_handler.setLevel(logging.ERROR)
    stream_handler.setFormatter(MilliSecondsFormatter(LOG_FMT))
    logger.addHandler(stream_handler)
    return logger

当系统可以访问 Internet 时,我想添加一个邮件处理程序(单独 class,从 logging.handlers.BufferingHandler 子class 编辑。 (下面的例子用一个简单的旋转文件处理程序来简化)

def add_rotating_file(logger: logging.Logger) -> logging.Logger:
    rot_fil_handler = logging.handlers.RotatingFileHandler(LOGFILE,
                                                           maxBytes=LOGMAXBYTES,
                                                           backupCount=3)
    rot_fil_handler.setLevel(logging.DEBUG)
    rot_fil_handler.setFormatter(MilliSecondsFormatter(LOG_FMT))
    logger.addHandler(rot_fil_handler)
    return logger

用法为:

logger = setup_logger()
logger = add_rotating_file(logger)

我觉得这看起来“不对”。将记录器作为参数提供给函数然后返回它似乎很奇怪,我想我最好创建一个 class、subclassing logging.Logger.

所以像这样:

class pLogger(logging.Logger):

    def __init__(self):
        super().__init__()
        self._basic_configuration()

    def _basic_configuration(self):
        self.setLevel(logging.DEBUG)
        stream_handler = logging.StreamHandler()  # handler to stdout
        stream_handler.setLevel(logging.ERROR)
        stream_handler.setFormatter(MilliSecondsFormatter(LOG_FMT))
        self.addHandler(stream_handler)

    def add_rotating_handler(self):
        rot_file_handler = logging.handlers.RotatingFileHandler(LOGFILE,
                                                            maxBytes=LOGMAXBYTES,
                                                            backupCount=3)
        self.addHandler(rot_file_handler)

                                                

但是,super().init() 函数需要记录器名称作为参数,而且据我所知,应使用 logging.getLogger(), 所以没有名字.

另一种方法是子class任何东西,但是在我的class中创建一个self.logger,这似乎也是错误的.

我发现 似乎相关,但我不知道如何解释答案。

执行此操作的“正确”方法是什么?

如果您觉得这很奇怪,那么从 add_rotating_file() 返回记录器没有特别的原因。这(根据条件添加处理程序)似乎不是创建记录器子类的理由。有许多方法可以安排一些基本的处理程序和一些基于其他条件的附加处理程序,但做这样的事情似乎最简单:

def setup_logger() -> logging.Logger:
    formatter = MilliSecondsFormatter(LOG_FMT)
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    handler = logging.StreamHandler(sys.stdout)  # default is stderr
    handler.setLevel(logging.ERROR)
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    if internet_is_available:
        handler = MyCustomEmailHandler(...)  # with whatever params you need
        handler.setLevel(...)
        handler.setFormatter(...) # a suitable formatter instance
        logger.addHandler(handler)
    if rotating_file_wanted:
        handler = RotatingFileHandler(LOGFILE,
                                      maxBytes=LOGMAXBYTES,
                                      backupCount=3)
        handler.setLevel(...)
        handler.setFormatter(...) # a suitable formatter instance
        logger.addHandler(handler)
    # and so on for other handlers
    return logger # and you don't even need to do this - you could pass the logger in instead

`