Tek bir dosya kullanarak Python Günlüğü (işlev adı, dosya adı, satır numarası)


109

Bir uygulamanın nasıl çalıştığını öğrenmeye çalışıyorum. Ve bunun için, günlük çıktısına bir mesaj gönderdiğim satır numarasını (kod içinde) yanı sıra işlevin adını da günlüğe kaydetme amacıyla her işlevin gövdesinin ilk satırı olarak hata ayıklama komutlarını ekliyorum. Son olarak, bu uygulama birçok dosyadan oluştuğundan, uygulamanın kontrol akışını daha iyi anlayabilmek için tek bir günlük dosyası oluşturmak istiyorum.

İşte bildiğim şey:

  1. işlev adını almak için kullanabilirim, function_name.__name__ancak işlev_adı kullanmak istemiyorum (böylece Log.info("Message")tüm işlevlerin gövdesine bir jeneriği hızlı bir şekilde kopyalayıp yapıştırabilirim ). Bunun __func__makro kullanılarak C'de yapılabileceğini biliyorum ama python konusunda emin değilim.

  2. dosya adını ve satır numarasını elde etmek için, uygulamamın Python locals()işlevini kullandığını gördüm (ve buna inanıyorum), ancak tam olarak farkında olmadığım bir sözdiziminde, örneğin: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals())ve bunu LOG.info("My message %s" % locals())hangisinin benzeri bir şey üretir gibi kullanmayı denedim {'self': <__main__.Class_name object at 0x22f8cd0>}. Bununla ilgili herhangi bir girdi lütfen?

  3. Günlüğe kaydetmeyi nasıl kullanacağımı ve bir dosyaya oturum açmak için ona işleyici eklemeyi biliyorum, ancak tüm günlük mesajlarını projedeki işlev çağrılarının doğru sırasına göre kaydetmek için tek bir dosyanın kullanılıp kullanılamayacağından emin değilim.

Her türlü yardıma çok minnettar olurum.

Teşekkürler!


Kullanarak python hata ayıklayıcısına bırakabilir import pdb; pdb.set_trace()ve ardından etkileşimli olarak kodda ilerleyebilirsiniz. Bu, program akışını izlemenize yardımcı olabilir.
Matthew Schinckel

İyi fikir! Teşekkürler Matt. Her seferinde hata ayıklamak zorunda kalmamam için soruda belirtildiği gibi bir günlük almak yine de faydalı olacaktır. Ayrıca, Python için Eclipse for Java kadar iyi (ctrl + tıklama sizi işlev tanımına götürür), hata ayıklamayı kolaylaştırmak için kullanabileceğim bir IDE biliyor musunuz?
user1126425

Yanıtlar:


28

Burada marjinal olarak alakalı birkaç sorunuz var.

En kolay olanla başlayacağım: (3). Kullanılması loggingsizi tek bir günlük dosyasına veya diğer çıkış hedefine tüm çağrıları araya getirebilmesini: onlar süreçte meydana sırayla olacak.

Sıradaki: (2). locals()mevcut kapsamın bir diktesini sağlar. Bu nedenle, başka argümanı olmayan bir yöntemde self, geçerli örneğe bir başvuru içeren kapsamınız vardır. Sizi şaşırtan, kullanılan hile, %operatörün RHS'si olarak bir dikt kullanan dize biçimlendirmesidir . "%(foo)s" % bardeğeri ne olursa olsun değiştirilecektir bar["foo"].

Son olarak, pdbdaha fazla bilgi kaydedebilen bazı içgözlem püf noktalarını kullanabilirsiniz :

def autolog(message):
    "Automatically log the current function details."
    import inspect, logging
    # Get the previous frame in the stack, otherwise it would
    # be this function!!!
    func = inspect.currentframe().f_back.f_code
    # Dump the message + the name of this function to the log.
    logging.debug("%s: %s in %s:%i" % (
        message, 
        func.co_name, 
        func.co_filename, 
        func.co_firstlineno
    ))

Bu, iletilen mesajın yanı sıra (orijinal) işlev adını, tanımın göründüğü dosya adını ve o dosyadaki satırı günlüğe kaydedecektir. İncele'ye bir göz atın - Canlı nesneleri daha fazla ayrıntı için inceleyin .

Daha önce yorumumda bahsettiğim gibi, istediğiniz pdbzaman satırı ekleyerek ve import pdb; pdb.set_trace()programınızı yeniden çalıştırarak etkileşimli bir hata ayıklama istemine girebilirsiniz . Bu, verileri seçtiğiniz gibi inceleyerek kodda ilerlemenizi sağlar.


Teşekkürler Matt! Bu otolog işlevini deneyeceğim. % Operatörünün RHS'si olarak dict kullanımıyla ilgili biraz kafa karışıklığım var: 'ın değerini '%(foo)s : %(bar)s'de yazdırır mıydım bar["foo"]? Yoksa sizin örneğinizden biraz farklı mı?
user1126425

Temel olarak, formdaki her şey, %(<foo>)starafından diktede belirtilen nesnenin değeriyle değiştirilir <foo>. Docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel

3
@ synthesizerpatel'in cevabı çok daha faydalı.
Jan

505

Bunun doğru cevabı, önceden sağlanan funcNamedeğişkeni kullanmaktır.

import logging
logger = logging.getLogger('root')
FORMAT = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s"
logging.basicConfig(format=FORMAT)
logger.setLevel(logging.DEBUG)

Sonra istediğiniz yere ekleyin:

logger.debug('your message') 

Şu anda üzerinde çalıştığım bir komut dosyasından örnek çıktı:

[invRegex.py:150 -          handleRange() ] ['[A-Z]']
[invRegex.py:155 -     handleRepetition() ] [[<__main__.CharacterRangeEmitter object at 0x10ba03050>, '{', '1', '}']]
[invRegex.py:197 -          handleMacro() ] ['\\d']
[invRegex.py:155 -     handleRepetition() ] [[<__main__.CharacterRangeEmitter object at 0x10ba03950>, '{', '1', '}']]
[invRegex.py:210 -       handleSequence() ] [[<__main__.GroupEmitter object at 0x10b9fedd0>, <__main__.GroupEmitter object at 0x10ba03ad0>]]

61
Cevap bu olmalıydı!
user3885927

1
Harika .. Eklenecek bir şey var, günlük dosyasını dinamik olarak kod dosyasıyla aynı olacak şekilde adlandırabilir miyiz? Örneğin: logging.basicConfig (filename = "% (dosyaadı)", format = FORMAT) dosya adını dinamik olarak almayı denedim, ancak statik değer aldı. herhangi bir öneri?
Aykırı

2
@Outlier Hayır, bunu başarmanın önerilen yolu şu yolla:getLogger(__name__)
farthVader

2
Bir sorum var: Java'da bir yerde, kaydedicinin hangi satırdan çağrıldığını anlamak fazladan zaman aldığı için satır numarasını yazdırmanın cesaret kırıldığını okudum. Python'da bu doğru değil mi?
McSonk

2
Alakasız, ama logging.getLogger('root')muhtemelen beklediğiniz şey değil, rootkaydedici değil, 'kök' adında sıradan bir kaydedici.
0xc0de

5

funcname, linenameVe linenogünlük yaptığımız son işlevi hakkında bilgi verir.

Günlükçü paketleyiciniz varsa (örn. Tekli kaydedici), @ synthesizerpatel'in cevabı sizin için işe yaramayabilir.

Çağrı yığınındaki diğer arayanları bulmak için şunları yapabilirsiniz:

import logging
import inspect

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class MyLogger(metaclass=Singleton):
    logger = None

    def __init__(self):
        logging.basicConfig(
            level=logging.INFO,
            format="%(asctime)s - %(threadName)s - %(message)s",
            handlers=[
                logging.StreamHandler()
            ])

        self.logger = logging.getLogger(__name__ + '.logger')

    @staticmethod
    def __get_call_info():
        stack = inspect.stack()

        # stack[1] gives previous function ('info' in our case)
        # stack[2] gives before previous function and so on

        fn = stack[2][1]
        ln = stack[2][2]
        func = stack[2][3]

        return fn, func, ln

    def info(self, message, *args):
        message = "{} - {} at line {}: {}".format(*self.__get_call_info(), message)
        self.logger.info(message, *args)

1
Cevabınız, problemimi çözmek için tam olarak ihtiyacım olan şeydi. Teşekkür ederim.
Hata - Sözdizimsel Pişmanlık

Python 3.8 beri loggingsınıf desteklenirken yığın seviye dışı-box atlama: metotlar gibi log(), debug()şimdi bir kabul vs. stacklevelargüman. Dokümanlara bakın .
amain
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.