Python'da yakalanmamış özel durumları günlüğe kaydetme


181

Yakalanmamış istisnaların loggingmodül yerine modül üzerinden çıkışına nasıl sebep oluyorsunuz stderr?

Bunu yapmanın en iyi yolunun:

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

Ama benim durumum öyle ki, bir istisna yakalanmadığında otomatik olarak çağrılsaydı gerçekten güzel olurdu logging.exception(...).



Yanıtlar:


143

Ned'in de işaret ettiği gibi, sys.excepthookher istisna ortaya çıktığında ve yakalanmadığında çağrılır. Bunun pratik anlamı, kodunuzda istediğiniz sys.excepthookşeyi (kullanma dahil logging.exception) yapmak için varsayılan davranışını geçersiz kılabilmenizdir .

Hasır bir adam örneği olarak:

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

Geçersiz kıl sys.excepthook:

>>> sys.excepthook = foo

Açık sözdizimi hatası verin (iki nokta üst üste bırakın) ve özel hata bilgilerini geri alın:

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

Hakkında daha fazla bilgi için belgelerisys.excepthook okuyun .


4
@Codemonkey Ayrılmış bir anahtar kelime değil, önceden var olan bir tür adı. typeBir işlev bağımsız değişkeni olarak kullanabilirsiniz , ancak IDE'ler genel gizleme konusunda şikayet edecek olsa da type( var self = thisJavascript'te kullanmak gibi ). typeİşlevinizin içindeki nesneye erişmeniz gerekmedikçe gerçekten önemli değildir , bu durumda type_bunun yerine argüman olarak kullanabilirsiniz .
Ryan P

3
İfade "her zaman" burada yanıltıcıdır :: "sys.excepthook çağrılır her zaman bir istisna yükseltti ve yakalanmamış olan" bir programda, orada çünkü ... olabilir tam bir "yakalanmamış" istisna. Ayrıca, sys.excepthookbir istisna "yükseltildiğinde" çağrılmaz. Bir kereden fazla gerçekleşemeyen yakalanmamış bir istisna nedeniyle program sona erdiğinde çağrılır.
Nawaz

2
@Nawaz: Bir REPL'de birden fazla olabilir
jfs

2
@Nawaz Program iş parçacığı kullanıyorsa, birden çok kez de olabilir. Ben de istisna sys.excepthook yaptı üç kez GUI olay döngüler (Qt gibi) çalışmaya devam gibi görünüyor
three_pineapples

1
Yukarıdaki kodu test etmeye çalışan herkes, işlevinizi test ederken bir geri izleme hatası oluşturduğunuzdan emin olun. Sözdizimi Hatası sys.excepthook tarafından işlenmiyor. Print (1/0) kullanabilirsiniz ve bu, sys.excepthook'u geçersiz kılmak için tanımladığınız işlevi çağırır
Parth Karia

177

İşte birkaç başka hileyi de içeren küçük bir örnek:

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")
  • Bir konsol python programının Ctrl + C ile çıkabilmesi için KeyboardInterrupt'ı yoksay.

  • İstisnayı biçimlendirmek için tamamen python'un kayıt modülüne güvenin.

  • Örnek bir işleyici ile özel bir günlükçü kullanın. Bu, işlenmeyen özel durumu stderr yerine stdout'a gitmek üzere değiştirir, ancak aynı stildeki her türlü işleyiciyi günlükçü nesnesine ekleyebilirsiniz.


13
Ben kullanacağı logger.critical()yakalanmamış istisna hoş diyorum kritik olduğundan, excepthook işleyicisi içinde.
gitaarik

2
Bu en pratik cevap IMO.
David Morales

@gnu_lorien snippet için teşekkürler. Bunu hangi dosyaya koyardınız?
stelios

@chefarov Diğer tüm günlük kaydını başlattığınız ana dosya
gnu_lorien

Merhaba, nasıl bu bilgiyi debug.log gibi bir dosyaya yazabiliriz. satır eklemek için deneyin logging.basicConfig(level=logging.DEBUG, filename="debug.log", format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')ama yardım etmedi.
GurhanCagin

26

sys.excepthookBir istisna yakalanmazsa yöntem çağrılır: http://docs.python.org/library/sys.html#sys.excepthook

Bir istisna oluşturulduğunda ve yakalanmadığında, yorumlayıcı sys.excepthook öğesini üç bağımsız değişken, istisna sınıfı, istisna örneği ve bir geri izleme nesnesi ile çağırır. Etkileşimli bir oturumda bu, denetim komut istemine dönmeden hemen önce gerçekleşir; Python programında bu, program çıkmadan hemen önce olur. Bu tür üst düzey istisnaların işlenmesi, sys.excepthook öğesine başka bir üç bağımsız değişken işlevi atayarak özelleştirilebilir.


2
Neden istisna sınıfını gönderiyor? Bunu her zaman typeörneği arayarak alamıyor musunuz?
Neil G

Parametrelerin türü nedir sys.excepthook?
Martin Thoma

23

Neden olmasın:

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

sys.excepthookYukarıda görüldüğü gibi çıktı :

$ 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

İşte sys.excepthookyorum ile çıktı:

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

Tek fark, ERROR:root:Unhandled exception:ilkinin ilk satırın başında olmasıdır.


Başka bir fark, ilkin izlemeyi günlük sistemine yazar; böylece yüklediğiniz tüm işleyiciler ve biçimlendiriciler uygulanır. İkincisi doğrudan yazıyor sys.stderr.
radiaph

8

Jacinda'nın cevabına dayanarak bir logger nesnesi kullanarak:

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
functools.partial()Lambda yerine kullanmak daha iyi olurdu . Bakınız: docs.python.org/2/library/functools.html#functools.partial
Mariusz Jamro

@MariuszJamro neden?
davr

4

Yakalanmayan try...excepttüm istisnaları yakalayabilmeniz ve kaydedebilmeniz (ve belki de yeniden yükseltebilmeniz) için uygulama giriş çağrınızı bir bloğa sarın . Örneğin;

if __name__ == '__main__':
    main()

Bunu yap:

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

Sorunun sorduğu şey bu değil. Sorunun amacı, istisna kod tarafından ele alınmadığında ne yapılacağını sormaktır.
Mayank Jaiswal

1
Python bir programlama dilidir ve bu, bunu yapmasını isteyip istemediğiniz ve ne zaman istemediğiniz dışında "otomatik olarak" (OP'nin istediği gibi) bir şey yapmadığını gösterir. Başka bir deyişle, kodlamadığınız sürece tüm istisnaları "otomatik olarak" günlüğe kaydetmenin bir yolu yoktur - ve benim cevabım budur.
flaviovs

1
Ned Batchelder'ın cevabına bakarsanız, istisna kancası olarak adlandırılan bir şey var. Kodunuzda bir yerde tanımlamanız gerekir ve yakalanmayan tüm istisnalarınız ele alınır.
Mayank Jaiswal

1
İstisna kancası "otomatik" (OP'nin istediği anlamda) olmadığı gerçeğini değiştirmez - başka bir deyişle, hala kodlamanız gerekir. Ned'in cevabı (istisna kancasını kullanan) gerçekten orijinal soruyu ele alıyor - bence , böyle yapmanın benden çok daha az pitonik olması.
flaviovs

1
Bu hemen hemen kendi hedeflerinize bağlıdır. IDE'yi memnun etmek için program yaparsanız, evet, tüm istisnaları yakalamak bir seçenek olmayabilir. Ancak hataları incelikle ele almak ve kullanıcıya güzel geri bildirimler görüntülemek istiyorsanız, tüm istisnaları yakalamanız gerekeceğinden korkuyorum. Tamam, yeterli alaycı :-) - dikkatle bakarsanız, kodun istisnayı durdurduğunu göreceksiniz, ancak o zaman yeniden yükseltin, bu yüzden IDE'niz "büyülü" bir şey yapmadıkça yapmamalı, yine de istisna.
flaviovs

3

Belki bir modülün üstünde stderr dosyasını bir dosyaya yönlendiren bir şey yapabilir ve daha sonra bu dosyayı altta kaydedebilirsiniz

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

@ Gnu_lorien'in cevabı bana iyi bir başlangıç ​​noktası verse de, programım ilk istisnada çöküyor.

Özel olarak (ve / veya) geliştirilmiş bir çözümle birlikte geldim @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

Mr.Zeus'un kabul edilen cevabın yorum bölümünde tartışılan soruyu cevaplamak için, etkileşimli bir konsolda yakalanmamış istisnaları kaydetmek için kullanıyorum (PyCharm 2018-2019 ile test edildi). sys.excepthookPython kabuğunda işe yaramadığını öğrendim , bu yüzden daha derine baktım ve sys.exc_infobunun yerine kullanabileceğimi gördüm . Ancak 3 argümandan sys.exc_infofarklı olarak hiçbir argüman almaz sys.excepthook.

İşte, hem kullanmak sys.excepthookve sys.exc_infobir sarıcı işlevi ile interaktif konsolda hem istisnalar ve bir senaryo oturum açmak için. Her iki işleve bir kanca işlevi eklemek için, bağımsız değişkenlerin verilmesine veya verilmemesine bağlı olarak iki farklı arabirimim var.

İşte kod:

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)

Günlüğe kaydetme kurulumu gnu_lorien'in cevabında bulunabilir.


2

Benim durumumda (kullanarak python 3) @Jacinda 's cevap kullanırken geri izleme içeriği yazdırılmadı. Bunun yerine, sadece nesnenin kendisi yazdırır: <traceback object at 0x7f90299b7b90>.

Bunun yerine şunu yaparı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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.