Многопроцессорность Python, ведение журнала в разные файлы

Я хотел бы запустить код для n процессов и иметь журналы каждого процесса в отдельном файле.

Я пытался, наивно, вот так

from multiprocessing import Process
import logging


class Worker(Process):
    def __init__(self, logger_name, log_file):
        super().__init__()
        self.logger = logging.getLogger(logger_name)
        self.log_file = log_file
        self.logger.addHandler(logging.FileHandler(log_file))
        print("from init", self.logger, self.logger.handlers)

    def run(self) -> None:
        print("from run", self.logger, self.logger.handlers)


if __name__ == '__main__':
    p1 = Worker("l1", "log1")
    p1.start()

(пробовал на питоне 3.9 и 3.11) но по какой-то причине обработчик ушел. Это результат:

from init <Logger l1 (WARNING)> [<FileHandler log1 (NOTSET)>]
from run <Logger l1 (WARNING)> []

Почему FileHandler ушел? Должен ли я использовать AddHandler в методе run - это правильный способ?

Я пытался использовать этот ответ, но не смог заставить его работать.

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

ОБНОВЛЕНИЕ: это происходит при установке Python на MacBook. На сервере Linux я не смог воспроизвести это. Очень запутанно.

В любом случае, вероятно, вопрос:

"Это правильный способ входа в файлы с несколькими копиями одного процесс?"

Я не могу воспроизвести и не понимаю, почему это может произойти. Можете ли вы опубликовать полный код с импортом и как именно вы его запускаете?

2e0byo 18.02.2023 22:37

Хороший вопрос. Я воспроизвел ваши результаты на Win10 с python3.11. Кажется, это связано с внутренностями модуля ведения журнала. Возможно, это поможет, а возможно и нет (он не объясняет, зачем нужен его подход, ИМО). superfastpython.com/multiprocessing-logging-in-python

Paul Cornelius 18.02.2023 23:07

Что произойдет, если вы получите свой регистратор из multiprocessing.getLogger()? Во всяком случае, некоторое состояние изменяется между созданием экземпляра Process() (в основном процессе) и разветвлением/ускорением (в дочернем процессе). Я не мог понять, почему с быстрым взглядом. Если вы действительно хотите знать, внимательно пройдите процесс разветвления и посмотрите, что происходит с регистратором. Странно, однако.

2e0byo 18.02.2023 23:58

@ 2e0byo Использование multiprocessing.get_logger имеет такое же поведение. Интересно, что когда я попытался сделать self.handler = ... in __init__, я получил какую-то ошибку `не могу распарить объект '_thread.RLock'. Может ли это быть подсказкой? Ничего из этого не происходит в Linux, по-видимому.

Peter Franek 19.02.2023 00:10

Предположительно это невозможно на всех платформах, но что-то меняется. Похоже, вы должны добавить обработчик в run(), поскольку известно, что он находится внутри дочернего процесса (и, следовательно, изолирован от основного процесса). Но я удивлен, что состояние, установленное в __init__, не выживает. Однако, когда вы пытаетесь передать состояние процессам, происходят всевозможные странные вещи, и, на мой взгляд, нет ничего хакерского в том, чтобы сделать это явно внутри нового контекста внутри run. Может быть, кто-то, кому не нужно глубоко погружаться в многопроцессорность, придет и расскажет нам, что происходит.

2e0byo 19.02.2023 00:14

Хорошо, я сделаю это на ходу. Впервые вижу разницу в базовом питоне (только stdlib) между платформами. Спасибо за ваши комментарии.

Peter Franek 19.02.2023 00:15

@PeterFranek, возможно, вы видите разницу между реализациями fork() (которой вообще нет в Windows IIRC). Но я на самом деле не знаю, как многопроцессорность настраивает новые процессы, так что это только предположения... В любом случае, это закон дырявых абстракций

2e0byo 19.02.2023 01:26
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Калькулятор CGPA 12 для семестра
Калькулятор CGPA 12 для семестра
Чтобы запустить этот код и рассчитать CGPA, необходимо сохранить код как HTML-файл, а затем открыть его в веб-браузере. Для этого выполните следующие...
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
О тренинге HTML JavaScript :HTML (язык гипертекстовой разметки) и CSS (каскадные таблицы стилей) - две основные технологии для создания веб-страниц....
Как собрать/развернуть часть вашего приложения Angular
Как собрать/развернуть часть вашего приложения Angular
Вам когда-нибудь требовалось собрать/развернуть только часть вашего приложения Angular или, возможно, скрыть некоторые маршруты в определенных средах?
Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы
Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем...
Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах
При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после...
2
7
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Я нашел причину наблюдаемого поведения. Это связано с травлением объектов при их передаче между процессами.

В реализации стандартной библиотеки Logger определен метод __reduce__. Этот метод используется в тех случаях, когда объект не может быть надежно протравлен. Вместо того, чтобы пытаться замариновать сам объект, протокол pickle вместо этого использует возвращаемое значение из __reduce__. В случае Logger __reduce__ возвращает имя функции (getLogger) и строку (имя собираемого Logger), которые будут использоваться в качестве аргумента. В процедуре распаковки протокол распаковки выполняет вызов функции (logging.getLogger(name)); результатом этого вызова функции становится незакрепленный экземпляр Logger.

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

В Python объекты Process не имеют общего адресного пространства (по крайней мере, не в Windows). Когда запускается новый процесс, его переменные экземпляра должны быть каким-то образом «перенесены» из одного процесса в другой. Это делается путем травления / рассолки. В примере кода переменные экземпляра, объявленные в функции Worker.__init__, действительно появляются в новом процессе, в чем вы можете убедиться, распечатав их в Worker.run. Но под капотом Python фактически замариновал и распаковал все переменные экземпляра, чтобы выглядело так, как будто они волшебным образом мигрировали в новый процесс. В подавляющем большинстве случаев это работает просто отлично. Но не обязательно, если одна из этих переменных экземпляра определяет метод __reduce__.

Я подозреваю, что logging.FileHandler нельзя замариновать, поскольку он использует ресурсы операционной системы (файл). Это, вероятно, причина (или, по крайней мере, одна из причин), почему Logger объекты не могут быть промаринованы.

Я лично предпочел бы полностью запретить определение таких объектов в Proces init (поднять), а не столкнуться с неожиданным поведением. Но теперь я вроде понимаю. Спасибо

Peter Franek 19.02.2023 09:00

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