Народ, подскажите с правильной организацией логгирования?
Есть функция, в процессе работы которой возникает промежуточный объект - экземпляр класса A
. Этот объект создается, используется для вычисления результата функции и отбрасывается, т.е. на выход не поступает, что-то вроде:
def f(p: int):
a = A()
# ....
return p
Во время отладки необходимо анализировать состояние объекта a
, но этот объект в виде текста не представим, его нужно рендерить в файл.
Решение «в лоб» - это что-то типа:
def f(p: int, path_to_render_a: Optional[str]=None):
a = A()
if path_to_render_a:
render(a, path_to_render_a)
# ....
return p
Ну это (или же коллбэк) усложняет интерфейс функции f
, и, особенно, если функция находится глубже, то требует «проброса» этого параметра через остальные функции.
Если использовать logging
:
def f(p: int):
a = A()
logger = logging.getLogger('my_function')
logger.info(a)
# ....
return p
То, для конечного потребителя, получается, в принципе, неплохо - если хочешь заняться отладкой, пиши хэндлер и рендери в нем, что-то типа:
class MyHandler(logging.Handler):
def emit(self, record):
if isinstance(record.msg, A):
a = record.msg
print('A received:', a, 'rendering!')
Проблема начинается при тестировании - у нас используется pytest, т.е. тест для функции f
выглядит следующим образом:
@pytest.mark.parametrize('p', [1, 2])
def test_my_function(p: int):
assert f(p) == p
И тут становится непонятно - в какой файл рендерить a
, чтобы сохранить привязку к конкретному запуску теста?
Решение «в лоб» - задавать пробрасывать имя теста через хэндлер, типа:
import logging
import pytest
logging.basicConfig(level=logging.DEBUG)
class A:
...
def f(p: int):
a = A()
logger = logging.getLogger('my_function')
logger.info(a)
# ....
return p
class MyHandler(logging.Handler):
def __init__(self):
super().__init__()
self.current_path = None
def emit(self, record):
if isinstance(record.msg, A) and self.current_path:
a = record.msg
print('A received:', a, 'rendering to:', self.current_path)
logger = logging.getLogger('my_function')
handler = MyHandler()
logger.addHandler(handler)
@pytest.mark.parametrize('p', [1, 2])
def test_my_function(request, p: int):
handler.current_path = request.node.name
assert f(p) == p
Не могу точно описать почему, но это решение мне не нравится.
Можно в начале теста очищать список хэндлеров у логгера и создавать каждый раз новый хэндлер, который рендерит в конкретную директорию под текущий тест, но.. может есть какой-то более правильный подход?