บันทึกข้อยกเว้นด้วยการย้อนกลับ


คำตอบ:


203

ใช้logging.exceptionจากภายในexcept:ตัวจัดการ / บล็อกเพื่อบันทึกข้อยกเว้นปัจจุบันพร้อมกับข้อมูลการติดตามที่มีข้อความ

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME, level=logging.DEBUG)

logging.debug('This message should go to the log file')

try:
    run_my_stuff()
except:
    logging.exception('Got exception on main handler')
    raise

ตอนนี้ดูไฟล์บันทึก/tmp/logging_example.out:

DEBUG:root:This message should go to the log file
ERROR:root:Got exception on main handler
Traceback (most recent call last):
  File "/tmp/teste.py", line 9, in <module>
    run_my_stuff()
NameError: name 'run_my_stuff' is not defined

1
ดูรหัส django สำหรับสิ่งนี้และฉันคาดว่าคำตอบคือไม่ แต่มีวิธี จำกัด การย้อนกลับถึงจำนวนอักขระหรือความลึกหรือไม่ ปัญหาคือว่าสำหรับการติดตามย้อนกลับขนาดใหญ่ใช้เวลาค่อนข้างนาน
Eduard Luca

10
โปรดทราบว่าถ้าคุณกำหนดคนตัดไม้ด้วยlogger = logging.getLogger('yourlogger')คุณต้องเขียนlogger.exception('...')ให้ทำงานนี้ ...
576i

เราสามารถแก้ไขสิ่งนี้เพื่อให้ข้อความถูกพิมพ์ด้วย INFO ระดับบันทึกได้หรือไม่?
NM

โปรดทราบว่าสำหรับแอปภายนอกบางรายการเช่นข้อมูลเชิงลึก Azure แทร็กแบ็คจะไม่ถูกเก็บไว้ในบันทึก จากนั้นจึงจำเป็นต้องส่งผ่านไปยังสตริงข้อความอย่างชัดเจนดังที่แสดงด้านล่าง
Edgar H

139

exc_infoตัวเลือกการใช้อาจจะดีกว่ายังคงเป็นคำเตือนหรือชื่อข้อผิดพลาด:

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)

ฉันไม่สามารถจำสิ่งที่exc_info=kwarg เรียกว่า; ขอบคุณ!
berto

4
นี่เป็นสิ่งเดียวกันกับ logging.exception ยกเว้นว่าชนิดนั้นถูกบันทึกซ้ำซ้อนสองครั้ง เพียงใช้ logging.exception เว้นแต่คุณต้องการระดับอื่นที่ไม่ใช่ข้อผิดพลาด
Wyrmwood

@Wyrmwood มันไม่เหมือนกับที่คุณต้องส่งข้อความถึงlogging.exception
Peter Wood

57

งานของฉันเมื่อเร็ว ๆ นี้มอบหมายให้ฉันด้วยการบันทึกการติดตาม / ข้อยกเว้นทั้งหมดจากใบสมัครของเรา ฉันลองใช้เทคนิคมากมายที่คนอื่นโพสต์ออนไลน์เช่นที่กล่าวมาข้างต้น แต่ตัดสินด้วยวิธีอื่น traceback.print_exceptionที่เอาชนะ

ฉันมีบทความที่http://www.bbarrows.com/ซึ่งจะง่ายกว่ามากในการอ่าน แต่ไม่วางไว้ที่นี่เช่นกัน

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

import traceback
import StringIO
import logging
import os, sys

def my_excepthook(excType, excValue, traceback, logger=logger):
    logger.error("Logging an uncaught exception",
                 exc_info=(excType, excValue, traceback))

sys.excepthook = my_excepthook  

สิ่งนี้ใช้ได้กับเธรดหลัก แต่ไม่ช้าฉันก็พบว่า sys.excepthook ของฉันจะไม่มีอยู่ในเธรดใหม่ ๆ ที่กระบวนการของฉันเริ่มต้น นี่เป็นปัญหาใหญ่เพราะทุกสิ่งส่วนใหญ่เกิดขึ้นในเธรดในโครงการนี้

หลังจาก googling และอ่านเอกสารมากมายข้อมูลที่เป็นประโยชน์ที่สุดที่ฉันพบคือจาก Python Issue tracker

โพสต์แรกบนเธรดแสดงตัวอย่างการทำงานของsys.excepthookNOT persisting ข้ามเธรด (ดังแสดงด้านล่าง) เห็นได้ชัดว่านี่เป็นพฤติกรรมที่คาดหวัง

import sys, threading

def log_exception(*args):
    print 'got exception %s' % (args,)
sys.excepthook = log_exception

def foo():
    a = 1 / 0

threading.Thread(target=foo).start()

ข้อความบนเธรด Python ฉบับนี้ส่งผลให้มีแฮ็กที่แนะนำ 2 รายการ คลาสย่อยThreadและล้อมรอบเมธอด run ในลองของเราเองยกเว้นบล็อกเพื่อที่จะจับและบันทึกข้อยกเว้นหรือแพทช์ลิงthreading.Thread.runเพื่อเรียกใช้ในการลองของคุณเองยกเว้นบล็อกและบันทึกข้อยกเว้น

วิธีแรกในการทำ subclassing นั้นThreadดูเหมือนว่าฉันจะไม่ค่อยสง่างามในรหัสของคุณเพราะคุณจะต้องนำเข้าและใช้Threadคลาสที่คุณกำหนดเองทุกที่ที่คุณต้องการให้มีเธรดการบันทึก เรื่องนี้จบลงด้วยการถูกรบกวนเพราะผมจะต้องค้นหาฐานรหัสของเราทั้งหมดและแทนที่ปกติกับที่กำหนดเองนี้Threads Threadอย่างไรก็ตามมันชัดเจนว่าสิ่งนี้Threadกำลังทำอะไรและใครบางคนจะสามารถวินิจฉัยและตรวจแก้จุดบกพร่องได้ง่ายขึ้นหากมีบางอย่างผิดพลาดกับรหัสการบันทึกที่กำหนดเอง เธรดการบันทึกของ custome อาจมีลักษณะเช่นนี้:

class TracebackLoggingThread(threading.Thread):
    def run(self):
        try:
            super(TracebackLoggingThread, self).run()
        except (KeyboardInterrupt, SystemExit):
            raise
        except Exception, e:
            logger = logging.getLogger('')
            logger.exception("Logging an uncaught exception")

วิธีที่สองของการปะลิงthreading.Thread.runเป็นสิ่งที่ดีเพราะฉันสามารถเรียกใช้มันได้ทันที__main__และใช้รหัสการบันทึกของฉันในทุกข้อยกเว้น การปะแก้ของลิงอาจสร้างความรำคาญให้กับการดีบักได้เนื่องจากมันเปลี่ยนการทำงานที่คาดหวังของบางสิ่งบางอย่าง แพทช์ที่แนะนำจากตัวติดตามการออก Python คือ:

def installThreadExcepthook():
    """
    Workaround for sys.excepthook thread bug
    From
http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html

(https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470).
    Call once from __main__ before creating any threads.
    If using psyco, call psyco.cannotcompile(threading.Thread.run)
    since this replaces a new-style class method.
    """
    init_old = threading.Thread.__init__
    def init(self, *args, **kwargs):
        init_old(self, *args, **kwargs)
        run_old = self.run
        def run_with_except_hook(*args, **kw):
            try:
                run_old(*args, **kw)
            except (KeyboardInterrupt, SystemExit):
                raise
            except:
                sys.excepthook(*sys.exc_info())
        self.run = run_with_except_hook
    threading.Thread.__init__ = init

มันไม่ได้จนกว่าฉันจะเริ่มต้นการทดสอบการบันทึกข้อยกเว้นของฉันฉันรู้ว่าฉันจะทำมันผิดทั้งหมด

เพื่อทดสอบฉันได้

raise Exception("Test")

บางแห่งในรหัสของฉัน อย่างไรก็ตามวิธีการห่อที่เรียกว่าวิธีนี้คือลองยกเว้นบล็อกที่พิมพ์การติดตามกลับและกลืนข้อยกเว้น มันน่าผิดหวังมากเพราะฉันเห็นร่องรอยนำกลับมาพิมพ์ไปที่ STDOUT แต่ไม่ได้เข้าสู่ระบบ ฉันตัดสินใจแล้วว่าวิธีการที่ง่ายกว่ามากในการบันทึกการสืบค้นกลับคือการแก้ไขวิธีที่รหัสงูใหญ่ใช้เพื่อพิมพ์การสืบค้นกลับด้วยตัวเอง ฉันลงเอยด้วยสิ่งที่คล้ายกับต่อไปนี้:

def add_custom_print_exception():
    old_print_exception = traceback.print_exception
    def custom_print_exception(etype, value, tb, limit=None, file=None):
        tb_output = StringIO.StringIO()
        traceback.print_tb(tb, limit, tb_output)
        logger = logging.getLogger('customLogger')
        logger.error(tb_output.getvalue())
        tb_output.close()
        old_print_exception(etype, value, tb, limit=None, file=None)
    traceback.print_exception = custom_print_exception

รหัสนี้เขียน traceback ไปยัง String Buffer และบันทึกลงในการบันทึก ERROR ฉันมีตัวจัดการการบันทึกแบบกำหนดเองตั้งค่าตัวบันทึก 'customLogger' ซึ่งใช้บันทึกระดับข้อผิดพลาดและส่งกลับบ้านเพื่อการวิเคราะห์


2
ค่อนข้างเป็นวิธีที่น่าสนใจ คำถามหนึ่งข้อ - add_custom_print_exceptionดูเหมือนจะไม่ปรากฏบนไซต์ที่คุณเชื่อมโยงและมีรหัสสุดท้ายที่แตกต่างกันบ้าง คุณจะบอกว่าอันไหนดีกว่า / สุดท้ายและทำไม? ขอบคุณ!
เพ้อฝัน

ขอบคุณคำตอบยอดเยี่ยม!
101

มีตัวพิมพ์ที่ตัดและวาง ในการโทรที่มอบหมายให้ old_print_exception ขีด จำกัด และไฟล์ควรผ่านขีด จำกัด และไฟล์ไม่ใช่ None - old_print_exception (etype, value, tb, limit, file)
Marvin

สำหรับบล็อกรหัสสุดท้ายของคุณแทนที่จะเริ่มต้น StringIO และพิมพ์ข้อยกเว้นคุณสามารถโทรlogger.error(traceback.format_tb())(หรือ format_exc () หากคุณต้องการข้อมูลข้อยกเว้นด้วย)
James

8

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

import sys
import logging

logging.basicConfig(filename='/tmp/foobar.log')

def exception_hook(exc_type, exc_value, exc_traceback):
    logging.error(
        "Uncaught exception",
        exc_info=(exc_type, exc_value, exc_traceback)
    )

sys.excepthook = exception_hook

raise Exception('Boom')

อย่างไรก็ตามหากโปรแกรมของคุณใช้เธรดโปรดทราบว่าเธรดที่สร้างขึ้นโดยใช้threading.Threadจะไม่ทริกเกอร์sys.excepthookเมื่อเกิดข้อยกเว้นที่ไม่ถูกตรวจจับซึ่งเกิดขึ้นภายในดังที่ระบุไว้ในปัญหา 1230540ในตัวติดตามปัญหาของ Python มีการแนะนำแฮ็กบางอย่างเพื่อแก้ไขข้อ จำกัด นี้เช่นการปะแก้ลิงThread.__init__เพื่อเขียนทับself.runด้วยrunวิธีการอื่นที่ล้อมรอบต้นฉบับในtryบล็อกและการโทรsys.excepthookจากภายในexceptบล็อก หรือคุณสามารถด้วยตนเองเพียงห่อจุดเริ่มต้นสำหรับแต่ละหัวข้อของคุณในtry/ exceptตัวเอง


3

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

ตัวอย่าง

ผนวกข้อผิดพลาดกับไฟล์เอาต์พุตอื่นไปยังเทอร์มินัล:

./test.py 2>> mylog.log

เขียนทับไฟล์ด้วยเอาต์พุต STDOUT และ STDERR interleaved:

./test.py &> mylog.log


2

คุณสามารถรับข้อมูลย้อนกลับได้โดยใช้ตัวบันทึกในระดับใดก็ได้ (DEBUG, INFO, ... ) โปรดทราบว่าการใช้logging.exceptionระดับคือข้อผิดพลาด

# test_app.py
import sys
import logging

logging.basicConfig(level="DEBUG")

def do_something():
    raise ValueError(":(")

try:
    do_something()
except Exception:
    logging.debug("Something went wrong", exc_info=sys.exc_info())
DEBUG:root:Something went wrong
Traceback (most recent call last):
  File "test_app.py", line 10, in <module>
    do_something()
  File "test_app.py", line 7, in do_something
    raise ValueError(":(")
ValueError: :(

แก้ไข:

ใช้งานได้เช่นกัน (ใช้ python 3.6)

logging.debug("Something went wrong", exc_info=True)

1

นี่คือรุ่นที่ใช้ sys.excepthook

import traceback
import sys

logger = logging.getLogger()

def handle_excepthook(type, message, stack):
     logger.error(f'An unhandled exception occured: {message}. Traceback: {traceback.format_tb(stack)}')

sys.excepthook = handle_excepthook

วิธีการเกี่ยวกับการใช้{traceback.format_exc()}แทน{traceback.format_tb(stack)}?
ตัวแปร


-1

นี่เป็นตัวอย่างง่ายๆที่นำมาจากเอกสาร python 2.6 :

import logging
LOG_FILENAME = '/tmp/logging_example.out'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG,)

logging.debug('This message should go to the log file')

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