Logowanie argumentów funkcji **kwargs gdy leci exception

Logowanie argumentów funkcji **kwargs gdy leci exception
MarekR22
Moderator C/C++
  • Rejestracja:ponad 17 lat
  • Ostatnio:12 minut
0

Głowny problem

Mam jakiś skrypt python uruchamiany przez Jenkins-a, który robi jakieś cuda: Skanuję dużą ilość plików i generuje uproszony raport.
Jak coś pójdzie nie tak i leci wyjątek, to chce by niektóre funkcje logowały, jakie dostały argumenty, żeby było łatwo ustalić co włąściwie poszło nie tak.
W moim przypadku chodzi o to by było wiadomo, który plik xml jest niepoprawny.

Próba rozwiązania

w związku z tym naskrobałem coś takiego:

Kopiuj
def log_argument_on_exception(func):
    """Decorator that logs the argument value if an exception is raised."""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"{func.__name__}({', '.join(map(repr, args))})")
            raise e

    return wrapper

Funkcja z tym dekoratorem jak rzuca wyjątek to wypisuje np:

Kopiuj
parse_xml(WindowsPath('../Rules/Exclusions/Microsoft, Various services - [B1002].xml'))

Problem

Powyższy kod działa bardzo dobrze. W moim przypadku sprawdza się wzorcowo.
Problem polega na tym, że rozwiązanie nie jest dostatecznie uniwersalne, żebym był zadowolony.
Nie wypisuje zawartości **kwargs, wiec jeśli udekorowana funkcja wygląda tak:

Kopiuj
@log_argument_on_exception
def magic(path, *, option=None):
    ...

To informacja option nie zostanie wypisana :(.

Taka próba poprawiania (która się wydawała rozsądna), psuje cały kod (normalne argumenty przestają działać):

Kopiuj
            print(f"{func.__name__}({', '.join(map(repr, args, kwargs))})")

Próbowałem na inne sposoby z gorszym rezultatem.

Pytanie

Czy da się w jakiś elegancki sposób dodać informację o nazwanych argumentach?
Chcę by kod był pythonic, nie chcę dużego boiler plate code (w ten sposób to potrafię to zrobić sam).

Minimalny Python 3.8.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 2x, ostatnio: MarekR22
DR
Wiadomość czy pytałeś o to chatGPT za 3, 2, 1
MarekR22
Moderator C/C++
  • Rejestracja:ponad 17 lat
  • Ostatnio:12 minut
2

@Dregorio

Wiadomość czy pytałeś o to chatGPT za 3, 2, 1

ChatGpt:

Tak, można dodać informacje o nazwanych argumentach do dekoratora. W Python 3.8 wprowadzono nowy argument pozycyjny * w definicji funkcji, który oznacza koniec argumentów pozycyjnych i początek argumentów nazwanych. Można go wykorzystać do rozdzielenia argumentów pozycyjnych od nazwanych i wypisania ich w odpowiedniej formie.

Oto poprawiona wersja dekoratora, która uwzględnia argumenty nazwane:

Kopiuj
def log_argument_on_exception(func):
    """Decorator that logs the argument value if an exception is raised."""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            args_repr = [repr(arg) for arg in args]
            kwargs_repr = [f"{key}={repr(value)}" for key, value in kwargs.items()]
            signature = ", ".join(args_repr + kwargs_repr)
            print(f"{func.__name__}({signature})")
            raise e

    return wrapper

W tej wersji dekoratora, argumenty pozycyjne i nazwane są reprezentowane przez args_repr i kwargs_repr odpowiednio. Następnie, łącząc te dwie listy, tworzony jest string signature, który zawiera pełną informację o argumentach.

Teraz, jeśli udekorowana funkcja wygląda tak:

Kopiuj
@log_argument_on_exception
def magic(path, *, option=None):
    ...

i zostanie rzucany wyjątek, to informacja o path i option będzie wypisana w postaci:

Kopiuj
magic('../Rules/Exclusions/Microsoft, Various services - [B1002].xml', option=None)

Dzięki temu kod jest bardziej uniwersalny i pokrywa oba rodzaje argumentów.

Nie podoba mi się to rozwiązanie, ale działa.
Ciekawy jestem, czy jest rozwiązanie, które jest bardziej Pythonic.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
PA
Co rozumiesz przez stwierdzenie bardziej pythonic? Pytam z ciekawości, sam postanowiłem się pouczyć Pythona i w ksiażce, która mam też pada to określenie, ale nie bardzo widzę powiązania z tym co jest w poście.
DR
  • Rejestracja:około 12 lat
  • Ostatnio:około 7 godzin
  • Postów:1131
0

@MarekR22: Mnie zdziwiło, że to co Ty zaproponowałeś nie zadziałało z tym **kwargs

MarekR22
Moderator C/C++
  • Rejestracja:ponad 17 lat
  • Ostatnio:12 minut
0

Najlepsze co udało mi się samemu napisać:

Kopiuj
import functools
import itertools
 
def repr_kwargs(kwargs):
	return (f"{key}={repr(value)}" for key, value in kwargs.items())
 
def log_argument_on_exception(func):
    """Decorator that logs the argument value if an exception is raised."""
 
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            all_args = itertools.chain(map(repr, args), repr_kwargs(kwargs))
            print(f"{func.__name__}({', '.join(all_args)})")
            raise e
 
    return wrapper

https://ideone.com/lial2T

Róznica z wersją ChatGpt jest niewielka :( jest troszkę lepiej. Przynajmniej repr_kwargs może się przydać w innym miejscu.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
lion137
  • Rejestracja:około 8 lat
  • Ostatnio:około 3 godziny
  • Postów:4935
0

Tak nawiasem pisząc, nie lepiej użyć logging i wysyłać logi do kibany, na przykład?


edytowany 1x, ostatnio: lion137
YA
  • Rejestracja:prawie 10 lat
  • Ostatnio:10 dni
  • Postów:2370
0

Nie wiem czy bardziej pythonicznie, ale z użyciem pprint, które ma różne opcje do formatowania wyniku.

Kopiuj
import pprint

def foobar(*args,**kwargs):
    argz = pprint.pformat(f'{args=} and {kwargs=}')
    print(f'Function called with {argz}')


foobar(3,5,8,path='/a/b/c',blah='3435',option=None)

--
p.s. testowałeś tego wrappera na metodzie klasy?

edytowany 1x, ostatnio: yarel
Spearhead
  • Rejestracja:prawie 6 lat
  • Ostatnio:około 5 godzin
  • Postów:1002
0

Pomyślałbym nad użyciem inspect.getcallargs, które ładnie ci dopasuje argumenty do parametrów pozycyjnych, nazwanych i schowanych w argsach/kwargsach

Kopiuj
import functools
import inspect


def log_argument_on_exception(func):
    """Decorator that logs the argument value if an exception is raised."""

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            print(f"{func.__name__}: {inspect.getcallargs(func, *args, **kwargs)}")

    return wrapper
Kopiuj
>>> @log_argument_on_exception
... def foo(i, x, *args, **kwargs):
...     raise Exception()
... 
>>> foo(3, 5, 8, path='/a/b/c', blah='3435', option=None)
foo: {'i': 3, 'x': 5, 'args': (8,), 'kwargs': {'path': '/a/b/c', 'blah': '3435', 'option': None}}

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.