Рубрики
Без рубрики

Добавление контекстных данных в журнал Python

Недавно я столкнулся с захватывающим вызовом. Мне нужно было создать надежную систему лесозаготовок, интегрированную с … Помечено Python, программирование, регистрацию.

Недавно я столкнулся с захватывающим вызовом. Мне нужно было создать надежную систему лесозаготовок, интегрированную с внешним API. Одним из тех, кто должен был иметь определенные контекстные данные, во всех случаях, когда он был доступен. Основной проблемой было то, что многие из него распространялись по всему нанесению, вложенные в разные функции.

Наиболее тривиальным подходом передачи необходимых объектов через функции не подумывают код без необходимости. Я начал думать о том, как использовать контекстные менеджеры, чтобы собрать данные. После нескольких итераций вместе с командой я придумал элегантное решение, которое дало нам дополнительный пух, который мы могли бы использовать в приложении. Позвольте мне показать вам код и объяснить рассуждение за ним. Готовый? Давайте двигаться дальше!

Установка обработчика контекста регистрации

Во-первых, мы создадим хранилище данных для нашего менеджера контекста. Это должно иметь три возможности:

  • Нам нужно быть в состоянии добавить больше контекстуальных данных для него
  • Это должно вернуть определенное значение атрибута по его названию
  • Он должен быть в состоянии удалить контекстные данные, когда больше не нужны.

Для достижения этого мы создадим стек с словарями в качестве их элементов. Словари будет содержать набор контекстных данных, которые будут использоваться в ведении регистрации. Давайте посмотрим, как это выглядит:

class LoggingContextHandler:
    def __init__(self):
        self.attributes = deque([{}])

    def add(self, **new_context_vars):
        old_context = self.attributes[0]
        new_context = {**old_context, **new_context_vars}
        self.attributes.appendleft(new_context)

    def get(self, key):
        return self.attributes[0].get(key)

    def remove(self):
        self.attributes.popleft()

    def __str__(self):
        return str(self.attributes)

Давайте посмотрим на более глубокий взгляд на Добавить Метод, потому что это где происходит волшебство.

Всякий раз, когда мы добавляем новые данные в контекст, Добавить Клонируют словарь с вершины стека и обновляют его вновь добавленным контентом. Таким образом, нам не нужно будет повторять весь стек, чтобы получить атрибут, скрытый глубоко внутри него.

Я упомянул менеджер контекста пару раз, но где это? Позвольте мне исправить это сразу!

Создание контекстно-менеджера

Там ничего не нравится, просто простая функция. Что бы вы ни использовали в качестве аргумента, будет толкаться в обработчик, определенный выше. После успешного выполнения завернутого кода менеджер отбросит данные из контекста.

logging_context_handler = LoggingContextHandler()

@contextmanager
def logging_context(**kwargs):
    logging_context_handler.add(**kwargs)

    yield

    logging_context_handler.remove()

Регулировка записей журнала с фильтрами

Теперь, когда у нас есть источник данных и способ управлять им, мы можем добавить данные в записи журнала. Рекомендуемый способ – использовать фильтры для этого, поэтому давайте создадим один и прикрепите его к обработчику регистрации.

class ContextFilter(logging.Filter):
    def __init__(self):
        super(ContextFilter, self).__init__()

    def filter(self, record):
        record.store = logging_context_handler.get("store")
        record.client = logging_context_handler.get("client")
        record.item = logging_context_handler.get("item")

        return True


logger = logging.getLogger()
context_filter = ContextFilter()
logger.addFilter(context_filter)

Такой фильтр добавит атрибуты из контекста регистрации в каждой записи журнала. Конечно, мы можем сойти с ума с тем, что мы хотели бы сделать с этим. Но ради простоты давайте построим Fixter, которая отображает наши новые добавленные атрибуты в регистратере нашего выбора.

Создание форматирования

Нам не нужно строить ничего крепкого. Для наших целей простой SetFormatter Функциональный звонок будет достаточно.

format_string = "[%(store)s | %(client)s | %(item)s]: %(message)s"
stdout_formatter = logging.Formatter(format_string)
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(stdout_formatter)
logger.addHandler(stdout_handler)

Давайте посмотрим, как все это работает

С помощью установки сделаны, теперь мы можем проверить, как все это используется. Позвольте мне показать вам простое приложение в качестве примера.

Представьте, что у нас есть продуктовый магазин, и мы хотим отследить, к которому мы продаем товары через регистрацию. У нас есть два клиента, Джим и Тим, каждый со своим списком покупок. Давайте построим код, который позволит нам продать им товары.

from logs import logger
from logs.logging_context import logging_context

clients = {"Jim": ["potatoes", "tomatoes"], "Tim": ["bread", "eggs", "milk"]}


def sell_goods(shopping_list):
    for item in shopping_list:
        with logging_context(item=item):
            logger.info("Sold 1 item.")


with logging_context(store="Hannah's Grocery Store"):
    for client, shopping_list in clients.items():
        with logging_context(client=client):
            sell_goods(shopping_list)

Обратите внимание, что единственный аргумент, переданный Sell_oods Функция – это список покупок, поскольку нет необходимости добавлять что-либо еще.

После того, как вы запустите скрипт, он будет производить такой выход:

[Hannah's Grocery Store | Jim | potatoes]: Sold 1 item.
[Hannah's Grocery Store | Jim | tomatoes]: Sold 1 item.
[Hannah's Grocery Store | Tim | bread]: Sold 1 item.
[Hannah's Grocery Store | Tim | eggs]: Sold 1 item.
[Hannah's Grocery Store | Tim | milk]: Sold 1 item.

Как видите, все данные, хранящиеся в контексте, отображаются в журналах.

Мы можем взять это еще дальше. Скажем, что Sell_oods Функция также проверяет, доступен ли элемент и бросает ошибку, когда там нет. Если мы вошли в систему исключение, мы могли бы увидеть, что следующие клиенты не покупали яйца, потому что магазин выбежал из них.

Остерегайтесь базового регистратора!

Одним из критических предметов, в том, что вы должны создать отдельный регистратор для вашего приложения.

Однажды я ошибочно добавил обработчиков к базовому регистратеру, а затем увидел на меня внешние библиотеки, бросая на меня ошибки. После короткого расследования оказалось, что они все пытались добавить дополнительные атрибуты для записи журнала без доступа к ним. Спаси себя от разочарования, и не делайте этого, пожалуйста.

Я надеюсь, что у вас был хороший прочитанный и у вас есть другое аккуратное решение для добавления к вашему набору инструментов!

Если вы хотите скачать проект, чтобы немедленно проверить свои возможности, вот ссылка на репозиторий: https://github.com/kampikd/logging-context-python.

Не стесняйтесь тянуть код и играть с ним!

Большое спасибо Piotr Kotnis за множество мыслей идей. Без его помощи и понимания это решение не будет так, как оно будет.

Ресурсы

Оригинал: “https://dev.to/kampikd/adding-contextual-data-to-python-logging-3h53”