我为其他人开发了几个Python程序,这些程序使用Tkinter接收用户的输入。为了保持简单和用户友好,命令行或python控制台永远不会被打开(即使用.pyw文件),所以我正在考虑在发生异常时使用日志库将错误文本写入文件。然而,我很难让它真正捕捉到异常。例如:
我们编写了一个会导致错误的函数:
def cause_an_error():
a = 3/0
现在我们尝试正常记录错误:
import logging
logging.basicConfig(filename='errors.log', level=logging.ERROR)
try:
cause_an_error()
except:
logging.exception('simple-exception')
不出所料,程序会出错,日志记录会将错误写入errors.log。控制台中不会显示任何内容。然而,当我们实现Tkinter接口时,会有不同的结果,比如:
import logging
import Tkinter
logging.basicConfig(filename='errors.log', level=logging.ERROR)
try:
root = Tkinter.Tk()
Tkinter.Button(root, text='Test', command=cause_an_error).pack()
root.mainloop()
except:
logging.exception('simple-exception')
在这种情况下,按下Tkinter窗口中的按钮会导致错误。然而,这一次,文件中没有写入任何内容,控制台中出现以下错误:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:Python27liblib-tkTkinter.py", line 1536, in __call__
return self.func(*args)
File "C:/Users/samk/Documents/GitHub/sandbox/sandbox2.pyw", line 8, in cause_an_error
a = 3/0
ZeroDivisionError: integer division or modulo by zero
是否有其他方法可以捕获并记录此错误?
虽然没有很好的文档,但tkinter会为回调导致的异常调用一个方法。通过在根窗口上设置属性report_callback_exception
,您可以编写自己的方法来执行任何您想要的操作。
例如:
import tkinter as tk
def handle_exception(exception, value, traceback):
print("Caught exception:", exception)
def raise_error():
raise Exception("Sad trombone!")
root = tk.Tk()
# setup custom exception handling
root.report_callback_exception=handle_exception
# create button that causes exception
b = tk.Button(root, text="Generate Exception", command=raise_error)
b.pack(padx=20, pady=20)
root.mainloop()
供参考:
- http://epydoc.sourceforge.net/stdlib/Tkinter.Tk-class.html#report_callback_exception
由于这个问题是关于日志记录和明确错误发生的位置,我将详细介绍Bryan(完全正确)的答案。
首先,您必须导入一些其他有用的模块。
import logging
import functools # for the logging decorator
import traceback # for printing the traceback to the log
然后,您必须配置记录器和日志记录功能:
logging.basicConfig(filename='/full/path/to_your/app.log',
filemode='w',
level=logging.INFO,
format='%(levelname)s: %(message)s')
def log(func):
# logging decorator
@functools.wraps(func)
def wrapper_log(*args, **kwargs):
msg = ' '.join(map(str, args))
level = kwargs.get('level', logging.INFO)
if level == logging.INFO:
logging.info(msg)
elif level == logging.ERROR:
logging.exception(msg)
traceback.print_exc()
return wrapper_log
@log
def print_to_log(s):
pass
请注意,执行日志记录的函数实际上是log
,您可以使用它将错误和常规信息打印到日志文件中。如何使用:函数print_to_log
用log
装饰。您可以放置一些常规的print()
,而不是pass
,这样您就可以将消息保存到日志中,并将其打印到控制台。您可以用您喜欢的任何命令替换pass
。
注nb。2我们使用log
函数中的回溯来跟踪您的代码到底在哪里生成了错误。
要处理异常,请按照已接受的答案进行操作。另外,您将消息(即回溯)传递给print_to_log
函数:
def handle_exception(exc_type, exc_value, exc_traceback):
# prints traceback in the log
message = ''.join(traceback.format_exception(exc_type,
exc_value,
exc_traceback))
print_to_log(message)
配置了所有这些之后,现在您告诉您的Tkinter应用程序,它必须使用handle_exception
功能:
class App(tk.Tk):
# [the rest of your app code]
if __name__ == '__main__':
app = App()
app.report_callback_exception = handle_exception
app.mainloop()
或者,如果您使用多个类,您甚至可以指示类本身使用andle_exception
函数:
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.report_callback_exception = handle_exception
# [the rest of your app code]
if __name__ == '__main__':
app = App()
app.mainloop()
这样,你的应用程序代码看起来很瘦,你不需要任何try/except方法,你可以在信息级别记录应用程序中的任何事件。