Hata ayıklama bilgileriyle bir Python hatasını nasıl kaydedebilirim?


470

Bir günlük dosyasına Python özel durum iletileri yazdırıyorum logging.error:

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

Kural dışı durum ve kodu oluşturan kural hakkında, kural dışı durum dizesinden daha ayrıntılı bilgi yazdırmak mümkün müdür? Satır numaraları veya yığın izleri gibi şeyler harika olurdu.

Yanıtlar:


735

logger.exception hata mesajının yanında bir yığın izlemesi çıkarır.

Örneğin:

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

Çıktı:

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

@Paulo Notları kontrol edin , "Python 3'te logging.exceptionyöntemi exceptparçanın hemen içinde çağırmanız gerektiğini unutmayın . Bu yöntemi rastgele bir yerde çağırırsanız tuhaf bir istisna alabilirsiniz. Dokümanlar bu konuda uyarıyor."


131
exceptionYöntem basitçe çağırır error(message, exc_info=1). exc_infoBir istisna bağlamından günlük yöntemlerinden herhangi birine geçtiğinizde , bir geri izleme alırsınız.
Helmut Grohne

16
Ayrıca , deneme / hariç tüm kodlarınızı sarmaktan kaçınmak için sys.excepthook( buraya bakın ) ayarlayabilirsiniz .
Temmuz

23
Yazabilirsiniz except Exception:çünkü ehiçbir şekilde kullanmıyorsunuz ;)
Marco Ferrari

21
eKodunuzda etkileşimli olarak hata ayıklamaya çalışırken çok iyi incelemek isteyebilirsiniz . :) Bu yüzden hep ekliyorum.
Vicki Laidler

4
Yanılıyorsam beni düzeltin, bu durumda, istisnanın gerçek bir kullanımı yoktur ve bu nedenle kapsamın raisesonuna eklemek mantıklıdır except. Aksi takdirde, koşma her şey yolunda gibi devam edecektir.
Dror

184

Hakkında güzel bir şey logging.exceptionolduğunu SiggyF cevabı göstermiyor Eğer keyfi bir mesajda geçebilir ve günlüğü hala tüm istisna detayları ile tam Traceback göstermesidir:

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

Yalnızca yazdırma hatalarının varsayılan (son sürümlerde) günlük tutma davranışıyla sys.stderr, şöyle görünür:

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

Bir istisna, mesaj verilmeden kaydedilebilir mi?
Stevoisiak

@StevenVascellaro - ''Gerçekten bir mesaj yazmak istemiyorsanız geçmenizi öneririm ... fonksiyon en az bir argüman olmadan çağrılamaz , bu yüzden ona bir şey vermeniz gerekecek.
ArtOfWarfare

148

exc_infoHata düzeyini seçmenize izin vermek için seçenekleri kullanmak daha iyi olabilir (kullanırsanız exception, her zaman errordüzeydedir):

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

@CivFan: Aslında diğer düzenlemelere veya yazı girişine bakmadım; bu giriş üçüncü taraf bir editör tarafından da eklendi. Silinen yorumlarda hiç niyetin olduğunu hiçbir yerde göremiyorum, ancak düzenlememi geri alabilir ve yorumları kaldırabilirim, burada oylamanın düzenlenmiş sürümden başka bir şey olması çok uzun sürdü .
Martijn Pieters

logging.fatalGünlük kitaplığında bir yöntem var mı ? Sadece görüyorum critical.
Ian

1
@Ian Bu bir takma ad criticalgibi, warnetmektir warning.
0xc0de

35

Alıntı yapmak

Uygulamanız loggingmodülü başka bir şekilde günlüğe kaydetmiyorsa - modülü kullanmıyorsa ?

Şimdi, tracebackburada kullanılabilir.

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)
  • Python 2'de kullanın :

    try:
        # your function call is here
    except Exception as ex:
        _, _, ex_traceback = sys.exc_info()
        log_traceback(ex, ex_traceback)
  • Python 3'te kullanın :

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

Neden "_, _, ex_traceback = sys.exc_info ()" fonksiyonunu log_traceback fonksiyonunun dışına koydunuz ve daha sonra bir argüman olarak ilettiniz? Neden doğrudan işlevin içinde kullanılmıyor?
Basil Musa

@BasilMusa, çünkü Python 3 ile uyumlu için, kısacası, sorunuzun cevabı ex_tracebackdan ex.__traceback__Python 3 altında, ama ex_tracebackdan sys.exc_info()Python 2. altında
zangw

12

Düz günlükleri kullanırsanız - tüm log kayıtları bu kuralı karşılık gelmelidir: one record = one line. Bu kurala göre grepgünlük dosyalarınızı işlemek için ve diğer araçları kullanabilirsiniz.

Ancak geri izleme bilgileri çok satırlıdır. Bu yüzden cevabım, bu iş parçacığında zangw tarafından önerilen çözümün genişletilmiş bir sürümüdür . Sorun şu ki, geri izleme satırları \niçeride olabilir , bu nedenle bu satır sonlarından kurtulmak için ekstra bir iş yapmamız gerekiyor:

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

Bundan sonra (günlüklerinizi analiz ederken) gerekli geri izleme satırlarını günlük dosyanızdan kopyalayabilir / yapıştırabilir ve bunu yapabilirsiniz:

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

Kar!


9

Bu cevap yukarıdaki mükemmel cevaplardan oluşur.

Çoğu uygulamada, doğrudan logging.exception (e) öğesini çağırmazsınız. Büyük olasılıkla uygulamanıza veya modülünüze özgü özel bir günlükçü tanımladınız:

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

Bu durumda, istisnayı (e) şu şekilde çağırmak için kaydediciyi kullanın:

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

Sadece istisnalar için özel bir kaydedici istiyorsanız, bu gerçekten kullanışlı bir son dokunuş.
logicOnAbstractions

7

İstisna olmadan yığın izlemesini günlüğe kaydedebilirsiniz.

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

İsteğe bağlı ikinci anahtar kelime bağımsız değişkeni, varsayılan olarak False olan stack_info şeklindedir. True olursa, günlük kaydına gerçek günlük çağrısı da dahil olmak üzere yığın bilgisi eklenir. Bunun exc_info belirtilerek görüntülenenle aynı yığın bilgisi olmadığını unutmayın: Birincisi yığının altından geçerli iş parçacığındaki günlük çağrısına kadar yığın çerçeveleridir, ikincisi ise açılmış olan yığın çerçeveler hakkında bilgi, bir istisnayı takiben, istisna işleyicileri ararken.

Misal:

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

Biraz dekoratör tedavisi (Maybe monad ve kaldırmasından çok gevşek esinlenerek). Python 3.6 tipi ek açıklamaları güvenle kaldırabilir ve daha eski bir mesaj biçimlendirme stili kullanabilirsiniz.

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

Demo:

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'

Ayrıca bu çözümü None, exceptparçadan biraz daha anlamlı bir şey döndürmek için değiştirebilirsiniz (veya çözümü fallible'argümanlarında bu dönüş değerini belirterek çözümü genel hale getirebilirsiniz ).


0

Günlük modülünüzde (özel modül ise) stack_info'yu etkinleştirmeniz yeterlidir.

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

-1

Ekstra bağımlılık ile başa çıkabilir, sonra twisted.log kullanın, hataları açıkça günlüğe kaydetmek zorunda değilsiniz ve ayrıca dosya veya akış için tüm geri izleme ve zaman döndürür.


8
Belki twistediyi bir tavsiye, ama bu cevap gerçekten çok fazla katkıda bulunmuyor. Nasıl kullanılacağını twisted.logveya loggingmodülün standart kütüphaneden ne gibi avantajları olduğunu ya da "hataları açıkça kaydetmek zorunda değilsiniz" ile ne kastedildiğini açıklamaz .
Mark Amery

-8

Bunu yapmanın temiz bir yolu format_exc(), ilgili parçayı almak için çıktıyı kullanmak ve sonra ayrıştırmaktır:

from traceback import format_exc

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

Saygılarımızla


4
Ha? Neden bu "ilgili kısım" ? Tüm .split('\n')[-2]yapmasıdır atmak sonucundan hat numarası ve Traceback format_exc()normalde istediğiniz yararlı bilgiler -! Dahası, hatta iyi bir iş yapmaz o ; kural dışı durum iletinizde yeni satır varsa, bu yaklaşım yalnızca kural dışı durum iletisinin son satırını yazdırır. Bu, kural dışı durum sınıfını ve kural dışı durum iletisinin çoğunu izlemeyi kaybetmenin en üstünde kaybedeceğiniz anlamına gelir. -1.
Mark Amery
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.