Как поделиться настроенным регистратором между запущенными процессами?

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

Очевидно, я ожидал, что все 5 сообщений будут зарегистрированы с помощью экземпляра logger, созданного в gui.py.

Я пробовал объединять методы, перемещать методы из класса в функции уровня модуля, создавать Queue/loggers в разных местах и ​​передавать первый экземпляр logger в качестве аргумента. Все, что я пробовал до этого момента, либо приводило к тем же результатам, либо выдавало ошибку pickling и в конечном итоге заканчивалось EOFError. Приведенный код представляет собой самую последнюю версию, которая не вызывает исключений.

Я просто пытаюсь понять, «где» я лажаю. Если это имеет значение, это в Windows 10 с использованием Python 3.12.

# gui.py

from multiprocessing import Queue
import tkinter as tk
from tkinter import ttk

import builder
import logger

class Gui(tk.Tk):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        btn = ttk.Button(self, text='Build', command=lambda: builder.Build(q))
        btn.pack()
        log.configure(q)
        self.mainloop()

if __name__ == '__main__':
    q = Queue()
    log = logger.Logger()
    Gui()
# logger.py

import logging
import logging.handlers

class Logger:
    def __init__(self):
        self.logger = logging.getLogger('dcre')
        self.logger.setLevel('DEBUG')
        self.log = self.logger.log

    def configure(self, q):
        self.q = q
        self.qh = logging.handlers.QueueHandler(self.q)
        self.file = logging.FileHandler('log.log')
        self.file.setLevel('DEBUG')
        self.logger.addHandler(self.qh)
        self.logger.addHandler(self.file)
        logging.basicConfig(level='INFO')
# builder.py

import logging
from multiprocessing import Process
import threading

class Build:
    def __init__(self, q):
        self.queue = q
        self.logger = logging.getLogger('dcre')

        workers = []
        for i in range(5):
            wp = Process(target=self.foo, args=(i,))
            workers.append(wp)
            wp.start()

            lp = threading.Thread(target=self.logger_thread)
            lp.start()
            for wp in workers:
                wp.join()

            self.queue.put(None)
            lp.join()

    def logger_thread(self):
        while True:
            record = self.queue.get()
            if record is None:
                break
            self.logger.handle(record)

    def foo(self, i):
        msgs = (
            (10, "This is a DEBUG message. You shouldn't see this."),
            (20, 'This is an INFO message. Just so you know.'),
            (30, 'This is a WARNING message. Be careful, yo.'),
            (40, 'This is an ERROR message. Man, you done messed up.'),
            (50, 'This is a CRITICAL message. Game over!')
        )
        self.logger.log(*msgs[i])

Примечание. Метод configure для средства ведения журнала существовал только для того, чтобы отложить настройку до тех пор, пока не будет создан графический интерфейс, чтобы он имел доступ к виджету «Текст» для записи в пользовательский обработчик.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Совместное использование объекта журнала между процессами может быстро стать странным.

Я бы рекомендовал использовать класс QueueHandler, поскольку вы начали это делать, а затем использовать ведение журнала верхнего уровня. Затем установите все параметры конфигурации в главном регистраторе очереди. Это работает в Python 3.11.9

Например

import multiprocessing
import logging
import logging.config
from logging.handlers import QueueListener
from logging import StreamHandler
import atexit

STD_FORMAT_STR = r"[%(levelname)s @ %(name)s|%(filename)s|L%(lineno)d|%(asctime)s|%(process)d]: %(message)s"
STD_DATE_FORMAT_STR = "%Y-%m-%dT%H:%M:%S%z"

def create_share_logging_queue():
  ### CREATE THE SHARED LOGGING QUEUE
  loggingQueue = multiprocessing.Queue()

  ## CREATE HANDLERS ###
  standardFormat = logging.Formatter(fmt=STD_FORMAT_STR,
                                       datefmt=STD_DATE_FORMAT_STR)
  _hndlr = StreamHandler()
  _hndlr.setLevel("DEBUG")
  _hndlr.setFormatter(standardFormat)

  ### CREATE AND START QUEUE LISTENER ###
  queueListener = QueueListener(loggingQueue,_hndlr,respect_handler_level=True)
  queueListener.start()
  atexit.register(queueListener.stop) # ensures that queue resources are cleared when dying
  return loggingQueue

def setup_mp_device_queued_logging(loggingQueue):
    """
        1. Take in the shared mp.Queue that all devices write to
        2. Create a QueueHandler that log records will push to
    Args:
        loggingQueue (_type_): _description_
    """
    loggingDictConfig  = {
        "version": 1,
        "disable_existing_loggers": True,
        
        "formatters" : {
        },
        "handlers": {
            "myQueueHandler" : {
                "class" : "logging.handlers.QueueHandler",
                "queue" : loggingQueue
            }
        },
        "root" : {
                "level": "DEBUG",
                "handlers" : [
                       "myQueueHandler"
                ]
            },
    }
    ### CREATE THE QUEUEHANDLER + CONFIGURE LOGGING ###
    logging.config.dictConfig(loggingDictConfig)

def someFoo(myQ : multiprocessing.Queue, nm : str):
   setup_mp_device_queued_logging(myQ)
   logger = logging.getLogger(f"{nm}")
   logger.debug(f"Hello from {nm}")
   pass

if __name__ == "__main__":
    myQ = create_share_logging_queue()
    setup_mp_device_queued_logging(myQ)
    logger = logging.getLogger("MAIN")

    p1 = multiprocessing.Process(target=someFoo, args=(myQ,"CoolProc1"))
    p2 = multiprocessing.Process(target=someFoo, args=(myQ,"CoolProc2"))
    p1.start()
    p2.start()
    logger.debug("Hello From Main")
    p1.join()
    p2.join()

ВНЕ:

[DEBUG @ MAIN|test.py|L72|2024-09-05T16:46:42-0500|26284]: Hello From Main
[DEBUG @ CoolProc1|test.py|L60|2024-09-05T16:46:42-0500|11940]: Hello from CoolProc1
[DEBUG @ CoolProc2|test.py|L60|2024-09-05T16:46:42-0500|19016]: Hello from CoolProc2

ПРИМЕЧАНИЯ:

  • Импорт библиотеки журналирования между версиями очень странный. Имейте это в виду.
  • Если вы используете самодельные библиотеки (например, my_tools.py), может быть очень полезно установить регистратор в качестве глобальной переменной и разрешить ему наследовать настроенный регистратор от того, кто вызывает инструмент. Для этого вам необходимо установить для параметра «отключить регистраторы» значение False и вручную удалить те, которые вам нужны. Я нашел этот фрагмент полезным:
VALID_LOGGERS = ['MAIN','CoolProc1','CoolProc2']
disabled_logger_cnt = 0
    for log_name in logging.Logger.manager.loggerDict:
        if not log_name in VALID_LOGGERS:
            log_obj = logging.getLogger(log_name)
            log_obj.setLevel(logging.WARNING)
            disabled_logger_cnt += 1
  • Поскольку это похоже на приложение tkinter, будьте осторожны с тем, как вы управляете своими mp-очередями. Неправильное закрытие процессов может привести к зависаниям, вызывающим разочарование, поскольку они зависят от общего ресурса очереди журналирования.

  • Ознакомьтесь с руководством по ведению журнала mCoding. оно очень информативно и описывает передовой опыт: https://thewikihow.com/video_9L77QExPmI0?si=4ggPehbVIzJoegip

=== TLDR ===

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

Мне потребовалось всего 5 минут, чтобы заставить этот шаблон работать с моим примером кода, включая сохранение регистратора в отдельном потоке. По какой-то причине мой мозг застрял на идее поделиться одним регистратором. Я никогда не думал попробовать включить регистратор в каждый процесс. Самая забавная часть этого эпизода для меня заключается в том, что код, которым вы поделились, удивительно похож на мою первоначальную попытку (с использованием QueueListener), прежде чем я позволил приведенному мной примеру кода направить меня по другому пути. Спасибо за это.

Sam 06.09.2024 16:21

Другие вопросы по теме