Đăng nhập ngoại lệ với truy nguyên


152

Làm cách nào để ghi nhật ký lỗi Python?

try:
    do_something()
except:
    # How can I log my exception here, complete with its traceback?

Câu trả lời:


203

Sử dụng logging.exceptiontừ bên trong except:trình xử lý / khối để ghi lại ngoại lệ hiện tại cùng với thông tin theo dõi, được gửi kèm theo một thông báo.

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

Bây giờ nhìn vào tệp nhật ký , /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
Đã xem qua mã django cho điều này, và tôi cho rằng câu trả lời là không, nhưng có cách nào để hạn chế truy nguyên ở một số lượng nhất định của một trong hai ký tự hoặc độ sâu không? Vấn đề là đối với các tracebacks lớn, nó mất khá nhiều thời gian.
Eduard Luca

10
Lưu ý rằng nếu bạn xác định một logger với logger = logging.getLogger('yourlogger')bạn phải viết logger.exception('...')để hoạt động này ...
576i

Chúng tôi có thể sửa đổi điều này để thông báo được in với mức nhật ký INFO không?
NM

Lưu ý rằng đối với một số ứng dụng bên ngoài nhất định, chẳng hạn như thông tin chi tiết Azure, trackback không được lưu trong nhật ký. Sau đó, cần phải chuyển chúng rõ ràng vào chuỗi thông báo như hiển thị bên dưới.
Edgar H

138

exc_infoTùy chọn sử dụng có thể tốt hơn, vẫn là cảnh báo hoặc tiêu đề lỗi:

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

Tôi không bao giờ có thể nhớ những gì exc_info=kwarg được gọi là; cảm ơn!
berto

4
Điều này giống hệt với log.exception, ngoại trừ loại được ghi lại hai lần. Chỉ cần sử dụng log.exception trừ khi bạn muốn một mức độ khác hơn là lỗi.
Wyrmwood

@Wyrmwood không giống như bạn phải cung cấp một tin nhắn tớilogging.exception
Peter Wood

57

Công việc của tôi gần đây đã giao nhiệm vụ cho tôi đăng nhập tất cả các dấu vết / ngoại lệ từ ứng dụng của chúng tôi. Tôi đã thử rất nhiều kỹ thuật mà những người khác đã đăng lên mạng như cách trên nhưng đã giải quyết theo một cách tiếp cận khác. Ghi đè traceback.print_exception.

Tôi có một bài viết tại http://www.bbarrows.com/ Điều đó sẽ dễ đọc hơn nhiều nhưng Ill cũng dán nó vào đây.

Khi được giao nhiệm vụ ghi nhật ký tất cả các ngoại lệ mà phần mềm của chúng tôi có thể gặp phải trong tự nhiên, tôi đã thử một số kỹ thuật khác nhau để ghi nhật ký truy xuất ngoại lệ python của chúng tôi. Lúc đầu, tôi nghĩ rằng hook ngoại lệ của hệ thống python, sys.ex805thook sẽ là nơi hoàn hảo để chèn mã đăng nhập. Tôi đã thử một cái gì đó tương tự như:

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  

Điều này làm việc cho chủ đề chính nhưng tôi sớm nhận thấy rằng sys.ex805thook của tôi sẽ không tồn tại trên bất kỳ chủ đề mới nào mà quá trình của tôi bắt đầu. Đây là một vấn đề lớn bởi vì hầu hết mọi thứ xảy ra trong các chủ đề trong dự án này.

Sau khi googling và đọc nhiều tài liệu, thông tin hữu ích nhất tôi tìm thấy là từ trình theo dõi vấn đề Python.

Bài đăng đầu tiên trên chuỗi cho thấy một ví dụ hoạt động của sys.excepthookKHÔNG tồn tại trên các luồng (như được hiển thị bên dưới). Rõ ràng đây là hành vi dự kiến.

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

Các thông báo trên luồng Vấn đề Python này thực sự dẫn đến 2 vụ hack được đề xuất. Hoặc là lớp con Threadvà bọc phương thức chạy trong thử riêng của chúng tôi ngoại trừ khối để bắt và ghi nhật ký ngoại lệ hoặc bản vá khỉ threading.Thread.runđể chạy trong thử của riêng bạn ngoại trừ chặn và ghi nhật ký ngoại lệ.

Phương pháp phân lớp đầu tiên Threadđối với tôi có vẻ kém thanh lịch trong mã của bạn vì bạn sẽ phải nhập và sử dụng Threadlớp tùy chỉnh của mình MỌI NƠI bạn muốn có một luồng đăng nhập. Điều này cuối cùng là một rắc rối vì tôi phải tìm kiếm toàn bộ cơ sở mã của chúng tôi và thay thế tất cả bình thường Threadsvới tùy chỉnh này Thread. Tuy nhiên, rõ ràng điều này Threadđang làm và sẽ dễ dàng hơn cho ai đó chẩn đoán và gỡ lỗi nếu có lỗi xảy ra với mã đăng nhập tùy chỉnh. Một chủ đề đăng nhập theo yêu cầu có thể trông như thế này:

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

Phương pháp vá khỉ thứ hai threading.Thread.runrất hay vì tôi chỉ có thể chạy nó một lần ngay sau đó __main__và sử dụng mã đăng nhập của mình trong tất cả các trường hợp ngoại lệ. Khỉ vá có thể gây khó chịu khi gỡ lỗi mặc dù nó thay đổi chức năng dự kiến ​​của một cái gì đó. Bản vá được đề xuất từ ​​trình theo dõi Vấn đề Python là:

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

Mãi đến khi tôi bắt đầu thử nghiệm ghi nhật ký ngoại lệ của mình, tôi mới nhận ra rằng mình đã sai.

Để kiểm tra tôi đã đặt một

raise Exception("Test")

ở đâu đó trong mã của tôi. Tuy nhiên, gói phương thức aa được gọi là phương pháp này là một thử ngoại trừ khối đã in ra dấu vết và nuốt ngoại lệ. Điều này đã rất bực bội vì tôi đã thấy bản in mang đến STDOUT nhưng không được ghi lại. Sau đó, tôi đã quyết định rằng một phương pháp ghi nhật ký các tracebacks dễ dàng hơn nhiều chỉ là để con khỉ vá phương thức mà tất cả mã trăn sử dụng để tự in tracebacks, tracBack.print_exception. Tôi đã kết thúc với một cái gì đó tương tự như sau:

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

Mã này ghi truy nguyên vào Bộ đệm chuỗi và ghi nhật ký để ghi nhật ký LRI. Tôi có một trình xử lý ghi nhật ký tùy chỉnh thiết lập trình ghi nhật ký 'customLogger', lấy nhật ký mức ERROR và gửi chúng về nhà để phân tích.


2
Khá là một cách tiếp cận thú vị. Một câu hỏi - add_custom_print_exceptiondường như không có trên trang web mà bạn liên kết đến, và thay vào đó có một số mã cuối cùng khá khác nhau ở đó. Cái nào bạn muốn nói là tốt hơn / cuối cùng và tại sao? Cảm ơn!
tưởng tượng

Cảm ơn, câu trả lời tuyệt vời!
101

Có một lỗi đánh máy và dán. trong cuộc gọi được ủy nhiệm đến old_print_exception, giới hạn và tệp phải được chuyển qua giới hạn và tệp, không phải là - old_print_exception (etype, value, tb, giới hạn, tệp)
Marvin

Đối với khối mã cuối cùng của bạn, thay vì khởi tạo StringIO và in ngoại lệ cho nó, bạn chỉ có thể gọi logger.error(traceback.format_tb())(hoặc format_exc () nếu bạn cũng muốn thông tin ngoại lệ).
James

8

Bạn có thể ghi nhật ký tất cả các trường hợp ngoại lệ chưa xử lý vào luồng chính bằng cách gán một trình xử lý sys.excepthook, có thể sử dụng exc_infotham số của các hàm ghi nhật ký của 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')

Tuy nhiên, nếu chương trình của bạn sử dụng các luồng, thì lưu ý rằng các luồng được tạo bằng cách sử dụng threading.Threadsẽ không kích hoạt sys.excepthookkhi xảy ra ngoại lệ chưa được xử lý bên trong chúng, như đã lưu ý trong Vấn đề 1230540 về trình theo dõi vấn đề của Python. Một số hack đã được đề xuất ở đó để khắc phục giới hạn này, như vá khỉ Thread.__init__để ghi đè lên self.runbằng một runphương pháp thay thế bao bọc bản gốc trong một trykhối và gọi sys.excepthooktừ bên trong exceptkhối. Ngoài ra, bạn chỉ có thể tự bọc điểm nhập cảnh cho từng chuỗi của mình trong try/ exceptchính mình.


3

Các thông báo ngoại lệ chưa được chuyển đến STDERR, vì vậy thay vì thực hiện đăng nhập bằng chính Python, bạn có thể gửi STDERR đến một tệp bằng bất kỳ shell nào bạn đang sử dụng để chạy tập lệnh Python của mình. Trong tập lệnh Bash, bạn có thể thực hiện việc này với chuyển hướng đầu ra, như được mô tả trong hướng dẫn BASH .

Ví dụ

Nối các lỗi vào tập tin, đầu ra khác vào thiết bị đầu cuối:

./test.py 2>> mylog.log

Ghi đè tập tin với đầu ra STDOUT và STDERR xen kẽ:

./test.py &> mylog.log


1

Bạn có thể nhận được truy nguyên bằng bộ ghi, ở mọi cấp độ (DEBUG, INFO, ...). Lưu ý rằng sử dụng logging.exception, cấp độ là LRI.

# 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: :(

BIÊN TẬP:

Điều này cũng hoạt động (sử dụng python 3.6)

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

1

Đây là một phiên bản sử dụng sys.ex805thook

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

Làm thế nào về việc sử dụng {traceback.format_exc()}thay vì {traceback.format_tb(stack)}?
biến

0

có thể không phong cách, nhưng dễ dàng hơn:

#!/bin/bash
log="/var/log/yourlog"
/path/to/your/script.py 2>&1 | (while read; do echo "$REPLY" >> $log; done)

-1

Đây là một ví dụ đơn giản được lấy từ tài liệu 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
Câu hỏi là làm thế nào để đăng nhập truy nguyên
Konstantin Schubert
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.