Я хотел бы запустить код для 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 я не смог воспроизвести это. Очень запутанно.
В любом случае, вероятно, вопрос:
"Это правильный способ входа в файлы с несколькими копиями одного процесс?"
Хороший вопрос. Я воспроизвел ваши результаты на Win10 с python3.11. Кажется, это связано с внутренностями модуля ведения журнала. Возможно, это поможет, а возможно и нет (он не объясняет, зачем нужен его подход, ИМО). superfastpython.com/multiprocessing-logging-in-python
Что произойдет, если вы получите свой регистратор из multiprocessing.getLogger()? Во всяком случае, некоторое состояние изменяется между созданием экземпляра Process() (в основном процессе) и разветвлением/ускорением (в дочернем процессе). Я не мог понять, почему с быстрым взглядом. Если вы действительно хотите знать, внимательно пройдите процесс разветвления и посмотрите, что происходит с регистратором. Странно, однако.
@ 2e0byo Использование multiprocessing.get_logger имеет такое же поведение. Интересно, что когда я попытался сделать self.handler = ... in __init__, я получил какую-то ошибку `не могу распарить объект '_thread.RLock'. Может ли это быть подсказкой? Ничего из этого не происходит в Linux, по-видимому.
Предположительно это невозможно на всех платформах, но что-то меняется. Похоже, вы должны добавить обработчик в run(), поскольку известно, что он находится внутри дочернего процесса (и, следовательно, изолирован от основного процесса). Но я удивлен, что состояние, установленное в __init__, не выживает. Однако, когда вы пытаетесь передать состояние процессам, происходят всевозможные странные вещи, и, на мой взгляд, нет ничего хакерского в том, чтобы сделать это явно внутри нового контекста внутри run. Может быть, кто-то, кому не нужно глубоко погружаться в многопроцессорность, придет и расскажет нам, что происходит.
Хорошо, я сделаю это на ходу. Впервые вижу разницу в базовом питоне (только stdlib) между платформами. Спасибо за ваши комментарии.
@PeterFranek, возможно, вы видите разницу между реализациями fork() (которой вообще нет в Windows IIRC). Но я на самом деле не знаю, как многопроцессорность настраивает новые процессы, так что это только предположения... В любом случае, это закон дырявых абстракций
Я нашел причину наблюдаемого поведения. Это связано с травлением объектов при их передаче между процессами.
В реализации стандартной библиотеки 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 (поднять), а не столкнуться с неожиданным поведением. Но теперь я вроде понимаю. Спасибо
Я не могу воспроизвести и не понимаю, почему это может произойти. Можете ли вы опубликовать полный код с импортом и как именно вы его запускаете?