Geri izleme ile günlük istisnası


152

Python hatalarımı nasıl kaydedebilirim?

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

Yanıtlar:


203

Geçerli özel durumu bir mesajla birlikte gelen izleme bilgileriyle birlikte günlüğe kaydetmek logging.exceptioniçin except:işleyici / blok içinden kullanın .

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

Şimdi günlük dosyasına bakalım /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
Bunun için django kodu üzerinde baktım ve cevabın hayır olduğunu varsayıyorum, ama izleme karakter ya da derinlik belirli bir miktar ile sınırlamak için bir yolu var mı? Sorun, büyük izlemeler için oldukça uzun sürmesi.
Eduard Luca

10
Unutmayın ki bir logger tanımlıyorsanız , bu çalışmayı yapabilmek logger = logging.getLogger('yourlogger')için yazmak logger.exception('...')zorundasınız ...
576i

Bunu, ileti günlük düzeyi INFO ile yazdırılacak şekilde değiştirebilir miyiz?
NM

Azure içgörü gibi bazı harici uygulamalar için geri izlemenin günlüklerde saklanmadığını unutmayın. Daha sonra bunları aşağıda gösterildiği gibi açıkça mesaj dizesine iletmek gerekir.
Edgar H

139

Kullanım exc_infoseçenekleri daha iyi olabilir, uyarı veya hata başlığı kalır:

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

exc_info=Kwarg'ın ne dediğini asla hatırlayamıyorum ; Teşekkürler!
berto

4
Bu, türün yedekli olarak iki kez kaydedilmesi dışında logging.exception ile aynıdır. Hata dışında bir seviye istemiyorsanız logging.exception komutunu kullanın.
Wyrmwood

@Wyrmwood bir mesaj vermek zorunda aynı değillogging.exception
Peter Wood

57

İşim kısa bir süre önce uygulamamızdaki tüm geri bildirimleri / istisnaları günlüğe kaydetmemi sağladı. Başkalarının çevrimiçi olarak yayınladığı yukarıdaki gibi çok sayıda teknik denedim, ancak farklı bir yaklaşıma karar verdim. Geçersiz kılıyor traceback.print_exception.

Http://www.bbarrows.com/ adresinde bir yazı var Bu okumak çok daha kolay olurdu ama ben de buraya yapıştırın.

Yazılımımızın vahşi ortamda karşılaşabileceği tüm istisnaları günlüğe kaydetmeyle görevlendirildiğinde, python istisna geri bildirimlerimizi günlüğe kaydetmek için bir dizi farklı teknik denedim. İlk başta python sistemi istisna kancası, sys.excepthook günlük kodunu eklemek için mükemmel bir yer olacağını düşündüm. Şuna benzer bir şey deniyordum:

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  

Bu ana iş parçacığı için çalıştı ama yakında benim sys.excepthook benim işlem başladı herhangi bir yeni iş parçacıkları arasında var olmadığını bulundu. Bu çok büyük bir konudur çünkü her şey bu projedeki iş parçacıklarında olur.

Googling ve bol miktarda belge okuduktan sonra bulduğum en yararlı bilgiler Python Issue tracker'dan geldi.

İplikteki ilk direk, sys.excepthookdişler arasında devam etmeyen NOT'un çalışan bir örneğini gösterir (aşağıda gösterildiği gibi). Görünüşe göre bu beklenen davranıştır.

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

Bu Python Issue dizisindeki mesajlar gerçekten önerilen 2 hack ile sonuçlanır. Threadİstisnaları yakalamak ve günlüğe kaydetmek için run yöntemini alt sınıftan ayırın ve kendi denememizde sarın veya threading.Thread.runblok ve istisnalar hariç kendi denemenizde çalıştırmak için maymun yaması .

Alt sınıflamanın ilk yöntemi, her gün Threadözel bir Threadsınıfınızı bir günlük iş parçacığına sahip olmak istediğinizde içe aktarmak ve kullanmak zorunda kalacağınız için kodunuzda daha az zarif görünüyor . Tüm kod tabanımızı aramak ve tüm normalleri Threadsbu özel ile değiştirmek zorunda kaldım çünkü bu bir güçlük oldu Thread. Bununla birlikte, bunun ne yaptığı açıktı Threadve özel günlük kodunda bir sorun olduğunda birisinin teşhis ve hata ayıklaması daha kolay olurdu. Özel günlük kaydı iş parçacığı şöyle görünebilir:

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

Maymun yamasının ikinci yöntemi threading.Thread.rungüzel çünkü hemen sonra bir kez çalıştırabilirim __main__ve günlük kodumu tüm istisnalarda kullanabilirim. Maymun yama, bir şeyin beklenen işlevselliğini değiştirdiği için hata ayıklamak için can sıkıcı olabilir. Python Issue tracker'dan önerilen yama:

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

İstisna günlüğümü test etmeye başlayana kadar her şey yanlış gittiğimi fark ettim.

Test etmek için bir

raise Exception("Test")

kodumda bir yerde. Ancak, bu yöntemi çağırmak aa yöntemini sarma, geri izleme yazdırdı ve özel durumu yuttu blok dışında bir deneyin oldu. Bu çok sinir bozucuydu çünkü izlemenin STDOUT'a basıldığını gördüm ama günlüğe kaydedilmedim. Daha sonra, izlemeleri günlüğe kaydetmenin çok daha kolay bir yönteminin, sadece pcehon kodunun traceback.print_exception adlı izleri yazdırmak için kullandığı yöntemi düzeltmek olduğuna karar verdim. Aşağıdakine benzer bir şey buldum:

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

Bu kod, izleme dizesini bir String Buffer'a yazar ve bunu ERROR günlüğüne kaydeder. ERROR seviye günlükleri alır ve analiz için eve gönderir 'customLogger' logger kurmak özel bir günlük işleyicisi var.


2
Oldukça ilginç bir yaklaşım. Bir soru - add_custom_print_exceptionbağlandığınız sitede görünmüyor ve bunun yerine orada oldukça farklı bir son kod var. Hangisinin daha iyi / daha nihai olduğunu söylerdiniz ve neden? Teşekkürler!
fantabolous

Teşekkürler, harika cevap!
101

Kes ve yapıştır yazım hatası var. old_print_exception delege çağrısında, sınır ve dosya sınır ve dosyadan geçirilmeli, Hiçbiri değil - old_print_exception (etype, value, tb, limit, file)
Marvin

Son kod bloğunuz için, bir StringIO'yu başlatmak ve istisnayı yazdırmak yerine logger.error(traceback.format_tb()), istisna bilgisi de istiyorsanız (veya format_exc () öğesini çağırabilirsiniz .
James

8

sys.excepthookBelki de exc_infoPython'un günlüğe kaydetme işlevlerinin parametresini kullanarak bir işleyici atayarak ana iş parçacığında yakalanmamış tüm özel durumları günlüğe kaydedebilirsiniz :

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

Programınız kullandığı ipler, ancak, daha sonra kullanılarak oluşturulan bu konuları dikkate ederse threading.Threadolacak değil tetiği sys.excepthookde belirtildiği gibi yakalanmamış istisna, içlerinde meydana geldiğinde Issue 1230540 Python'un sorun izleyicide üzerinde. Orjinalini bir blokta saran ve çağrı yapan alternatif bir yöntemle maymun yaması gibi bazı kısıtlamaların bu sınırlamanın üstesinden Thread.__init__gelmesi önerildi.self.runruntrysys.excepthook içindeki exceptblok. Alternatif olarak, her bir iş parçacığınızın giriş noktasını kendiniz try/ exceptkendiniz manuel olarak sarabilirsiniz .


3

Yakalanmayan istisna iletileri STDERR'a gider, bu nedenle Python'da kendi günlüklemenizi uygulamak yerine Python komut dosyanızı çalıştırmak için kullandığınız kabuğu kullanarak bir dosyaya STDERR gönderebilirsiniz. Bash betiğinde, bunu BASH kılavuzunda açıklandığı gibi çıkış yeniden yönlendirmesi ile yapabilirsiniz. .

Örnekler

Dosyaya, diğer çıkışları terminale ekleyin:

./test.py 2>> mylog.log

Araya eklenen STDOUT ve STDERR çıktısıyla dosyanın üzerine yaz:

./test.py &> mylog.log


2

Geri izleme herhangi bir düzeyde (DEBUG, INFO, ...) bir logger kullanarak alabilirsiniz. Kullanırken logging.exception, seviyenin HATA olduğunu unutmayın.

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

DÜZENLE:

Bu da işe yarar (python 3.6 kullanarak)

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

1

İşte sys.excepthook kullanan bir sürüm

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

{traceback.format_exc()}Bunun yerine kullanmaya ne dersiniz {traceback.format_tb(stack)}?
değişken

0

belki o kadar şık değil, ama daha kolay:

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

-1

Heres python 2.6 belgelerinden alınan basit bir örnek :

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
Soru, izlemenin nasıl kaydedileceğiydi
Konstantin Schubert
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.