【问题标题】:Logging an exception if and only if it was not caught later by try and except当且仅当它后来没有被 try 和 except 捕获时才记录异常
【发布时间】:2019-12-07 09:34:57
【问题描述】:

我有一个类,我在其中记录错误并引发错误。但是,我有不同的函数会出现该错误。

现在,即使这些功能除了错误正确,它仍然被记录下来。这会导致混乱的日志文件,其中可以看到多个冲突条目。例如:

import logging
logging.basicConfig(filename = "./log")
logger = logging.getLogger()
logger.setLevel(logging.INFO)

class Foo:

    def __init__(self):
        pass

    def foo_error(self):
        logger.error("You have done something very stupid!")
        raise RuntimeError("You have done something very stupid!")

    def foo_except(self):
        try:
            self.foo_error()
        except RuntimeError as error: 
            logger.info("It was not so stupid after all!") 


Foo = Foo()
Foo.foo_except()

在这里,两条消息都显示在 "./log" 中。最好是,如果稍后捕获到第一条错误日志消息,我想取消它。

我在其他任何地方都没有看到答案。也许我做事的方式暗示了糟糕的设计。有什么想法吗?

【问题讨论】:

    标签: python logging try-catch


    【解决方案1】:

    您不能真正询问 Python 是否会在稍后捕获异常。所以你唯一的选择是只有在你知道异常是否被捕获后才记录。

    一种可能的解决方案(虽然我不确定这是否适用于您的情况):

    import logging
    logging.basicConfig(filename = "./log")
    logger = logging.getLogger()
    
    class Foo:
    
        def __init__(self):
            pass
    
        def foo_error(self):
            # logger.error("You have done something very stupid!")
            raise RuntimeError("You have done something very stupid!")
    
        def foo_except(self):
            try:
                self.foo_error()
            except RuntimeError as error:
                logger.warning("It was not so stupid after all!")
    
    try:
        Foo = Foo()
        Foo.foo_except()
        Foo.foo_error()
    except Exception as exc:
        if isinstance(exc, RuntimeError):
            logger.error("%s",  exc)
        raise
    
    

    【讨论】:

      【解决方案2】:

      经过一番思考和几次失败的尝试,我得出了以下答案。

      首先,正如@gelonida 提到的:

      你不能真正询问 Python 以后是否会捕获异常。

      这意味着必须写入同样引发异常的日志条目,因为如果稍后未捕获异常,则日志条目将被清除并从文件中丢失。

      因此,与其尝试控制将哪个日志消息写入文件,我们应该实现一种从文件中删除无效日志消息的方法。

      import logging
      logging.basicConfig(filename = "./log")
      logger = logging.getLogger()
      logger.setLevel(logging.INFO)
      
      class Foo:
      
          def __init__(self):
              pass
      
          def foo_error(self):
              logger.error("You have done something very stupid!")
              raise RuntimeError("You have done something very stupid!")
      
          def foo_except(self):
              try:
                  self.foo_error()
              except RuntimeError as error: 
                  logger.info("It was not so stupid after all!") 
      
      
      Foo = Foo()
      Foo.foo_except()
      

      按照这个逻辑,我们应该将原始示例中logger.info("It was not so stupid after all!") 行上方的函数替换为删除最后提交的日志消息并记录正确消息的函数!

      实现此目的的一种方法是修改日志记录类并添加两个组件。即一个日志记录历史记录和一个支持删除日志记录的FileHandler。让我们从日志记录历史开始。

      class RecordHistory:
          def __init__(self):
              self._record_history = []
      
          def write(self, record):
              self._record_history.append(record)
      
          def flush(self):
              pass
      
          def get(self):
              return self._record_history[-1]
      
          def pop(self):
              return self._record_history.pop()
      

      这基本上是一个数据容器,它实现了 write 和 flush 方法以及其他一些便利。 logging.StreamHandler 需要 write 和 flush 方法。欲了解更多信息,请访问logging.handlersdocumentation

      接下来,我们修改现有的logging.FileHandler 以支持revoke 方法。该方法允许我们删除特定的日志记录。

      import re
      
      class RevokableFileHandler(logging.FileHandler):
          def __init__(self, *args, **kwargs):
              super().__init__(*args, **kwargs)
      
          def revoke(self, record):
              with open(self.baseFilename, mode="r+") as log:
                  substitute = re.sub(rf"{record}", "", log.read(), count=1)
                  log.seek(0)
                  log.write(substitute)
                  log.truncate()
      

      最后,我们修改Logger 类。但是请注意,我们不能直接从logging.Logger 继承,如here 所述。此外,我们添加了一个logging.StreamHandler,它将日志记录推送到我们的RecordHistory 对象。我们还实现了一个addRevokableHandler 方法,它注册了所有支持撤销记录的处理程序。

      import logging
      
      class Logger(logging.getLoggerClass()):
          def __init__(self, name):
              super().__init__(name)
      
              self.revokable_handlers = []
              self.record_history = RecordHistory()
      
              stream_handler = logging.StreamHandler(stream=self.record_history)
              stream_handler.setLevel(logging.INFO)
      
              self.addHandler(stream_handler)
      
          def addRevokableHandler(self, handler):
              self.revokable_handlers.append(handler)
              super().addHandler(handler)
      
          def pop_and_log(self, level, msg):
              record = self.record_history.pop()
              for handler in self.revokable_handlers:
                  handler.revoke(record)
              self.log(level, msg)
      

      这导致原始代码中的解决方案如下:

      logging.setLoggerClass(Logger)
      
      logger = logging.getLogger("root")
      logger.setLevel(logging.INFO)
      
      file_handler = RevokableFileHandler("./log")
      file_handler.setLevel(logging.INFO)
      logger.addRevokableHandler(file_handler)
      
      
      class Foo:
          def __init__(self):
              pass
      
          def foo_error(self):
              logger.error("You have done something very stupid!")
              raise RuntimeError("You have done something very stupid!")
      
          def foo_except(self):
              try:
                  self.foo_error()
              except KeyError as error:
                  logger.pop_and_log(logging.INFO, "It was not so stupid after all!")
      
      
      Foo = Foo()
      Foo.foo_except()
      

      希望这个冗长的答案对某人有用。虽然我仍然不清楚以这种方式记录错误和信息消息是否被认为是糟糕的代码设计。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-13
        • 1970-01-01
        • 2015-04-22
        • 1970-01-01
        • 2020-05-09
        相关资源
        最近更新 更多