การบันทึกข้อยกเว้นที่ไม่ได้ตรวจสอบใน Python


181

คุณจะทำให้เกิดข้อยกเว้นที่ไม่ได้ตรวจสอบเพื่อเอาท์พุทผ่านloggingโมดูลแทนที่จะทำstderrอย่างไร

ฉันรู้วิธีที่ดีที่สุดในการทำเช่นนี้คือ:

try:
    raise Exception, 'Throwing a boring exception'
except Exception, e:
    logging.exception(e)

แต่สถานการณ์ของฉันเป็นเช่นนั้นมันจะดีจริงๆถ้าlogging.exception(...)ถูกเรียกโดยอัตโนมัติเมื่อใดก็ตามที่ข้อยกเว้นไม่ได้ถูกจับ



คำตอบ:


143

ดังที่เน็ดชี้ให้เห็นsys.excepthookถูกเรียกใช้ทุกครั้งที่มีการยกและยกเว้นข้อยกเว้น ความหมายเชิงปฏิบัติของสิ่งนี้คือในรหัสของคุณคุณสามารถแทนที่พฤติกรรมเริ่มต้นของsys.excepthookการทำสิ่งที่คุณต้องการ (รวมถึงการใช้logging.exception)

เป็นตัวอย่างฟางคน:

>>> import sys
>>> def foo(exctype, value, tb):
...     print 'My Error Information'
...     print 'Type:', exctype
...     print 'Value:', value
...     print 'Traceback:', tb
... 

แทนที่sys.excepthook:

>>> sys.excepthook = foo

ยอมรับข้อผิดพลาดทางไวยากรณ์ที่ชัดเจน (ออกจากโคลอน) และรับข้อมูลข้อผิดพลาดที่กำหนดเองกลับมา:

>>> def bar(a, b)
My Error Information
Type: <type 'exceptions.SyntaxError'>
Value: invalid syntax (<stdin>, line 1)
Traceback: None

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการsys.excepthookอ่านเอกสาร


4
@Codemonkey ไม่ใช่คำหลักที่สงวนไว้ แต่เป็นชื่อประเภทที่มีอยู่แล้ว คุณสามารถใช้typeเป็นอาร์กิวเมนต์ฟังก์ชันแม้ว่า IDEs จะบ่นเกี่ยวกับการซ่อนโกลบอลtype(เหมือนกับการใช้var self = thisใน Javascript) ไม่สำคัญเว้นแต่คุณจะต้องเข้าถึงtypeวัตถุในฟังก์ชันของคุณซึ่งในกรณีนี้คุณสามารถใช้type_เป็นอาร์กิวเมนต์แทน
Ryan P

3
วลี"ทุกครั้ง"นี่เป็นความเข้าใจผิด :: "sys.excepthook จะเรียกทุกครั้งยกเว้นจะยกขึ้นและไม่ถูกตรวจจับ" ... เพราะในโปรแกรมมีสามารถ ว่าหนึ่ง "ไม่ถูกตรวจจับ" ข้อยกเว้น นอกจากนี้sys.excepthookจะไม่ถูกเรียกเมื่อข้อยกเว้น "ยก" มันถูกเรียกเมื่อโปรแกรมกำลังจะยุติเนื่องจากข้อยกเว้นที่ไม่ได้ตรวจสอบซึ่งไม่สามารถเกิดขึ้นได้มากกว่าหนึ่งครั้ง
Nawaz

2
@Nawaz: มันสามารถเกิดขึ้นได้มากกว่าหนึ่งครั้งใน REPL
jfs

2
@Nawaz นอกจากนี้ยังสามารถเกิดขึ้นได้หลายครั้งหากโปรแกรมใช้เธรด ฉันยังดูเหมือน GUI เหตุการณ์ลูป (เช่น Qt) ยังคงทำงานแม้ว่าข้อยกเว้นทำให้ sys.excepthook
three_pineapples

1
ใครก็ตามที่พยายามทดสอบโค้ดด้านบนตรวจสอบให้แน่ใจว่าคุณสร้างข้อผิดพลาดในการย้อนกลับขณะทดสอบฟังก์ชันของคุณ SyntaxError ไม่ได้รับการจัดการโดย sys.excepthook คุณสามารถใช้การพิมพ์ (1/0) และสิ่งนี้จะเรียกใช้ฟังก์ชันที่คุณกำหนดไว้เพื่อแทนที่ sys.excepthook
Parth Karia

177

นี่คือตัวอย่างเล็ก ๆ ที่สมบูรณ์ที่ยังมีลูกเล่นอื่น ๆ :

import sys
import logging
logger = logging.getLogger(__name__)
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)

def handle_exception(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))

sys.excepthook = handle_exception

if __name__ == "__main__":
    raise RuntimeError("Test unhandled")
  • ละเว้น KeyboardInterrupt ดังนั้นโปรแกรมของ python ในคอนโซลสามารถออกได้ด้วย Ctrl + C

  • พึ่งพาโมดูลการบันทึกของ python ทั้งหมดสำหรับการจัดรูปแบบข้อยกเว้น

  • ใช้ตัวบันทึกที่กำหนดเองพร้อมกับตัวจัดการตัวอย่าง อันนี้เปลี่ยนข้อยกเว้นที่ไม่สามารถจัดการได้เพื่อไปที่ stdout แทนที่จะเป็น stderr แต่คุณสามารถเพิ่มตัวจัดการทุกประเภทในลักษณะเดียวกันนี้ให้กับวัตถุตัวบันทึก


13
ฉันจะใช้logger.critical()ภายในตัวจัดการ excepthook เนื่องจากข้อยกเว้นที่ไม่ได้ตรวจสอบนั้นสำคัญมากที่ฉันจะบอก
gitaarik

2
นี่คือคำตอบที่ใช้งานได้จริง IMO
เดวิดโมราเลส

@gnu_lorien ขอบคุณสำหรับตัวอย่าง คุณจะวางไฟล์นี้ไว้ในไฟล์ไหน?
stelios

@chefarov ไฟล์หลักที่คุณเริ่มต้นการบันทึกอื่น ๆ ทั้งหมด
gnu_lorien

สวัสดีเราจะเขียนไฟล์เช่น debug.log ข้อมูลนี้ได้อย่างไร ฉันลองเพิ่มบรรทัดlogging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')แต่ก็ไม่ได้ช่วย
GurhanCagin

26

วิธีการsys.excepthookนี้จะถูกเรียกใช้หากไม่ได้รับการยกเว้น: http://docs.python.org/library/sys.html#sys.excepthook

เมื่อมีการยกและยกเว้นข้อยกเว้นล่ามจะเรียก sys.excepthook โดยมีอาร์กิวเมนต์สามตัวคลาสยกเว้นข้อยกเว้นอินสแตนซ์และวัตถุติดตามย้อนกลับ ในเซสชันแบบโต้ตอบสิ่งนี้จะเกิดขึ้นก่อนที่การควบคุมจะถูกส่งกลับไปที่พรอมต์ ในโปรแกรม Python สิ่งนี้จะเกิดขึ้นก่อนที่โปรแกรมจะออก การจัดการข้อยกเว้นระดับบนสุดดังกล่าวสามารถปรับแต่งได้โดยการกำหนดฟังก์ชั่นสามข้อโต้แย้งอีกครั้งเพื่อ sys.excepthook


2
ทำไมมันส่งคลาสยกเว้น? คุณไม่สามารถรับสิ่งนั้นได้ด้วยการโทรหาtypeอินสแตนซ์
Neil G

พารามิเตอร์ประเภทsys.excepthookใด
Martin Thoma

23

ทำไมจะไม่ล่ะ:

import sys
import logging
import traceback

def log_except_hook(*exc_info):
    text = "".join(traceback.format_exception(*exc_info))
    logging.error("Unhandled exception: %s", text)

sys.excepthook = log_except_hook

None()

นี่คือผลลัพธ์ที่มีsys.excepthookตามที่เห็นด้านบน:

$ python tb.py
ERROR:root:Unhandled exception: Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

นี่คือผลลัพธ์ที่มีsys.excepthookความคิดเห็น:

$ python tb.py
Traceback (most recent call last):
  File "tb.py", line 11, in <module>
    None()
TypeError: 'NoneType' object is not callable

ข้อแตกต่างเพียงอย่างเดียวคืออดีตมีERROR:root:Unhandled exception:ที่จุดเริ่มต้นของบรรทัดแรก


ข้อแตกต่างอีกอย่างคือเมื่อก่อนเขียนการติดตามไปยังระบบการบันทึกดังนั้นตัวจัดการและฟอร์แมตที่คุณติดตั้งจะถูกนำไปใช้ sys.stderrเขียนหลังโดยตรงกับ
radiaph

8

ในการสร้างคำตอบของ Jacinda แต่ใช้วัตถุตัวบันทึก:

def catchException(logger, typ, value, traceback):
    logger.critical("My Error Information")
    logger.critical("Type: %s" % typ)
    logger.critical("Value: %s" % value)
    logger.critical("Traceback: %s" % traceback)

# Use a partially applied function
func = lambda typ, value, traceback: catchException(logger, typ, value, traceback)
sys.excepthook = func

2
มันจะดีกว่าที่จะใช้functools.partial()แทนแลมบ์ดา ดู: docs.python.org/2/library/functools.html#functools.partial
Mariusz Jamro

@MariuszJamro ทำไม
davr

4

ปิดการเรียกรายการแอปของคุณในtry...exceptบล็อกเพื่อให้คุณสามารถจับและบันทึก (และอาจเพิ่มอีกครั้ง) ข้อยกเว้นที่ไม่ได้ตรวจสอบทั้งหมด เช่นแทนที่จะเป็น:

if __name__ == '__main__':
    main()

ทำเช่นนี้:

if __name__ == '__main__':
    try:
        main()
    except Exception as e:
        logger.exception(e)
        raise

นี่ไม่ใช่คำถามที่ถาม ความตั้งใจของคำถามคือถามว่าจะทำอย่างไรเมื่อไม่มีการจัดการข้อยกเว้นด้วยรหัส
Mayank Jaiswal

1
Python เป็นภาษาการเขียนโปรแกรมและนี่ก็หมายความว่ามันไม่ได้ทำสิ่งที่ "อัตโนมัติ" (ตามที่ OP ต้องการ) ยกเว้นถ้าและเมื่อคุณขอให้มันทำ กล่าวอีกนัยหนึ่งไม่มีทางที่จะ "บันทึกอัตโนมัติ" บันทึกข้อยกเว้นทั้งหมดยกเว้นว่าคุณใช้รหัส - และนั่นคือสิ่งที่อยู่ในคำตอบของฉัน
flaviovs

1
ถ้าคุณดูคำตอบของ Ned Batchelder มีบางสิ่งที่เรียกว่า hook hook ยกเว้น คุณต้องกำหนดที่เดียวในรหัสของคุณและจัดการข้อยกเว้นที่ไม่ได้ตรวจสอบทั้งหมดของคุณ
Mayank Jaiswal

1
เบ็ดยกเว้นนั้นไม่เปลี่ยนความจริงที่ว่ามันไม่ใช่ "อัตโนมัติ" (ในแง่ที่ OP ต้องการ) - กล่าวอีกนัยหนึ่งคุณยังคงต้องใช้รหัส คำตอบของเน็ด (ซึ่งใช้เบ็ดยกเว้น) ตอบคำถามดั้งเดิมจริงๆ - เป็นเพียงแค่นั้นในความคิดของฉันวิธีที่มันทำนั้นมีความไพเราะน้อยกว่าของฉันมาก
flaviovs

1
มันสวยมากขึ้นอยู่กับวัตถุประสงค์ของคุณเอง หากคุณตั้งโปรแกรมให้พอใจ IDE ดังนั้นการจับข้อยกเว้นทั้งหมดอาจไม่ใช่ตัวเลือก แต่ถ้าคุณต้องการจัดการข้อผิดพลาดอย่างสง่างามและแสดงผลตอบรับที่ดีแก่ผู้ใช้ฉันกลัวว่าคุณจะต้องตรวจสอบข้อยกเว้นทั้งหมด ตกลงการเสียดสีมากพอ :-) - ถ้าคุณดูอย่างระมัดระวังคุณจะเห็นว่าโค้ดตัดข้อยกเว้น แต่ยกขึ้นใหม่ดังนั้นเว้นแต่ว่า IDE ของคุณกำลังทำอะไร "ขลัง" มันไม่ควรทำมันจะยังคงได้รับ ข้อยกเว้น
flaviovs

3

บางทีคุณสามารถทำบางสิ่งที่ด้านบนของโมดูลที่เปลี่ยนเส้นทาง stderr ไปยังไฟล์แล้วล็อกไฟล์นั้นที่ด้านล่าง

sock = open('error.log', 'w')               
sys.stderr = sock

doSomething() #makes errors and they will log to error.log

logging.exception(open('error.log', 'r').read() )

3

แม้ว่าคำตอบของ @ gnu_lorien นั้นให้จุดเริ่มต้นที่ดี แต่โปรแกรมของฉันก็ล้มเหลวเนื่องจากข้อยกเว้นแรก

ฉันมาพร้อมกับโซลูชันที่ปรับปรุงใหม่ (และ / หรือ) ที่กำหนดเองซึ่งจะบันทึกข้อยกเว้นของฟังก์ชันที่ตกแต่งด้วยความ@handle_errorเงียบ

import logging

__author__ = 'ahmed'
logging.basicConfig(filename='error.log', level=logging.DEBUG)


def handle_exception(exc_type, exc_value, exc_traceback):
    import sys
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    logging.critical(exc_value.message, exc_info=(exc_type, exc_value, exc_traceback))


def handle_error(func):
    import sys

    def __inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception, e:
            exc_type, exc_value, exc_tb = sys.exc_info()
            handle_exception(exc_type, exc_value, exc_tb)
        finally:
            print(e.message)
    return __inner


@handle_error
def main():
    raise RuntimeError("RuntimeError")


if __name__ == "__main__":
    for _ in xrange(1, 20):
        main()

2

เพื่อตอบคำถามจาก Mr.Zeus ที่กล่าวถึงในส่วนความคิดเห็นของคำตอบที่ยอมรับฉันใช้สิ่งนี้เพื่อบันทึกข้อยกเว้นที่ไม่ได้ตรวจสอบในคอนโซลแบบโต้ตอบ (ทดสอบด้วย PyCharm 2018-2019) ฉันค้นพบว่าsys.excepthookใช้งานไม่ได้ในเปลือกงูหลามดังนั้นฉันจึงมองลึกลงไปและพบว่าฉันสามารถใช้sys.exc_infoแทนได้ อย่างไรก็ตามsys.exc_infoไม่รับข้อโต้แย้งใด ๆsys.excepthook 3 ข้อ

ที่นี่ฉันใช้ทั้งsys.excepthookและsys.exc_infoบันทึกข้อยกเว้นทั้งสองในคอนโซลโต้ตอบและสคริปต์ด้วยฟังก์ชั่นเสื้อคลุม ในการแนบฟังก์ชั่น hook เข้ากับฟังก์ชั่นทั้งสองฉันมีสองอินเตอร์เฟสที่แตกต่างกันขึ้นอยู่กับว่าจะให้อาร์กิวเมนต์หรือไม่

นี่คือรหัส:

def log_exception(exctype, value, traceback):
    logger.error("Uncaught exception occurred!",
                 exc_info=(exctype, value, traceback))


def attach_hook(hook_func, run_func):
    def inner(*args, **kwargs):
        if not (args or kwargs):
            # This condition is for sys.exc_info
            local_args = run_func()
            hook_func(*local_args)
        else:
            # This condition is for sys.excepthook
            hook_func(*args, **kwargs)
        return run_func(*args, **kwargs)
    return inner


sys.exc_info = attach_hook(log_exception, sys.exc_info)
sys.excepthook = attach_hook(log_exception, sys.excepthook)

การตั้งค่าการบันทึกสามารถพบได้ในคำตอบของ gnu_lorien


2

ในกรณีของฉัน (ใช้python 3) เมื่อใช้คำตอบของ @Jacinda เนื้อหาของการติดตามกลับไม่ได้ถูกพิมพ์ออกมา แต่มันแค่พิมพ์วัตถุเอง:<traceback object at 0x7f90299b7b90> .

แทนที่จะทำ:

import sys
import logging
import traceback

def custom_excepthook(exc_type, exc_value, exc_traceback):
    # Do not print exception when user cancels the program
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return

    logging.error("An uncaught exception occurred:")
    logging.error("Type: %s", exc_type)
    logging.error("Value: %s", exc_value)

    if exc_traceback:
        format_exception = traceback.format_tb(exc_traceback)
        for line in format_exception:
            logging.error(repr(line))

sys.excepthook = custom_excepthook
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.