Как сделать так, чтобы в логах при падении функции в декораторе отображалось имя оригинальной функции, а не wrapper
Что проверяют
Знаете ли вы, что делает functools.wraps, и понимаете ли, где он помогает, а где нет. Хороший уточняющий вопрос: почему в форматтере логов %(funcName)s всё равно показывает wrapper.
Как отвечать
Базовый ответ: внутреннюю функцию декоратора оборачивают в @functools.wraps(func). Он копирует на обёртку __name__, __qualname__, __module__, __doc__, __annotations__, дополняет __dict__ и кладёт ссылку на оригинал в __wrapped__. После этого inspect.signature и документация показывают исходную функцию.
def log_errors(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception:
logger.exception("Ошибка в %s", func.__qualname__)
raise
return wrapperДальше важная оговорка. wraps не трогает объект кода, поэтому в трейсбеке останется кадр wrapper, а за ним кадр оригинальной функции. Поле funcName в LogRecord берётся из кадра, где вызван логгер, то есть это тоже wrapper. Поэтому имя кладут явно: в текст сообщения, как выше, или в extra={"func_name": func.__qualname__} с выводом через форматтер.
logger.exception: пишет с уровнемERRORи прикладывает трейсбек, вызывать его стоит внутриexcept;raiseбез аргумента: пробрасывает исключение дальше, не проглатывая его;- async-функции: для корутин нужна своя обёртка
async def wrapperсawait func(...), иначе декоратор вернёт корутину, так и не поймав ошибку; - класс-декоратор: там вместо
wrapsвызываютfunctools.update_wrapper(self, func).
Где спотыкаются
Забывают wraps, и в логах, метриках и документации везде оказывается wrapper.
Уверены, что wraps меняет трейсбек и %(funcName)s. Не меняет, и на собеседовании это легко проверяют вопросом «а что покажет форматтер?».
Логируют исключение и не пробрасывают его. Вызывающий код получает None вместо ошибки, и сбой тихо уходит дальше по программе.