Ghi nhật ký ngoại lệ chưa được lưu trong Python


181

Làm thế nào để bạn gây ra ngoại lệ chưa được lưu cho đầu ra thông qua loggingmô-đun chứ không phải stderr?

Tôi nhận ra cách tốt nhất để làm điều này sẽ là:

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

Nhưng tình huống của tôi là nó sẽ thực sự tốt nếu logging.exception(...)được gọi tự động bất cứ khi nào một ngoại lệ không bị bắt.



Câu trả lời:


143

Như Ned đã chỉ ra, sys.excepthookđược viện dẫn mỗi khi có ngoại lệ được nêu ra và không bị bắt. Ý nghĩa thực tế của điều này là trong mã của bạn, bạn có thể ghi đè hành vi mặc định sys.excepthookđể làm bất cứ điều gì bạn muốn (bao gồm cả việc sử dụng logging.exception).

Như một ví dụ người rơm:

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

Ghi đè sys.excepthook:

>>> sys.excepthook = foo

Cam kết lỗi cú pháp rõ ràng (bỏ dấu hai chấm) và lấy lại thông tin lỗi tùy chỉnh:

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

Để biết thêm thông tin về sys.excepthook, đọc tài liệu .


4
@Codemonkey Đây không phải là một từ khóa dành riêng, nó là một loại tên có sẵn. Bạn có thể sử dụng typelàm đối số chức năng, mặc dù IDE sẽ phàn nàn về việc ẩn toàn cục type(giống như sử dụng var self = thistrong Javascript). Nó không thực sự quan trọng trừ khi bạn cần truy cập vào typeđối tượng bên trong hàm của mình, trong trường hợp đó bạn có thể sử dụng type_làm đối số thay thế.
Ryan P

3
Cụm từ "mọi lúc" ở đây là sai lệch :: "sys.ex805thook được gọi mỗi khi một ngoại lệ được nêu ra và không bị bắt" ... bởi vì trong một chương trình, có thể có chính xác một ngoại lệ "không bị bắt". Ngoài ra, sys.excepthookKHÔNG được gọi khi một ngoại lệ được "nêu ra". Nó được gọi khi chương trình sắp kết thúc do một ngoại lệ chưa được lưu, điều này không thể xảy ra nhiều hơn một lần.
Nawaz

2
@Nawaz: nó có thể xảy ra nhiều hơn một lần trong REPL
jfs

2
@Nawaz Nó cũng có thể xảy ra nhiều lần nếu chương trình đang sử dụng các luồng. Tôi cũng có vẻ như các vòng lặp sự kiện GUI (như Qt) vẫn tiếp tục chạy, mặc dù ngoại lệ đã biến nó thành sys.ex805thook
ba_pineapples

1
Bất cứ ai đang cố kiểm tra mã trên, hãy đảm bảo rằng bạn tạo ra lỗi truy nguyên trong khi kiểm tra chức năng của mình. Cú pháp không được xử lý bởi sys.ex805thook. Bạn có thể sử dụng in (1/0) và điều này sẽ gọi chức năng bạn đã xác định để ghi đè sys.ex805thook
Parth Karia

177

Đây là một ví dụ nhỏ hoàn chỉnh bao gồm một vài thủ thuật khác:

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")
  • Bỏ qua KeyboardInterrupt để chương trình python console có thể thoát bằng Ctrl + C.

  • Hoàn toàn dựa vào mô đun đăng nhập của python để định dạng ngoại lệ.

  • Sử dụng một logger tùy chỉnh với một trình xử lý ví dụ. Cái này thay đổi ngoại lệ chưa được xử lý thành stdout thay vì stderr, nhưng bạn có thể thêm tất cả các loại trình xử lý theo cùng kiểu này vào đối tượng logger.


13
Tôi sẽ sử dụng logger.critical()bên trong trình xử lý exeemthook, vì một ngoại lệ chưa được xử lý là khá quan trọng tôi sẽ nói.
gitaarik

2
Đây là câu trả lời thiết thực nhất IMO.
David Morales

@gnu_lorien cảm ơn vì đoạn trích. Trong tập tin nào bạn sẽ đặt cái này?
stelios

@chefarov Tệp chính nơi bạn khởi tạo tất cả các bản ghi khác
gnu_lorien

Xin chào, Làm thế nào chúng ta có thể ghi vào tệp như debug.log thông tin này. tôi thử nó để thêm dòng logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')Nhưng không giúp được gì.
GurhanCagin

26

Phương thức sys.excepthooknày sẽ được gọi nếu không có ngoại lệ: http://docs.python.org/l Library / sys.html # sys.ex805thook

Khi một ngoại lệ được nêu ra và không bị phát hiện, trình thông dịch gọi sys.ex805thook với ba đối số, lớp ngoại lệ, trường hợp ngoại lệ và đối tượng truy nguyên. Trong phiên tương tác, điều này xảy ra ngay trước khi điều khiển được trả về dấu nhắc; trong chương trình Python, điều này xảy ra ngay trước khi chương trình thoát. Việc xử lý các ngoại lệ cấp cao nhất như vậy có thể được tùy chỉnh bằng cách gán một hàm ba đối số khác cho sys.ex805thook.


2
Tại sao nó gửi lớp ngoại lệ? Bạn không thể luôn luôn có được điều đó bằng cách gọi typevào ví dụ?
Neil G

Các loại tham số của là sys.excepthookgì?
Martin Thoma

23

Tại sao không:

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()

Đây là đầu ra với sys.excepthooknhư đã thấy ở trên:

$ 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

Đây là đầu ra với sys.excepthooknhận xét:

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

Sự khác biệt duy nhất là cái trước có ERROR:root:Unhandled exception:ở đầu dòng đầu tiên.


Một điểm khác biệt nữa là trước đây ghi dấu vết vào hệ thống ghi nhật ký, do đó, bất kỳ trình xử lý và trình định dạng nào bạn đã cài đặt đều được áp dụng. Sau này viết trực tiếp đến sys.stderr.
radiaph

8

Để xây dựng dựa trên câu trả lời của Jacinda, nhưng sử dụng một đối tượng logger:

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
Nó sẽ tốt hơn để sử dụng functools.partial()thay vì lambda. Xem: docs.python.org/2/library/functools.html#functools.partial
Mariusz Jamro

@MariuszJamro tại sao?
davr

4

Kết thúc cuộc gọi mục nhập ứng dụng của bạn trong một try...exceptkhối để bạn có thể bắt và đăng nhập (và có thể nâng lại) tất cả các ngoại lệ chưa được phát hiện. Ví dụ: thay vì:

if __name__ == '__main__':
    main()

Làm cái này:

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

Đây không phải là những gì câu hỏi đang hỏi. Ý định của câu hỏi là hỏi phải làm gì khi ngoại lệ KHÔNG được xử lý bằng mã.
Mayank Jaiswal

1
Chà, Python là ngôn ngữ lập trình và điều này ngụ ý rằng nó không làm mọi thứ "tự động" (như OP muốn), ngoại trừ nếu và khi bạn yêu cầu nó làm điều đó. Nói cách khác, không có cách nào để "tự động" ghi lại tất cả các ngoại lệ, trừ khi bạn viết mã cho nó - và đó là những gì trong câu trả lời của tôi.
flaviovs 30/03/2016

1
Chà, nếu bạn nhìn vào câu trả lời của Ned Batchelder, có một thứ gọi là móc ngoại lệ. Bạn phải xác định tại một nơi trong mã của bạn và tất cả các trường hợp ngoại lệ chưa được xử lý của bạn được xử lý.
Mayank Jaiswal

1
Móc ngoại lệ không thay đổi thực tế rằng nó không phải là "tự động" (theo nghĩa OP muốn) - nói cách khác, bạn vẫn phải mã hóa nó. Câu trả lời của Ned (sử dụng móc ngoại lệ) thực sự giải quyết được câu hỏi ban đầu - theo tôi , đó là cách mà nó ít hơn pythonic so với tôi.
flaviovs

1
Nó khá nhiều phụ thuộc vào mục tiêu của riêng bạn. Nếu bạn lập trình để làm hài lòng IDE, thì có, bắt tất cả các ngoại lệ có thể không phải là một lựa chọn. Nhưng nếu bạn muốn xử lý lỗi một cách duyên dáng và hiển thị phản hồi tốt cho người dùng, thì tôi e rằng bạn sẽ cần phải nắm bắt tất cả các ngoại lệ. Ok, đủ mỉa mai :-) - nếu bạn nhìn kỹ, bạn sẽ thấy mã đó chặn ngoại lệ, nhưng sau đó nâng lên, vì vậy trừ khi IDE của bạn đang làm điều gì đó "kỳ diệu" thì không nên làm, nó vẫn sẽ nhận được sự ngoại lệ.
flaviovs

3

Có lẽ bạn có thể làm một cái gì đó ở đầu mô-đun chuyển hướng stderr đến một tệp, sau đó đăng nhập tệp đó ở dưới cùng

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

Mặc dù câu trả lời của @ gnu_lorien cho tôi điểm khởi đầu tốt, chương trình của tôi gặp sự cố ngoại lệ đầu tiên.

Tôi đã đi kèm với một giải pháp cải tiến tùy chỉnh (và / hoặc), trong đó âm thầm ghi lại Ngoại lệ của các chức năng được trang trí @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

Để trả lời câu hỏi từ Mr.Zeus đã thảo luận trong phần bình luận của câu trả lời được chấp nhận, tôi sử dụng điều này để ghi lại các trường hợp ngoại lệ chưa được kiểm tra trong bảng điều khiển tương tác (được thử nghiệm với PyCharm 2018-2019). Tôi phát hiện ra sys.excepthooknó không hoạt động trong vỏ trăn nên tôi nhìn sâu hơn và thấy rằng tôi có thể sử dụng sys.exc_infothay thế. Tuy nhiên, sys.exc_infokhông có đối số không giống như sys.excepthookmất 3 đối số.

Ở đây, tôi sử dụng cả hai sys.excepthooksys.exc_infođể ghi lại cả hai ngoại lệ trong bảng điều khiển tương tác và tập lệnh có chức năng bao bọc. Để gắn một hàm hook cho cả hai hàm, tôi có hai giao diện khác nhau tùy thuộc vào việc các đối số có được đưa ra hay không.

Đây là mã:

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)

Thiết lập ghi nhật ký có thể được tìm thấy trong câu trả lời của gnu_lorien.


2

Trong trường hợp của tôi (sử dụng python 3) khi sử dụng câu trả lời của @Jacinda, nội dung của truy nguyên không được in. Thay vào đó, nó chỉ in chính đối tượng : <traceback object at 0x7f90299b7b90>.

Thay vào đó tôi làm:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.