Rejestrowanie w Pythonie (nazwa funkcji, nazwa pliku, numer linii) przy użyciu jednego pliku


109

Próbuję się dowiedzieć, jak działa aplikacja. W tym celu wstawiam polecenia debugowania jako pierwszy wiersz treści każdej funkcji w celu zarejestrowania nazwy funkcji, a także numeru wiersza (w kodzie), w którym wysyłam wiadomość do wyjścia dziennika. Wreszcie, ponieważ ta aplikacja składa się z wielu plików, chcę utworzyć pojedynczy plik dziennika, aby lepiej zrozumieć przepływ sterowania w aplikacji.

Oto co wiem:

  1. aby uzyskać nazwę funkcji, mogę użyć, function_name.__name__ale nie chcę używać funkcji nazwa_funkcji (aby móc szybko skopiować i wkleić rodzaj ogólny Log.info("Message")w treści wszystkich funkcji). Wiem, że można to zrobić w C przy użyciu __func__makra, ale nie jestem pewien co do Pythona.

  2. aby uzyskać nazwę pliku i numer linii, widziałem, że (i uważam, że) moja aplikacja używa locals()funkcji Pythona, ale w składni, której nie jestem do końca świadomy, np.: options = "LOG.debug('%(flag)s : %(flag_get)s' % locals())i próbowałem użyć LOG.info("My message %s" % locals())czegoś podobnego, który daje coś podobnego {'self': <__main__.Class_name object at 0x22f8cd0>}. Jakieś uwagi w tej sprawie?

  3. Wiem, jak używać rejestrowania i dodawać do niego program obsługi, aby logować się do pliku, ale nie jestem pewien, czy pojedynczy plik może być użyty do zapisania wszystkich komunikatów dziennika we właściwej kolejności wywołań funkcji w projekcie.

Byłbym bardzo wdzięczny za każdą pomoc.

Dzięki!


Możesz import pdb; pdb.set_trace()przejść do debugera języka Python za pomocą , a następnie interaktywnie przechodzić przez kod. Może to pomóc w śledzeniu przepływu programu.
Matthew Schinckel

Świetny pomysł! Dzięki Matt. Nadal pomocne byłoby uzyskanie dziennika, o którym mowa w pytaniu, aby nie musieć debugować za każdym razem. Czy znasz też IDE dla Pythona, które jest równie dobre jak Eclipse for Java (ctrl + klik przenosi cię do definicji funkcji), z którego mogę skorzystać, aby ułatwić debugowanie?
user1126425

Odpowiedzi:


28

Masz tutaj kilka marginalnie powiązanych pytań.

Zacznę od najłatwiejszego: (3). Używając loggingyou możesz zagregować wszystkie wywołania do pojedynczego pliku dziennika lub innego celu wyjściowego: będą one w kolejności, w jakiej wystąpiły w procesie.

Dalej: (2). locals()zawiera dyktando aktualnego zakresu. Tak więc w metodzie, która nie ma innych argumentów, masz selfzakres, który zawiera odwołanie do bieżącego wystąpienia. Sztuczka używana, która cię przytłacza, polega na formatowaniu łańcucha przy użyciu dyktowania jako prawej strony %operatora. "%(foo)s" % barzostanie zastąpiony jakąkolwiek wartością bar["foo"].

Na koniec możesz użyć kilku sztuczek introspekcji, podobnych do tych używanych przez pdbto, aby zarejestrować więcej informacji:

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

Spowoduje to zarejestrowanie przekazanego komunikatu plus (oryginalna) nazwa funkcji, nazwa pliku, w którym pojawia się definicja oraz wiersz w tym pliku. Spójrz na Inspect - Zbadaj obiekty na żywo, aby uzyskać więcej szczegółów.

Jak wspomniałem wcześniej w moim komentarzu, możesz również pdbw dowolnym momencie przejść do interaktywnego monitu o debugowanie, wstawiając wiersz import pdb; pdb.set_trace()i ponownie uruchamiając program. Umożliwia to przechodzenie przez kod, sprawdzanie danych według własnego uznania.


Dzięki Matt! Spróbuję tej funkcji autologu. Mam trochę zamieszania w związku z używaniem dyktu jako prawej strony operatora%: Czy '%(foo)s : %(bar)s'wypisałby również wartość bar["foo"]'s? A może trochę różni się od twojego przykładu?
user1126425

Zasadniczo wszystko w formularzu %(<foo>)sjest zastępowane przez wartość obiektu, do którego odwołuje się dykt <foo>. Więcej przykładów / szczegółów można znaleźć na stronie docs.python.org/library/stdtypes.html#string-formatting
Matthew Schinckel

3
Odpowiedź @synthesizerpatel jest znacznie bardziej pomocna.
Jan

505

Prawidłową odpowiedzią na to jest użycie już podanej funcNamezmiennej

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

Następnie, gdziekolwiek chcesz, po prostu dodaj:

logger.debug('your message') 

Przykładowe dane wyjściowe ze skryptu, nad którym teraz pracuję:

[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
To powinna być odpowiedź!
user3885927

1
Świetnie ... Jeszcze jedna rzecz, czy możemy nazwać plik dziennika tak, aby był taki sam jak plik kodowy dynamicznie? np .: próbowałem logować.basicConfig (nazwa_pliku = "% (nazwa_pliku)", format = FORMAT), aby dynamicznie pobierać nazwę pliku, ale przyjmowała wartość statyczną. jakieś sugestie?
Outlier

2
@Outlier Nie, zalecanym sposobem na osiągnięcie tego jestgetLogger(__name__)
farthVader

2
Mam jedno pytanie: gdzieś w Javie przeczytałem, że drukowanie numeru linii jest odradzane, ponieważ ustalenie, z której linii jest wywoływany rejestrator, zajmuje więcej czasu. W Pythonie to nieprawda?
McSonk

2
Nieistotne, ale logging.getLogger('root')prawdopodobnie nie jest tym, czego się spodziewasz, to nie jest rootrejestrator, ale zwykły rejestrator o nazwie „root”.
0xc0de

5

funcname, linenameI linenodostarczają informacji o ostatniej funkcji, która zrobiła to rejestrowanie.

Jeśli masz opakowanie loggera (np. Singleton logger), odpowiedź @ synthesizerpatel może nie działać dla Ciebie.

Aby znaleźć innych dzwoniących w stosie połączeń, możesz:

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
Twoja odpowiedź była dokładnie tym, czego potrzebowałem, aby rozwiązać mój problem. Dziękuję Ci.
Błąd - Syntactical Remorse

Od Pythona 3.8, az loggingpoziomu klasy obsługuje stos omijając out-of-the-box: Metody podoba log(), debug()itd teraz przyjąć stacklevelargumentu. Zobacz dokumentację .
amain
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.