Làm cách nào để ghi nhật ký lỗi Python với thông tin gỡ lỗi?


468

Tôi đang in các thông báo ngoại lệ Python vào một tệp nhật ký với logging.error:

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

Có thể in thông tin chi tiết hơn về ngoại lệ và mã đã tạo ra nó không chỉ là chuỗi ngoại lệ? Những thứ như số dòng hoặc dấu vết ngăn xếp sẽ là tuyệt vời.

Câu trả lời:


733

logger.exception sẽ xuất ra dấu vết ngăn xếp cùng với thông báo lỗi.

Ví dụ:

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

Đầu ra:

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

@Paulo Kiểm tra ghi chú, "lưu ý rằng trong Python 3, bạn phải gọi logging.exceptionphương thức ngay bên trong exceptphần. Nếu bạn gọi phương thức này ở một nơi tùy ý, bạn có thể nhận được một ngoại lệ kỳ quái. Tài liệu cảnh báo về điều đó."


131
Các exceptionphương pháp đơn giản là gọi error(message, exc_info=1). Ngay khi bạn chuyển exc_infosang bất kỳ phương thức ghi nhật ký nào từ ngữ cảnh ngoại lệ, bạn sẽ nhận được một dấu vết.
Helmut Grohne

16
Bạn cũng có thể đặt sys.excepthook(xem tại đây ) để tránh phải bọc tất cả mã của mình trong thử / ngoại trừ.
Tháng Bảy

23
Bạn chỉ có thể viết except Exception:vì bạn không sử dụng edưới bất kỳ hình thức nào;)
Marco Ferrari

21
Bạn rất có thể muốn kiểm tra ekhi cố gắng tương tác gỡ lỗi mã của bạn. :) Đây là lý do tại sao tôi luôn luôn bao gồm nó.
Vicki Laidler 16/2/2016

4
Sửa lỗi cho tôi nếu tôi sai, trong trường hợp này, không có xử lý thực sự của ngoại lệ và do đó, sẽ rất hợp lý khi thêm raisevào cuối exceptphạm vi. Nếu không, chạy sẽ tiếp tục như thể mọi thứ đều ổn.
Dror

184

Một điều tốt đẹp về logging.exceptioncâu trả lời SiggyF của không hiển thị là bạn có thể vượt qua trong một thông báo tùy ý, và khai thác gỗ vẫn sẽ hiển thị các traceback đầy đủ với tất cả các chi tiết ngoại lệ:

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

Với hành vi ghi nhật ký mặc định (trong các phiên bản gần đây) chỉ in lỗi sys.stderr, có vẻ như sau:

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

Một ngoại lệ có thể được ghi lại mà không cung cấp một tin nhắn?
Stevoisiak

@StevenVascellaro - Tôi khuyên bạn nên vượt qua ''nếu bạn thực sự không muốn nhập tin nhắn ... tuy nhiên chức năng không thể được gọi mà không có ít nhất một đối số, vì vậy, bạn sẽ phải cung cấp cho nó một cái gì đó.
ArtOfWarfare

147

Sử dụng exc_infotùy chọn có thể tốt hơn, để cho phép bạn chọn mức độ lỗi (nếu bạn sử dụng exception, nó sẽ luôn ở errormức):

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

@CivFan: Tôi thực sự không nhìn vào các chỉnh sửa khác hoặc phần giới thiệu bài đăng; phần giới thiệu đó cũng được thêm vào bởi một biên tập viên bên thứ 3. Tôi không thấy bất cứ nơi nào trong các bình luận bị xóa mà đó là ý định, nhưng tôi cũng có thể hoàn tác chỉnh sửa và xóa các bình luận, đã quá lâu để bỏ phiếu ở đây cho bất kỳ điều gì khác ngoài phiên bản đã chỉnh sửa .
Martijn Pieters

logging.fatalmột phương pháp trong thư viện đăng nhập? Tôi chỉ thấy critical.
Ian

1
@Ian Đó là một bí danh critical, giống như warnwarning.
0xc0de

35

Trích dẫn

Điều gì xảy ra nếu ứng dụng của bạn ghi nhật ký theo một cách khác - không sử dụng loggingmô-đun?

Bây giờ, tracebackcó thể được sử dụng ở đây.

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)
  • Sử dụng nó trong Python 2 :

    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
  • Sử dụng nó trong Python 3 :

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

Tại sao bạn lại đặt "_, _, ex_tracBack = sys.exc_info ()" bên ngoài hàm log_tracBack và sau đó chuyển nó làm đối số? Tại sao không sử dụng nó trực tiếp bên trong chức năng?
Basil Musa

@BasilMusa, để trả lời câu hỏi của bạn, nói ngắn gọn, để tương thích với Python 3, bởi vì ex_tracebackex.__traceback__thuộc về Python 3, nhưng ex_tracebacklà từ sys.exc_info()dưới Python 2.
zangw

12

Nếu bạn sử dụng nhật ký đơn giản - tất cả các bản ghi nhật ký của bạn sẽ tương ứng với quy tắc này : one record = one line. Theo quy tắc này, bạn có thể sử dụnggrep và các công cụ khác để xử lý tệp nhật ký của mình.

Nhưng thông tin truy nguyên là đa dòng. Vì vậy, câu trả lời của tôi là một phiên bản mở rộng của giải pháp được đề xuất bởi zangw ở trên trong chủ đề này. Vấn đề là các dòng truy nguyên có thể có \nbên trong, vì vậy chúng ta cần thực hiện thêm một công việc để thoát khỏi kết thúc dòng này:

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

Sau đó (khi bạn sẽ phân tích nhật ký của mình), bạn có thể sao chép / dán các dòng truy nguyên cần thiết từ tệp nhật ký của mình và thực hiện việc này:

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

Lợi nhuận!


9

Câu trả lời này được xây dựng từ những người xuất sắc ở trên.

Trong hầu hết các ứng dụng, bạn sẽ không được gọi log.exception (e) trực tiếp. Nhiều khả năng bạn đã xác định một trình ghi nhật ký tùy chỉnh cụ thể cho ứng dụng hoặc mô-đun của bạn như thế này:

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

Trong trường hợp này, chỉ cần sử dụng bộ ghi để gọi ngoại lệ (e) như thế này:

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

Đây thực sự là một kết thúc hữu ích nếu bạn muốn một logger chuyên dụng chỉ dành cho ngoại lệ.
logicOn sát thương

7

Bạn có thể đăng nhập theo dõi ngăn xếp mà không có ngoại lệ.

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

Đối số từ khóa tùy chọn thứ hai là stack_info, mặc định là Sai. Nếu đúng, thông tin ngăn xếp được thêm vào thông điệp ghi nhật ký, bao gồm cả cuộc gọi đăng nhập thực tế. Lưu ý rằng đây không phải là thông tin ngăn xếp giống như thông tin được hiển thị thông qua exc_info: Cái trước là khung ngăn xếp từ dưới cùng của ngăn xếp đến cuộc gọi đăng nhập trong luồng hiện tại, trong khi đó là thông tin về khung ngăn xếp không được cung cấp, theo một ngoại lệ, trong khi tìm kiếm xử lý ngoại lệ.

Thí dụ:

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

Một chút điều trị trang trí (lấy cảm hứng rất lỏng lẻo từ Có thể đơn nguyên và nâng). Bạn có thể xóa chú thích loại Python 3.6 một cách an toàn và sử dụng kiểu định dạng thư cũ hơn.

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

Bản giới thiệu:

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'

Bạn cũng có thể thay đổi giải pháp này để trả lại một cái gì đó có ý nghĩa hơn một chút so với Nonetừ các exceptphần (hoặc thậm chí làm cho giải pháp chung chung, bằng cách xác định giá trị trả về này trong fallibleđối số 's).


0

Trong mô-đun đăng nhập của bạn (nếu mô-đun tùy chỉnh) chỉ cần kích hoạt stack_info.

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

-1

Nếu bạn có thể đối phó với sự phụ thuộc thêm thì hãy sử dụng twist.log, bạn không phải ghi nhật ký lỗi một cách rõ ràng và nó cũng trả lại toàn bộ dấu vết và thời gian cho tệp hoặc luồng.


8
Có lẽ twistedlà một đề xuất tốt, nhưng câu trả lời này không thực sự đóng góp nhiều. Nó không nói cách sử dụng twisted.log, cũng không có lợi thế gì so với loggingmô-đun từ thư viện tiêu chuẩn, cũng không giải thích ý nghĩa của "bạn không phải ghi nhật ký lỗi rõ ràng" .
Đánh dấu Amery

-8

Một cách rõ ràng để làm điều đó là sử dụng format_exc()và sau đó phân tích đầu ra để có được phần có liên quan:

from traceback import format_exc

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

Trân trọng


4
Huh? Tại sao đó là "phần có liên quan" ? Tất cả những .split('\n')[-2]điều này là vứt bỏ số dòng và truy nguyên từ kết quả của format_exc()- thông tin hữu ích mà bạn thường muốn! Hơn nữa, nó thậm chí còn không làm tốt điều đó ; nếu thông báo ngoại lệ của bạn chứa một dòng mới, thì cách tiếp cận này sẽ chỉ in dòng cuối cùng của thông báo ngoại lệ - có nghĩa là bạn mất lớp ngoại lệ và hầu hết các thông báo ngoại lệ trên đầu mất dấu vết. -1.
Đánh dấu Amery
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.