经过一番思考和几次失败的尝试,我得出了以下答案。
首先,正如@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()
希望这个冗长的答案对某人有用。虽然我仍然不清楚以这种方式记录错误和信息消息是否被认为是糟糕的代码设计。