Pull to refresh

Comments 21

<зануда_мод>

"SettingsStore" - магазин настроек? или все же планировалось хранилище SettingsStorage?

</зануда_мод>

Я думал об этом. Мне вариант со "Storage" не очень подходит, поскольку навевает ассоциации с персистентностью хранилища, а оно де-факто существует только в оперативной памяти. Между тем, одно из значений "Store" - это как раз "хранилище".

Хмм. Как proof-of-concept идея асинхронности логирования подходит, но добавляет довольно существенные минусы и я бы сторонился такого решения на проде.
От логов обычно ждут:
1) точность во времени. Вот поставил я запись в лог между строками программы — вот ровно в этот момент и должно записаться. Асинхронность же тут отдаляет момент записи от реального исполнения. Причем никакой определенности «насколько отдаляет» — нет. Что делать с логом, в котором записи будут отставать по времени на 20 минут? А если у меня 2 лога, в одном отстают, а в другом нет — как их сопоставлять?
2) последовательность. Логи — это часто отладочная информация, как вызываются функции друг за другом. А в документации «при работе асинхронного движка не гарантируется правильный порядок записи логов».

Автор, а для чего вы используете эту штуку?

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

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

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

В целом асинхронный движок для логов - это компромисс между пиковой производительностью сервиса и упомянутыми вами свойствами логов. Чем больше мы теряем в первом, тем больше приобретаем во втором, и наоборот. Здесь уже нужно выбирать по потребности, и фреймворк дает такую возможность.

Я пока фреймворк практически не использую, только пишу. Его текущая версия начинается с нуля, то есть на мой взгляд, он пока не готов для промышленного применения (хотя его уже можно использовать в каких-нибудь хобби-проектах).

мысли в слух ...

а, не легче ли было взять готовое, старое, проверенное решение на C и завернуть его в cpython?

Можно сделать так - при каждом изменении создается объект настроек, содержащий их полный комплект. Он целиком валидируется и кладется в очередь, потоконезависимую, конечно. Отдельный тред читает эту очередь и применяет настройки - по сути замещает текущий объект настроек новым из очереди.

Так не проще?

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

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

Впрочем, не настаиваю. Ваш код, ваша статья, ваше родео.

В принципе подход с "не изменять, а копировать" в многопоточке валидный и часто решает много проблем, но для меня не очевидно, как именно он применим в данном случае.

Привет. Интересно было посмотреть, как работают новички с параллельным выполнением кода, спасибо.

Немного поправлю:

class ThreadPool:
  def wait_empty_queue(self):
    delay = self.settings['time_quant'] * self.settings['delay_on_exit_loop_iteration_in_quants']
    while not self.queue.empty():
      time.sleep(delay)

где Delay, вероятно, стоит вычислять отдельной функцией.

Кстати, большинство циклов в коде можно сделать генераторами.

ну и метод run явно может быть проще:

while not self.stopped:
    try:
        log = self.queue.get(timeout=self.settings['time_quant'])
        self.do_anything(log)
        self.queue.task_done()
    except Empty:
        pass
    except Exception as e:
        self.queue.task_done()

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

Отличие, в моем варианте в том, что если на сбое пришел стоп - мой вариант закончит раньше работу.

В твоем варианте будет считывать дальше из queue и делать do_anything пока не выпадет except Empty. Подумай, действительно ли ты уверен в двух циклах. Выглядит, неостанавливаемо, если self.stopped но не выпадает Empty

Привет. Мне нравятся эти улучшения. Первое точно затащу к себе, насчет второго есть нюанс.

Я не стал включать это статью, т.к. счел мелкой подробностью, которыми ее не стоит загромождать, но в доке к Queue.empty() есть приписка:

Return True if the queue is empty, False otherwise. If empty() returns True it doesn’t guarantee that a subsequent call to put() will not block. Similarly, if empty() returns False it doesn’t guarantee that a subsequent call to get() will not block.

То есть метод empty() на самом деле не точный и дает примерный результат. Т.к. моя цель - не потерять ни одного лога, я не могу закончить работу и выйти из цикла, пока не буду точно убежден, что очередь пуста. Да, на это уйдет еще один time_quant, но это цена гарантии.

ну тогда так. Но шансов прервать процедуру принудительно не останется.

work=True
while work:
    try:
        log = self.queue.get(timeout=self.settings['time_quant'])
        self.do_anything(log)
        self.queue.task_done()
    except Empty:
        work = not self.stopped
    except Exception as e:
        self.queue.task_done()

это аналог твоего кода.

Да, это очень похоже на правду. Пораскуриваю еще, и если не найду проблем - тоже утащу себе.

Хотелось бы в статье видеть некий сравнительный анализ:

  1. Чем не устраивает валидация в pydantic?

  2. Чем не устраивают неблокирущие примеры logging из документации python? (там есть варианты и с мультипроцессингом, и с тредами)

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

  2. Между моим фреймворком и logging слишком много отличий, чтобы здесь их все перечислить. Конкретно в вопросе неблокирующего движка главных отличий 2: у меня можно изменить количество воркеров в рантайме в любой момент, не потеряв ни одного лога, или даже безболезненно сменить движок; у меня есть защита при завершении программы от потери логов, оставшихся в очереди.

На эту же тему, есть вроде прекрасная либа loguru. Весьма популярная, минималистичная в использовании и по заверениям авторов Asynchronous, Thread-safe, Multiprocess-safe.


Просто если задаться задачей "не тащить", а самому писать даже логирование, то тяжело будет далеко уехать.

Я в курсе про нее. Она мне не нравится по ряду причин, начиная с того, как написан и декомпозирован код, как там все прибито гвоздями в архитектурном плане.

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

Когда-нибудь я напишу статью про свой фреймворк и чем он лучше остальных, но сегодня это не совсем по теме.

loguru - прекрасная? От неё больше проблем, чем пользы... Начните хотя бы с тестирования производительности

Сноска от авторов про "10x faster" давно зачёркнута с припиской "In an upcoming release, Loguru's critical functions will be implemented in C for maximum speed.", но этот upcoming release уже давно не может произойти )

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

Т.е. прочитав статью, я остался при мнении, что в случае необходимости валидации я возьму pydantic, а в случае необходимости неблокирующего logging возьму пример из документации. А хотелось бы зайти в статью, найти что-то полезное и унести с собой ))

Прочитал тезис про "красиво" - спорить не буду. Но хочется посмотреть на тесты производительности - это была одна из причин отказа от loguru и возврат к обычному logging

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

Sign up to leave a comment.