ฉันจะบันทึกข้อผิดพลาด Python ด้วยข้อมูลการดีบักได้อย่างไร


468

ฉันกำลังพิมพ์ข้อความข้อยกเว้น Python ไปยังไฟล์บันทึกด้วยlogging.error:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.error(e)  # ERROR:root:division by zero

เป็นไปได้หรือไม่ที่จะพิมพ์ข้อมูลรายละเอียดเพิ่มเติมเกี่ยวกับข้อยกเว้นและรหัสที่สร้างมันขึ้นมานอกเหนือจากสตริงข้อยกเว้น? สิ่งต่างๆเช่นหมายเลขบรรทัดหรือร่องรอยสแต็กจะดีมาก

คำตอบ:


733

logger.exception จะส่งออกการติดตามสแต็คพร้อมกับข้อผิดพลาด

ตัวอย่างเช่น:

import logging
try:
    1/0
except ZeroDivisionError as e:
    logging.exception("message")

เอาท์พุท:

ERROR:root:message
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero

@Paulo ตรวจสอบหมายเหตุ "ระวังให้ดีว่าใน Python 3 คุณต้องเรียกใช้logging.exceptionเมธอดข้างในexceptส่วนนั้นถ้าคุณเรียกวิธีนี้ในตำแหน่งที่กำหนดเองคุณอาจได้รับข้อยกเว้นที่แปลกประหลาด


131
วิธีการก็เรียกexception error(message, exc_info=1)ทันทีที่คุณส่งexc_infoต่อไปยังวิธีการบันทึกใด ๆ จากบริบทข้อยกเว้นคุณจะได้รับการสืบค้นกลับ
Helmut Grohne

16
คุณสามารถตั้งค่าsys.excepthook(ดูที่นี่ ) เพื่อหลีกเลี่ยงการตัดโค้ดทั้งหมดของคุณในลอง / ยกเว้น
กรกฎาคม

23
คุณสามารถเขียนได้except Exception:เพราะคุณไม่ได้ใช้eวิธีการใด ๆ )
มาร์โกเฟอร์รารี่

21
คุณอาจต้องการตรวจสอบเป็นอย่างดีeเมื่อพยายามดีบักโค้ดของคุณแบบโต้ตอบ :) นี่คือเหตุผลที่ฉันรวมไว้เสมอ
Vicki Laidler

4
แก้ไขให้ฉันถ้าฉันผิดในกรณีนี้ไม่มีการจัดการข้อยกเว้นที่แท้จริงดังนั้นจึงควรเพิ่มraiseที่ส่วนท้ายของexceptขอบเขต มิฉะนั้นการทำงานจะดำเนินต่อไปราวกับว่าทุกอย่างเรียบร้อยดี
Dror

184

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

import logging
try:
    1/0
except ZeroDivisionError:
    logging.exception("Deliberate divide by zero traceback")

ด้วยพฤติกรรมการบันทึกเริ่มต้น (เป็นรุ่นล่าสุด) ที่เพิ่งพิมพ์ข้อผิดพลาดไปsys.stderrดูเหมือนว่า:

>>> import logging
>>> try:
...     1/0
... except ZeroDivisionError:
...     logging.exception("Deliberate divide by zero traceback")
... 
ERROR:root:Deliberate divide by zero traceback
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero

สามารถบันทึกข้อยกเว้นโดยไม่แจ้งข้อความได้หรือไม่?
Stevoisiak

@StevenVascellaro - ฉันขอแนะนำให้คุณผ่าน''ถ้าคุณไม่ต้องการพิมพ์ข้อความ ... ฟังก์ชั่นที่ไม่สามารถเรียกได้โดยไม่มีอาร์กิวเมนต์อย่างน้อยหนึ่งข้อดังนั้นคุณจะต้องให้อะไรบางอย่าง
ArtOfWarfare

147

การใช้exc_infoตัวเลือกอาจดีกว่าเพื่อให้คุณเลือกระดับข้อผิดพลาด (ถ้าคุณใช้exceptionมันจะอยู่ในerrorระดับเสมอ):

try:
    # do something here
except Exception as e:
    logging.critical(e, exc_info=True)  # log exception info at CRITICAL log level

@CivFan: จริง ๆ แล้วฉันไม่ได้ดูการแก้ไขอื่น ๆ หรือโพสต์บทนำ คำนำนั้นถูกเพิ่มโดยบรรณาธิการจากบุคคลที่สาม ฉันไม่เห็นที่ใดก็ได้ในความคิดเห็นที่ถูกลบซึ่งเป็นความตั้งใจ แต่ฉันอาจยกเลิกการแก้ไขและลบความคิดเห็นได้มันนานเกินไปที่การลงคะแนนที่นี่จะเป็นสิ่งอื่นนอกเหนือจากรุ่นที่แก้ไข .
Martijn Pieters

เป็นlogging.fatalวิธีการในการเข้าสู่ระบบห้องสมุดหรือ? criticalผมเห็นเพียง
เอียน

1
@Ian มันเป็นชื่อแทนcriticalเช่นเดียวคือการwarn warning
0xc0de

35

quoting

จะเกิดอะไรขึ้นถ้าแอปพลิเคชันของคุณทำการบันทึกด้วยวิธีอื่น - ไม่ได้ใช้loggingโมดูล?

ตอนนี้tracebackสามารถใช้ที่นี่

import traceback

def log_traceback(ex, ex_traceback=None):
    if ex_traceback is None:
        ex_traceback = ex.__traceback__
    tb_lines = [ line.rstrip('\n') for line in
                 traceback.format_exception(ex.__class__, ex, ex_traceback)]
    exception_logger.log(tb_lines)
  • ใช้ในPython 2 :

    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
    
  • ใช้ในPython 3 :

    try:
        x = get_number()
    except Exception as ex:
        log_traceback(ex)
    

ทำไมคุณวาง "_, _, ex_traceback = sys.exc_info ()" นอกฟังก์ชั่น log_traceback แล้วส่งผ่านเป็นอาร์กิวเมนต์? ทำไมไม่ใช้โดยตรงในฟังก์ชั่น?
Basil Musa

@BasilMusa เพื่อตอบคำถามของคุณในระยะสั้นเพื่อให้เข้ากันได้กับ Python 3 เพราะex_tracebackมาจาก ex.__traceback__ใต้ Python 3 แต่ex_tracebackมาจากsys.exc_info()ใต้ Python 2
zangw

12

ถ้าคุณใช้บันทึกธรรมดา - one record = one lineทั้งหมดบันทึกเข้าสู่ระบบของคุณควรตรงกฎนี้: ปฏิบัติตามกฎนี้คุณสามารถใช้grepและเครื่องมืออื่น ๆ เพื่อประมวลผลไฟล์บันทึกของคุณ

แต่ข้อมูลการสืบค้นกลับเป็นหลายบรรทัด ดังนั้นคำตอบของฉันคือโซลูชันเพิ่มเติมที่เสนอโดยzangwด้านบนในชุดข้อความนี้ ปัญหาคือเส้นการสืบค้นกลับอาจมี\nอยู่ข้างในดังนั้นเราต้องทำงานพิเศษเพื่อกำจัดจุดสิ้นสุดของบรรทัดนี้:

import logging


logger = logging.getLogger('your_logger_here')

def log_app_error(e: BaseException, level=logging.ERROR) -> None:
    e_traceback = traceback.format_exception(e.__class__, e, e.__traceback__)
    traceback_lines = []
    for line in [line.rstrip('\n') for line in e_traceback]:
        traceback_lines.extend(line.splitlines())
    logger.log(level, traceback_lines.__str__())

หลังจากนั้น (เมื่อคุณจะวิเคราะห์บันทึกของคุณ) คุณสามารถคัดลอก / วางบรรทัดการสืบค้นกลับที่ต้องการจากไฟล์บันทึกของคุณและทำสิ่งนี้:

ex_traceback = ['line 1', 'line 2', ...]
for line in ex_traceback:
    print(line)

กำไร!


9

คำตอบนี้สร้างขึ้นจากสิ่งที่ยอดเยี่ยมข้างต้น

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

# Set the name of the app or module
my_logger = logging.getLogger('NEM Sequencer')
# Set the log level
my_logger.setLevel(logging.INFO)

# Let's say we want to be fancy and log to a graylog2 log server
graylog_handler = graypy.GELFHandler('some_server_ip', 12201)
graylog_handler.setLevel(logging.INFO)
my_logger.addHandler(graylog_handler)

ในกรณีนี้ให้ใช้ตัวบันทึกเพื่อเรียกข้อยกเว้น (e) ดังนี้:

try:
    1/0
except ZeroDivisionError, e:
    my_logger.exception(e)

นี่คือการสัมผัสที่มีประโยชน์อย่างแน่นอนถ้าคุณต้องการคนตัดไม้เฉพาะสำหรับข้อยกเว้น
logicOnAbstractions

7

คุณสามารถบันทึกการติดตามสแต็กได้โดยไม่มีข้อยกเว้น

https://docs.python.org/3/library/logging.html#logging.Logger.debug

อาร์กิวเมนต์คำหลักทางเลือกที่สองคือ stack_info ซึ่งมีค่าเริ่มต้นเป็นเท็จ หากเป็นจริงข้อมูลสแต็กจะถูกเพิ่มไปยังข้อความการบันทึกรวมถึงการเรียกการบันทึกจริง โปรดทราบว่านี่ไม่ใช่ข้อมูลสแต็กเดียวกันกับที่แสดงผ่านการระบุ exc_info: อดีตคือเฟรมสแต็กจากด้านล่างของสแต็กจนถึงการเรียกใช้การบันทึกในเธรดปัจจุบันในขณะที่ข้อมูลหลังเป็นข้อมูลเกี่ยวกับสแต็กเฟรม ติดตามข้อยกเว้นในขณะที่ค้นหาตัวจัดการข้อยกเว้น

ตัวอย่าง:

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG)
>>> logging.getLogger().info('This prints the stack', stack_info=True)
INFO:root:This prints the stack
Stack (most recent call last):
  File "<stdin>", line 1, in <module>
>>>

5

การรักษามัณฑนากรเล็กน้อย (ได้รับแรงบันดาลใจอย่างมากจากอาจจะเป็น monad และยก) คุณสามารถลบบันทึกย่อของ Python 3.6 ได้อย่างปลอดภัยและใช้สไตล์การจัดรูปแบบข้อความที่เก่ากว่า

fallible.py

from functools import wraps
from typing import Callable, TypeVar, Optional
import logging


A = TypeVar('A')


def fallible(*exceptions, logger=None) \
        -> Callable[[Callable[..., A]], Callable[..., Optional[A]]]:
    """
    :param exceptions: a list of exceptions to catch
    :param logger: pass a custom logger; None means the default logger, 
                   False disables logging altogether.
    """
    def fwrap(f: Callable[..., A]) -> Callable[..., Optional[A]]:

        @wraps(f)
        def wrapped(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except exceptions:
                message = f'called {f} with *args={args} and **kwargs={kwargs}'
                if logger:
                    logger.exception(message)
                if logger is None:
                    logging.exception(message)
                return None

        return wrapped

    return fwrap

การสาธิต:

In [1] from fallible import fallible

In [2]: @fallible(ArithmeticError)
    ...: def div(a, b):
    ...:     return a / b
    ...: 
    ...: 

In [3]: div(1, 2)
Out[3]: 0.5

In [4]: res = div(1, 0)
ERROR:root:called <function div at 0x10d3c6ae8> with *args=(1, 0) and **kwargs={}
Traceback (most recent call last):
  File "/Users/user/fallible.py", line 17, in wrapped
    return f(*args, **kwargs)
  File "<ipython-input-17-e056bd886b5c>", line 3, in div
    return a / b

In [5]: repr(res)
'None'

นอกจากนี้คุณยังสามารถปรับเปลี่ยนการแก้ปัญหานี้จะกลับมาบางบิตที่มีความหมายมากกว่าNoneจากexceptส่วนหนึ่ง (หรือแม้กระทั่งทำให้การแก้ปัญหาทั่วไปโดยระบุค่าตอบแทนในfallible's อาร์กิวเมนต์)


0

ในโมดูลการบันทึกของคุณ (หากโมดูลที่กำหนดเอง) เพียงแค่เปิดใช้งาน stack_info

api_logger.exceptionLog("*Input your Custom error message*",stack_info=True)

-1

หากคุณสามารถรับมือกับการพึ่งพาพิเศษจากนั้นใช้ twisted.log คุณไม่จำเป็นต้องบันทึกข้อผิดพลาดอย่างชัดเจนและจะส่งคืนการติดตามย้อนกลับและเวลาทั้งหมดไปยังไฟล์หรือสตรีม


8
อาจtwistedเป็นคำแนะนำที่ดี แต่คำตอบนี้ไม่ได้ช่วยอะไรมาก มันไม่ได้บอกวิธีการใช้งานtwisted.logหรือสิ่งที่ได้เปรียบก็มีมากกว่าloggingโมดูลจากห้องสมุดมาตรฐานหรืออธิบายสิ่งที่หมายโดย"คุณไม่จำเป็นต้องเข้าสู่ระบบอย่างชัดเจนข้อผิดพลาด"
Mark Amery

-8

วิธีที่สะอาดในการใช้งานformat_exc()และแยกวิเคราะห์ผลลัพธ์เพื่อให้ได้ส่วนที่เกี่ยวข้อง:

from traceback import format_exc

try:
    1/0
except Exception:
    print 'the relevant part is: '+format_exc().split('\n')[-2]

ความนับถือ


4
ฮะ? ทำไมจึงเป็น"ส่วนที่เกี่ยวข้อง" ? สิ่งที่ต้องทำทั้งหมด.split('\n')[-2]คือการทิ้งหมายเลขบรรทัดและการย้อนกลับจากผลลัพธ์format_exc()- ข้อมูลที่มีประโยชน์ตามปกติที่คุณต้องการ! มีอะไรเพิ่มเติมก็ไม่ได้ทำผลงานที่ดีของว่า ; หากข้อความข้อยกเว้นของคุณมีการขึ้นบรรทัดใหม่วิธีการนี้จะพิมพ์บรรทัดสุดท้ายของข้อความยกเว้น - หมายความว่าคุณสูญเสียคลาสยกเว้นและข้อความยกเว้นส่วนใหญ่ที่ด้านบนของการสูญเสียการติดตามกลับ -1
Mark Amery
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.