Обработка ключевого слова «уровень» с широким журналированием structlogPython

Программы на Python
Ответить
Anonymous
 Обработка ключевого слова «уровень» с широким журналированием structlog

Сообщение Anonymous »

Я хочу реализовать широкое ведение журналов с помощью structlog Python. Я также хочу предоставить для этого несколько модульных тестов, и мне очень трудно понять, как использовать structlog с базовым ведением журнала stdlib. В общем, чего я хочу добиться, это чтобы все журналы были красиво отформатированы в простой JSON, без вложенных ключей, например:

Код: Выделить всё

{
"msg": "some event",
"level": "warning",
"timestamp": "2026-03-09 19:01:20",
"user_id": 123,
"frequency": "some key"
// some other key-value pairs
}
Первоначальная конфигурация журнала структуры:

Код: Выделить всё

def configure_events() -> None:
processors = [
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
structlog.stdlib.render_to_log_kwargs,
structlog.stdlib.PositionalArgumentsFormatter(),
structlog.processors.JSONRenderer()
]
structlog.configure(
processors=processors,
wrapper_class=structlog.make_filtering_bound_logger("INFO"),
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
Тестовая конфигурация

Код: Выделить всё

@pytest.fixture
def capture_logs():
emitted = []

def capture(*args):
event_dict = args[-1]
emitted.append(event_dict)
return event_dict

with patch("structlog.processors.JSONRenderer.__call__", capture):
yield emitted
Когда я запускаю такой тест:

Код: Выделить всё

def test_event_emits_structured_wide_event(capture_logs):
configure_events()
logger = structlog.get_logger()

logger.info("some event", user_id=123)

assert len(capture_logs) == 1
log = capture_logs[0]

assert log["msg"] == "some event"
assert log["extra"]["level"] == "info"
assert log["extra"]["user_id"] == 123
тест проходит нормально, но уровень и user_id находятся под дополнительным ключом, что вполне понятно, учитывая, как ведение журнала принимает аргументы.
Но я не хочу, чтобы это было так, я хочу, чтобы все ключи были в виде простого JSON. Я попробовал много разных подходов и в конце концов придумал добавить собственный процессор, который сглаживает значение дополнительного ключа, поэтому после реализации моя конфигурация structlog выглядит следующим образом:

Код: Выделить всё

def configure_events() -> None:

def _flatten_extra(logger, method_name, event_dict):
if "extra" in event_dict:
extra = event_dict.pop("extra")
if not isinstance(extra, dict):
raise ValueError("Value of 'extra' is not of dict type.")
event_dict.update(extra)
return event_dict

processors = [
structlog.contextvars.merge_contextvars,
structlog.processors.add_log_level,
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
structlog.stdlib.render_to_log_kwargs,
structlog.stdlib.PositionalArgumentsFormatter(),
_flatten_extra,
structlog.processors.JSONRenderer()
]
structlog.configure(
processors=processors,
wrapper_class=structlog.make_filtering_bound_logger("INFO"),
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
Затем, после того как я удалю ссылку на дополнительные в своих утверждениях, тест пройдет успешно.
Но это не работает с ПРЕДУПРЕЖДЕНИЕМ.
У меня есть такой тест:

Код: Выделить всё

def test_event_can_use_warning_level(capture_logs):
configure_events()
logger = structlog.get_logger()
logger.warning("check_failed", user_id=222, reason="timeout")
assert capture_logs[0]["level"] == "warning"
Тест не пройден из-за TypeError: Logger._log() получил неожиданный аргумент ключевого слова 'user_id', что для меня уже странно, почему я не могу передать аргументы так же, как для INFO, но ок, вот что я тестировал:

Код: Выделить всё

def test_event_can_use_warning_level(capture_logs):
configure_events()
logger = structlog.get_logger()
extra = {"user_id": 222, "reason": "timeout"}
logger.warning("check_failed", extra=extra)
assert capture_logs[0]["level"] == "warning"
Результат этого теста приводит меня к основной путанице: он завершается неудачно из-за TypeError: Logger._log() получил несколько значений для уровня аргумента. Насколько я понимаю, ведение журнала stdlib добавляет к ключевому слову уровня сообщения журнала, полученному из имени метода, но почему оно ведет себя по-разному между INFO и WARNING???
Я здесь совершенно потерян, и у меня нет идей. Что я упускаю или неправильно настраиваю?

Подробнее здесь: https://stackoverflow.com/questions/799 ... de-logging
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»