Catat pengecualian dengan traceback

152

Bagaimana saya bisa mencatat kesalahan Python saya?

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

Jawaban:

203

Gunakan logging.exceptiondari dalam except:pawang / blok untuk mencatat pengecualian saat ini bersama dengan informasi jejak, yang diawali dengan pesan.

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

Sekarang melihat file log, /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
nosklo
sumber
1
Melihat kode django untuk ini, dan saya berasumsi jawabannya tidak, tetapi apakah ada cara untuk membatasi traceback ke sejumlah karakter atau kedalaman tertentu? Masalahnya adalah bahwa untuk traceback besar, dibutuhkan waktu yang cukup lama.
Eduard Luca
10
Perhatikan bahwa jika Anda mendefinisikan logger dengan logger = logging.getLogger('yourlogger')Anda harus menulis logger.exception('...')agar ini berfungsi ...
576i
Bisakah kita memodifikasi ini sehingga pesan dicetak dengan INFO level log?
NM
Perhatikan bahwa untuk aplikasi eksternal tertentu, seperti wawasan Azure, jalur balikan tidak disimpan dalam log. Maka perlu untuk mengirimkannya secara eksplisit ke string pesan seperti yang ditunjukkan di bawah ini.
Edgar H
138

Gunakan exc_infoopsi mungkin lebih baik, tetap judul peringatan atau kesalahan:

try:
    # coode in here
except Exception as e:
    logging.error(e, exc_info=True)
flycee
sumber
Saya tidak pernah bisa mengingat apa yang exc_info=disebut kwarg; Terima kasih!
berto
4
Ini identik dengan logging.exception, dengan pengecualian bahwa jenis ini secara berlebihan dicatat dua kali. Cukup gunakan logging.exception kecuali Anda menginginkan level selain kesalahan.
Wyrmwood
@ Wymwood itu tidak identik karena Anda harus menyediakan pesan kelogging.exception
Peter Wood
57

Pekerjaan saya baru-baru ini menugasi saya untuk mencatat semua traceback / pengecualian dari aplikasi kami. Saya mencoba berbagai teknik yang telah diposting orang lain secara online seperti yang di atas tetapi memilih pendekatan yang berbeda. Mengesampingkan traceback.print_exception.

Saya punya tulisan di http://www.bbarrows.com/ Itu akan jauh lebih mudah dibaca tetapi saya akan menempelkannya di sini juga.

Ketika ditugaskan untuk mencatat semua pengecualian yang mungkin ditemui oleh perangkat lunak kami di alam liar, saya mencoba sejumlah teknik berbeda untuk mencatat traceback python pengecualian kami. Pada awalnya saya berpikir bahwa sistem hook python pengecualian, sys.excepthook akan menjadi tempat yang sempurna untuk memasukkan kode logging. Saya mencoba sesuatu yang mirip dengan:

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  

Ini berfungsi untuk utas utama tetapi saya segera menemukan bahwa sys.excepthook saya tidak akan ada di utas baru pun memulai proses saya. Ini adalah masalah besar karena kebanyakan semuanya terjadi di utas dalam proyek ini.

Setelah googling dan membaca banyak dokumentasi, informasi paling bermanfaat yang saya temukan adalah dari pelacak Isu Python.

Posting pertama di utas menunjukkan contoh kerja dari sys.excepthookTIDAK yang bertahan di utas (seperti yang ditunjukkan di bawah). Ternyata ini perilaku yang diharapkan.

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

Pesan pada utas Python Issue ini benar-benar menghasilkan 2 peretasan yang disarankan. Entah subkelas Threaddan bungkus metode jalankan dalam percobaan kami sendiri kecuali blok untuk menangkap dan mencatat pengecualian atau patch monyet threading.Thread.rununtuk berjalan dalam percobaan Anda sendiri kecuali memblokir dan mencatat pengecualian.

Metode pertama dari subclassing Threadbagi saya tampaknya kurang elegan dalam kode Anda karena Anda harus mengimpor dan menggunakan Threadkelas kustom di mana pun Anda ingin memiliki thread logging. Ini akhirnya menjadi masalah karena saya harus mencari seluruh basis kode kami dan mengganti semua normal Threadsdengan kebiasaan ini Thread. Namun, jelas apa yang Threadsedang dilakukan dan akan lebih mudah bagi seseorang untuk mendiagnosis dan men-debug jika ada yang salah dengan kode logging kustom. Thread custome logging mungkin terlihat seperti ini:

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

Metode kedua dari patch monyet threading.Thread.runbagus karena saya bisa menjalankannya sekali saja dan memasukkan __main__kode logging saya di semua pengecualian. Menambal monyet bisa mengganggu untuk debug meskipun karena mengubah fungsionalitas yang diharapkan dari sesuatu. Tambalan yang disarankan dari pelacak Isu Python adalah:

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

Tidak sampai saya mulai menguji pengecualian logging saya, saya menyadari bahwa saya salah melakukan semuanya.

Untuk menguji saya telah menempatkan a

raise Exception("Test")

di suatu tempat dalam kode saya. Namun, membungkus metode yang disebut metode ini adalah percobaan kecuali blok yang mencetak traceback dan menelan pengecualian. Ini sangat menyebalkan karena saya melihat traceback membawa cetakan ke STDOUT tetapi tidak dicatat. Saya kemudian memutuskan bahwa metode yang jauh lebih mudah untuk mencatat tracebacks adalah dengan menambal metode yang semua kode python gunakan untuk mencetak tracebacks sendiri, traceback.print_exception. Saya berakhir dengan sesuatu yang mirip dengan yang berikut:

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

Kode ini menulis traceback ke String Buffer dan mencatatnya untuk mencatat ERROR. Saya memiliki penangan logging kustom mengatur logger 'customLogger' yang mengambil log tingkat ERROR dan mengirim mereka pulang untuk analisis.

Brad Barrows
sumber
2
Pendekatan yang cukup menarik. Satu pertanyaan - add_custom_print_exceptiontampaknya tidak ada di situs yang Anda tautkan, dan alih-alih ada beberapa kode akhir yang sangat berbeda di sana. Mana yang akan Anda katakan lebih baik / lebih final dan mengapa? Terima kasih!
fantabolous
Terima kasih, jawaban yang bagus!
101
Ada salah ketik dan tempel. pada panggilan yang didelegasikan ke old_print_exception, batas dan file harus melewati batas dan file, bukan Tidak ada - old_print_exception (etype, nilai, tb, batas, file)
Marvin
Untuk blok kode terakhir Anda, daripada menginisialisasi StringIO dan mencetak pengecualian untuk itu, Anda bisa memanggil logger.error(traceback.format_tb())(atau format_exc () jika Anda menginginkan info pengecualian juga).
James
8

Anda dapat mencatat semua pengecualian yang tidak tertangkap pada utas utama dengan menetapkan penangan sys.excepthook, mungkin menggunakan exc_infoparameter fungsi logging 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')

Namun, jika program Anda menggunakan utas, maka perhatikan bahwa utas yang dibuat menggunakan tidakthreading.Thread akan memicu ketika pengecualian tanpa tertangkap terjadi di dalamnya, seperti yang dicatat dalam Masalah 1230540 pada pelacak masalah Python. Beberapa peretasan telah disarankan di sana untuk mengatasi keterbatasan ini, seperti penambalan monyet untuk menimpa dengan metode alternatif yang membungkus sumber asli dalam sebuah blok dan panggilan dari dalam blok. Atau, Anda bisa hanya secara manual membungkus entry point untuk setiap benang Anda di / diri sendiri.sys.excepthookThread.__init__self.runruntrysys.excepthookexcepttryexcept

Mark Amery
sumber
3

Pesan pengecualian yang tidak tertangkap masuk ke STDERR, jadi alih-alih menerapkan logging dengan Python sendiri, Anda bisa mengirim STDERR ke file menggunakan shell apa pun yang Anda gunakan untuk menjalankan skrip Python. Dalam skrip Bash, Anda bisa melakukan ini dengan pengalihan output, seperti yang dijelaskan dalam panduan BASH .

Contohnya

Tambahkan kesalahan ke file, output lain ke terminal:

./test.py 2>> mylog.log

Timpa file dengan output STDOUT dan STDERR yang disisipkan:

./test.py &> mylog.log
panchicore
sumber
2

Apa yang saya cari:

import sys
import traceback

exc_type, exc_value, exc_traceback = sys.exc_info()
traceback_in_var = traceback.format_tb(exc_traceback)

Lihat:

Martin Thoma
sumber
1

Anda bisa mendapatkan traceback menggunakan logger, di tingkat mana pun (DEBUG, INFO, ...). Perhatikan bahwa menggunakan logging.exception, levelnya ERROR.

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

EDIT:

Ini juga berfungsi (menggunakan python 3.6)

logging.debug("Something went wrong", exc_info=True)
Constantin De La Roche
sumber
1

Ini adalah versi yang menggunakan 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
sveilleux2
sumber
Bagaimana kalau menggunakan {traceback.format_exc()}bukan {traceback.format_tb(stack)}?
variabel
0

mungkin tidak bergaya, tetapi lebih mudah:

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

Inilah contoh sederhana yang diambil dari dokumentasi 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')
rogeriopvl
sumber
4
Pertanyaannya adalah bagaimana cara mencatat traceback
Konstantin Schubert