diff --git a/src/elexam_core/logging.py b/src/elexam_core/logging.py index bb86e1d..e670b51 100644 --- a/src/elexam_core/logging.py +++ b/src/elexam_core/logging.py @@ -1,55 +1,34 @@ -from typing import MutableMapping, Any import structlog from structlog.typing import EventDict from elexam_core.context import trace_id_context -def add_trace_id(event_dict: EventDict): +def add_trace_id(logger, method_name, event_dict: EventDict) -> EventDict: """ Structlog-процессор: добавляет поле ``trace_id`` в каждую лог-запись. - ``trace_id`` читается из ``trace_id_context`` — это ``contextvars.ContextVar``, - которая устанавливается middleware при входе каждого HTTP-запроса. Таким образом - все лог-строки одного запроса объединяются одним сквозным идентификатором — - это называется distributed tracing / сквозная трассировка. + Сигнатура обязана быть ``(logger, method_name, event_dict)`` — structlog + вызывает каждый процессор именно с этими тремя позиционными аргументами. + Первые два не используем, но принять их обязаны. ``trace_id`` читается из + ``trace_id_context`` (``contextvars.ContextVar``, ставится middleware на входе + запроса), что связывает все строки лога одного запроса единым идентификатором. - .. note:: - Стандартная сигнатура structlog-процессора — три аргумента: - ``(logger, method_name, event_dict)``. Здесь сигнатура упрощена до одного - параметра: structlog передаёт ``event_dict`` напрямую, потому что первые два - аргумента нигде в теле функции не нужны. - - :param event_dict: ``EventDict`` (structlog-тип из ``structlog.typing``, эквивалентный ``MutableMapping[str, Any]``): - словарь с накопленными к данному моменту данными лог-записи (поля, добавленные - предыдущими процессорами и вызывающим кодом). Процессор мутирует его, - добавляя ключ ``"trace_id"``, и обязан вернуть его дальше по цепочке. - :return: Тот же объект ``event_dict`` с добавленным полем ``"trace_id"``. - Возврат обязателен — structlog передаёт его следующему процессору в цепочке. + :return: тот же ``event_dict`` с добавленным ``trace_id`` — возврат обязателен, + structlog передаёт результат следующему процессору в цепочке. """ event_dict["trace_id"] = trace_id_context.get() return event_dict -# Глобальная конфигурация structlog. -# `processors` — это цепочка (pipeline): каждая лог-запись проходит через все -# процессоры строго по порядку. Каждый процессор получает `event_dict` от предыдущего, -# дополняет или преобразует его и передаёт дальше. Последний процессор должен -# вернуть финальную строку или байты — это и есть то, что уйдёт в stdout/файл. structlog.configure( processors=[ - # 1. Добавляет поле "level" (например, "info", "error") в event_dict. structlog.processors.add_log_level, - # 2. Добавляет поле "timestamp" в формате ISO 8601, время в UTC. structlog.processors.TimeStamper(fmt="iso", utc=True), - # 3. Наш процессор: добавляет поле "trace_id" для сквозной трассировки запроса. add_trace_id, - # 4. Финальный рендер: сериализует весь event_dict в JSON-строку. - # Стоит последним — к этому моменту все поля уже добавлены. + structlog.processors.format_exc_info, structlog.processors.JSONRenderer(), ] ) -# Готовый логгер для импорта в любом сервисе проекта. -# Использование: `from elexam_core.logging import log`, затем `log.info("msg", key=val)`. log = structlog.get_logger() \ No newline at end of file