ฉันจะเข้าสู่ระบบในขณะที่ใช้มัลติโปรเซสเซอร์ใน Python ได้อย่างไร


239

ตอนนี้ผมมีโมดูลกลางในกรอบที่ spawns กระบวนการหลายโดยใช้ Python 2.6 โมดูลmultiprocessing เพราะมันใช้multiprocessingมีระดับโมดูลบันทึก multiprocessing LOG = multiprocessing.get_logger()ทราบ ต่อเอกสารตัวบันทึกนี้มีการล็อกที่ใช้ร่วมกันในกระบวนการเพื่อให้คุณไม่สับสนกับสิ่งต่างsys.stderr

ปัญหาที่ฉันมีตอนนี้คือโมดูลอื่น ๆ ในกรอบไม่ได้ประมวลผลแบบหลายตัวรู้ วิธีที่ฉันเห็นฉันต้องทำการอ้างอิงทั้งหมดในโมดูลกลางนี้โดยใช้การบันทึกแบบมัลติโพรเซสซิง นั่นเป็นสิ่งที่น่ารำคาญภายในเฟรมเวิร์กนับประสาสำหรับลูกค้าทั้งหมดของเฟรมเวิร์ก มีทางเลือกอื่นที่ฉันไม่คิดหรือไม่


10
เอกสารที่คุณเชื่อมโยงระบุตรงข้ามกับสิ่งที่คุณพูดคนตัดไม้ไม่มีกระบวนการล็อคที่ใช้ร่วมกันและสิ่งต่าง ๆ เข้าด้วยกัน - ปัญหาที่ฉันมีเช่นกัน
Sebastian Blask

3
ดูตัวอย่างในเอกสาร STDLIB นี้: การบันทึกเป็นไฟล์เดียวจากหลายกระบวนการ สูตรอาหารไม่จำเป็นต้องมีโมดูลอื่น ๆ
jfs

ดังนั้นกรณีใช้สำหรับทำmultiprocessing.get_logger()อะไร ดูเหมือนว่าจะขึ้นอยู่กับวิธีอื่น ๆ ในการบันทึกคือฟังก์ชั่นการบันทึกmultiprocessingมีค่าน้อย
Tim Ludwinski

4
get_logger()เป็นคนตัดไม้ที่ใช้โดยmultiprocessingโมดูลเอง มันจะมีประโยชน์ถ้าคุณต้องการแก้multiprocessingปัญหา
jfs

คำตอบ:


69

วิธีเดียวที่จะจัดการกับสิ่งที่ไม่ใช่การรุกล้ำคือ:

  1. วางกระบวนการแต่ละขั้นตอนของผู้ปฏิบัติงานเพื่อให้บันทึกไปยังตัวบอกรายละเอียดไฟล์อื่น (ไปยังดิสก์หรือไปป์) ตามหลักการแล้วรายการบันทึกทั้งหมดควรถูกประทับเวลา
  2. กระบวนการควบคุมของคุณสามารถทำได้ อย่างใดอย่างหนึ่งต่อไปนี้:
    • หากใช้ไฟล์ดิสก์:รวมไฟล์บันทึกในตอนท้ายของการรันให้เรียงลำดับตามเวลาประทับ
    • หากใช้ไพพ์(แนะนำ):รวมรายการบันทึกแบบ on-the-fly จากไพพ์ทั้งหมดให้เป็นไฟล์บันทึกส่วนกลาง (ตัวอย่างเช่นselectจากตัวอธิบายไฟล์ของไปป์เป็นระยะทำการผสานเรียงลำดับในรายการบันทึกที่มีอยู่และลบทิ้งไปยังบันทึกจากส่วนกลางทำซ้ำ)

Nice นั่นคือ 35s ก่อนที่ฉันจะคิดว่า (คิดว่าฉันจะใช้atexit:-) ปัญหาคือว่ามันจะไม่ให้คุณอ่านข้อมูลเรียลไทม์ นี่อาจเป็นส่วนหนึ่งของราคาของการประมวลผลหลายตัวซึ่งตรงข้ามกับมัลติเธรด
cdleary

@ cdleary โดยใช้วิธีการ piped มันจะใกล้เคียงกับเรียลไทม์เท่าที่จะได้รับ (โดยเฉพาะอย่างยิ่งถ้า stderr ไม่ได้ถูกบัฟเฟอร์ในกระบวนการเกิด)
vladr

1
อนึ่งสมมติฐานใหญ่ ๆ ที่นี่ไม่ใช่ Windows คุณอยู่บน Windows หรือไม่
vladr

22
ทำไมไม่ใช้เพียงการประมวลผลหลายครั้งคำถามและเธรดการบันทึกในกระบวนการหลักแทน ดูเหมือนง่ายขึ้น
แบรนดอนโรดส์

1
@BrandonRhodes - ที่ผมกล่าวว่าไม่ใช่ก้าวร้าว การใช้multiprocessing.Queueจะไม่ง่ายกว่านี้หากมีโค้ดจำนวนมากที่ต้องใช้ rewire เพื่อใช้งานmultiprocessing.Queueและ / หรือหากประสิทธิภาพการทำงานเป็นปัญหา
vladr

122

ตอนนี้ฉันเพิ่งเขียนตัวจัดการบันทึกของฉันเองที่เพิ่งดึงข้อมูลทุกอย่างไปยังกระบวนการผู้ปกครองผ่านไพพ์ ฉันเพิ่งทดสอบมาสิบนาทีแล้ว แต่มันก็ใช้งานได้ดี

( หมายเหตุ:นี่คือฮาร์ดโค้ดRotatingFileHandlerซึ่งเป็นกรณีการใช้งานของฉันเอง)


อัปเดต: @javier คงวิธีการนี้เป็นแพคเกจที่มีอยู่ใน Pypi - ดูการบันทึกมัลติโพรเซสซิงใน Pypi, github ที่https://github.com/jruere/multiprocessing-logging


ปรับปรุง: การใช้งาน!

ตอนนี้ใช้คิวสำหรับจัดการการเกิดพร้อมกันและแก้ไขจากข้อผิดพลาดได้อย่างถูกต้อง ตอนนี้ฉันใช้สิ่งนี้ในการผลิตมาหลายเดือนแล้วและเวอร์ชั่นปัจจุบันด้านล่างใช้งานได้โดยไม่มีปัญหา

from logging.handlers import RotatingFileHandler
import multiprocessing, threading, logging, sys, traceback

class MultiProcessingLog(logging.Handler):
    def __init__(self, name, mode, maxsize, rotate):
        logging.Handler.__init__(self)

        self._handler = RotatingFileHandler(name, mode, maxsize, rotate)
        self.queue = multiprocessing.Queue(-1)

        t = threading.Thread(target=self.receive)
        t.daemon = True
        t.start()

    def setFormatter(self, fmt):
        logging.Handler.setFormatter(self, fmt)
        self._handler.setFormatter(fmt)

    def receive(self):
        while True:
            try:
                record = self.queue.get()
                self._handler.emit(record)
            except (KeyboardInterrupt, SystemExit):
                raise
            except EOFError:
                break
            except:
                traceback.print_exc(file=sys.stderr)

    def send(self, s):
        self.queue.put_nowait(s)

    def _format_record(self, record):
        # ensure that exc_info and args
        # have been stringified.  Removes any chance of
        # unpickleable things inside and possibly reduces
        # message size sent over the pipe
        if record.args:
            record.msg = record.msg % record.args
            record.args = None
        if record.exc_info:
            dummy = self.format(record)
            record.exc_info = None

        return record

    def emit(self, record):
        try:
            s = self._format_record(record)
            self.send(s)
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

    def close(self):
        self._handler.close()
        logging.Handler.close(self)

4
ตัวจัดการด้านบนทำการเขียนไฟล์ทั้งหมดจากกระบวนการหลักและใช้เพียงหนึ่งเธรดเพื่อรับข้อความที่ส่งจากกระบวนการลูก หากคุณเรียกใช้ตัวจัดการจากกระบวนการลูกที่เกิดจากการใช้งานอย่างไม่ถูกต้องและคุณจะได้รับปัญหาเดียวกันทั้งหมดเช่น RotatingFileHandler ฉันใช้รหัสข้างต้นเป็นเวลาหลายปีโดยไม่มีปัญหา
zzzeek

9
น่าเสียดายที่วิธีการนี้ใช้ไม่ได้กับ Windows จากdocs.python.org/library/multiprocessing.html 16.6.2.12 "โปรดทราบว่าในกระบวนการลูกของ Windows จะสืบทอดระดับของ logger ของกระบวนการหลักเท่านั้น - การปรับแต่งอื่น ๆ ของตัวบันทึกจะไม่ถูกสืบทอด" กระบวนการย่อยจะไม่สืบทอดตัวจัดการและคุณไม่สามารถผ่านมันได้อย่างชัดเจนเพราะมันไม่สามารถเลือกได้
โนอาห์ Yetter

2
มันน่าสังเกตว่าการใช้ด้ายในmultiprocessing.Queue put()ดังนั้นอย่าเรียกใช้put(เช่นบันทึกข้อความ msg โดยใช้MultiProcessingLogตัวจัดการ) ก่อนสร้างกระบวนการย่อยทั้งหมด มิฉะนั้นเธรดจะตายในกระบวนการลูก ทางออกหนึ่งคือโทรหาQueue._after_fork()ตอนเริ่มต้นของแต่ละกระบวนการลูกหรือใช้multiprocessing.queues.SimpleQueueแทนซึ่งไม่เกี่ยวข้องกับเธรด แต่กำลังบล็อก
Danqi Wang

5
คุณสามารถเพิ่มตัวอย่างง่ายๆที่แสดงการเริ่มต้นรวมถึงการใช้งานจากกระบวนการลูกที่เป็นสมมุติได้หรือไม่? ฉันไม่แน่ใจว่ากระบวนการของเด็กควรเข้าถึงคิวได้อย่างไรโดยไม่ต้องยกตัวอย่างอินสแตนซ์อื่นของชั้นเรียนของคุณ
JesseBuesking

11
@zzzeek โซลูชันนี้ดี แต่ฉันไม่สามารถหาแพ็คเกจด้วยหรือคล้ายกันดังนั้นฉันจึงสร้างชื่อmultiprocessing-loggingขึ้นมา
Javier

30

QueueHandlerมีถิ่นกำเนิดใน Python 3.2+ และทำสิ่งนี้ทุกประการ มันถูกจำลองแบบได้อย่างง่ายดายในรุ่นก่อนหน้า

Python docs มีสองตัวอย่างที่สมบูรณ์: การเข้าสู่ไฟล์เดียวจากหลายกระบวนการ

สำหรับผู้ที่ใช้หลาม <3.2 เพียงคัดลอกQueueHandlerลงในรหัสของคุณเองจาก: https://gist.github.com/vsajip/591589หรือหรือนำเข้าlogutils

แต่ละกระบวนการ (รวมถึงกระบวนการพาเรนต์) ทำให้การบันทึกบนQueueและจากนั้นlistenerเธรดหรือกระบวนการ (ตัวอย่างหนึ่งมีไว้สำหรับแต่ละ) หยิบขึ้นมาและเขียนพวกเขาทั้งหมดลงในไฟล์ - ไม่มีความเสี่ยงของความเสียหายหรือการอ่านไม่ออก


21

ด้านล่างเป็นอีกโซลูชันหนึ่งที่มุ่งเน้นความเรียบง่ายสำหรับคนอื่น ๆ (เช่นฉัน) ที่มาจาก Google การบันทึกควรเป็นเรื่องง่าย! สำหรับ 3.2 หรือสูงกว่าเท่านั้น

import multiprocessing
import logging
from logging.handlers import QueueHandler, QueueListener
import time
import random


def f(i):
    time.sleep(random.uniform(.01, .05))
    logging.info('function called with {} in worker thread.'.format(i))
    time.sleep(random.uniform(.01, .05))
    return i


def worker_init(q):
    # all records from worker processes go to qh and then into q
    qh = QueueHandler(q)
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    logger.addHandler(qh)


def logger_init():
    q = multiprocessing.Queue()
    # this is the handler for all log records
    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter("%(levelname)s: %(asctime)s - %(process)s - %(message)s"))

    # ql gets records from the queue and sends them to the handler
    ql = QueueListener(q, handler)
    ql.start()

    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    # add the handler to the logger so records from this process are handled
    logger.addHandler(handler)

    return ql, q


def main():
    q_listener, q = logger_init()

    logging.info('hello from main thread')
    pool = multiprocessing.Pool(4, worker_init, [q])
    for result in pool.map(f, range(10)):
        pass
    pool.close()
    pool.join()
    q_listener.stop()

if __name__ == '__main__':
    main()

2
QueueHandlerและQueueListenerเรียนที่สามารถใช้กับงูหลาม 2.7 เช่นเดียวกับที่มีอยู่ในlogutilsแพคเกจ
Lev Levitsky

5
ตัวบันทึกของกระบวนการหลักควรใช้ QueueHandler ด้วย ในรหัสปัจจุบันของคุณกระบวนการหลักคือการข้ามคิวเพื่อให้สามารถมีเงื่อนไขการแข่งขันระหว่างกระบวนการหลักและคนงาน ทุกคนควรเข้าสู่ระบบคิว (ผ่าน QueueHandler) และเฉพาะ QueueListener เท่านั้นที่ควรได้รับอนุญาตให้เข้าสู่ StreamHandler
Ismael EL ATIFI

นอกจากนี้คุณไม่ต้องเริ่มต้นคนตัดไม้ในเด็กแต่ละคน เพียงเริ่มต้นคนตัดไม้ในกระบวนการหลักและรับคนตัดไม้ในแต่ละกระบวนการลูก
okwap

20

อีกทางเลือกหนึ่งอาจเป็นตัวจัดการการบันทึกที่ไม่ใช่ไฟล์ที่หลากหลายในloggingแพ็คเกจ :

  • SocketHandler
  • DatagramHandler
  • SyslogHandler

(และคนอื่น ๆ)

ด้วยวิธีนี้คุณสามารถมีดีมอนการบันทึกที่ไหนสักแห่งที่คุณสามารถเขียนไปอย่างปลอดภัยและจะจัดการผลลัพธ์ได้อย่างถูกต้อง (เช่นซ็อกเก็ตเซิร์ฟเวอร์อย่างง่ายที่เพิ่งคลายเกลียวข้อความและส่งไปยังตัวจัดการไฟล์หมุนเวียนของตัวเอง)

SyslogHandlerจะดูแลนี้สำหรับคุณมากเกินไป แน่นอนคุณสามารถใช้อินสแตนซ์ของคุณเองsyslogไม่ใช่ระบบหนึ่ง


13

ตัวแปรของรายการอื่น ๆ ที่แยกการบันทึกและคิวเธรดแยกกัน

"""sample code for logging in subprocesses using multiprocessing

* Little handler magic - The main process uses loggers and handlers as normal.
* Only a simple handler is needed in the subprocess that feeds the queue.
* Original logger name from subprocess is preserved when logged in main
  process.
* As in the other implementations, a thread reads the queue and calls the
  handlers. Except in this implementation, the thread is defined outside of a
  handler, which makes the logger definitions simpler.
* Works with multiple handlers.  If the logger in the main process defines
  multiple handlers, they will all be fed records generated by the
  subprocesses loggers.

tested with Python 2.5 and 2.6 on Linux and Windows

"""

import os
import sys
import time
import traceback
import multiprocessing, threading, logging, sys

DEFAULT_LEVEL = logging.DEBUG

formatter = logging.Formatter("%(levelname)s: %(asctime)s - %(name)s - %(process)s - %(message)s")

class SubProcessLogHandler(logging.Handler):
    """handler used by subprocesses

    It simply puts items on a Queue for the main process to log.

    """

    def __init__(self, queue):
        logging.Handler.__init__(self)
        self.queue = queue

    def emit(self, record):
        self.queue.put(record)

class LogQueueReader(threading.Thread):
    """thread to write subprocesses log records to main process log

    This thread reads the records written by subprocesses and writes them to
    the handlers defined in the main process's handlers.

    """

    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue
        self.daemon = True

    def run(self):
        """read from the queue and write to the log handlers

        The logging documentation says logging is thread safe, so there
        shouldn't be contention between normal logging (from the main
        process) and this thread.

        Note that we're using the name of the original logger.

        """
        # Thanks Mike for the error checking code.
        while True:
            try:
                record = self.queue.get()
                # get the logger for this record
                logger = logging.getLogger(record.name)
                logger.callHandlers(record)
            except (KeyboardInterrupt, SystemExit):
                raise
            except EOFError:
                break
            except:
                traceback.print_exc(file=sys.stderr)

class LoggingProcess(multiprocessing.Process):

    def __init__(self, queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue

    def _setupLogger(self):
        # create the logger to use.
        logger = logging.getLogger('test.subprocess')
        # The only handler desired is the SubProcessLogHandler.  If any others
        # exist, remove them. In this case, on Unix and Linux the StreamHandler
        # will be inherited.

        for handler in logger.handlers:
            # just a check for my sanity
            assert not isinstance(handler, SubProcessLogHandler)
            logger.removeHandler(handler)
        # add the handler
        handler = SubProcessLogHandler(self.queue)
        handler.setFormatter(formatter)
        logger.addHandler(handler)

        # On Windows, the level will not be inherited.  Also, we could just
        # set the level to log everything here and filter it in the main
        # process handlers.  For now, just set it from the global default.
        logger.setLevel(DEFAULT_LEVEL)
        self.logger = logger

    def run(self):
        self._setupLogger()
        logger = self.logger
        # and here goes the logging
        p = multiprocessing.current_process()
        logger.info('hello from process %s with pid %s' % (p.name, p.pid))


if __name__ == '__main__':
    # queue used by the subprocess loggers
    queue = multiprocessing.Queue()
    # Just a normal logger
    logger = logging.getLogger('test')
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    logger.setLevel(DEFAULT_LEVEL)
    logger.info('hello from the main process')
    # This thread will read from the subprocesses and write to the main log's
    # handlers.
    log_queue_reader = LogQueueReader(queue)
    log_queue_reader.start()
    # create the processes.
    for i in range(10):
        p = LoggingProcess(queue)
        p.start()
    # The way I read the multiprocessing warning about Queue, joining a
    # process before it has finished feeding the Queue can cause a deadlock.
    # Also, Queue.empty() is not realiable, so just make sure all processes
    # are finished.
    # active_children joins subprocesses when they're finished.
    while multiprocessing.active_children():
        time.sleep(.1)

ฉันชอบความคิดในการดึงชื่อตัวบันทึกจากคิวการบันทึก สิ่งนี้อนุญาตให้ใช้แบบดั้งเดิมfileConfig()ใน MainProcess และตัวบันทึกที่กำหนดค่าแทบใน PoolWorkers (มีเท่านั้นsetLevel(logging.NOTSET)) ดังที่ฉันพูดถึงในความคิดเห็นอื่นฉันใช้ Pool ดังนั้นฉันต้องได้รับ Queue (proxy) จาก Manager แทนการประมวลผลแบบมัลติเพื่อให้สามารถเลือกได้ สิ่งนี้ทำให้ฉันสามารถส่งต่อคิวไปยังผู้ปฏิบัติงานภายในพจนานุกรม (ส่วนใหญ่มาจากการใช้วัตถุ argsparse vars()) ฉันรู้สึกว่าในท้ายที่สุดนี้เป็นวิธีที่ดีที่สุดสำหรับ MS Windows ที่ไม่มี fork () และแบ่งโซลูชัน @zzzeak
mlt

@mlt ฉันคิดว่าคุณสามารถใส่คิวหลายตัวประมวลผลใน init แทนการใช้ผู้จัดการ (ดูคำตอบของstackoverflow.com/questions/25557686// - มันเกี่ยวกับล็อค แต่ฉันเชื่อว่ามันใช้งานได้กับคิวด้วย)
fantabolous

@fantabolous ที่จะไม่ทำงานบน MS Windows หรือแพลตฟอร์มอื่น ๆ forkที่ขาด วิธีนั้นแต่ละกระบวนการจะมีคิวที่ไร้ประโยชน์ของตัวเอง วิธีที่สองในการเชื่อมโยง Q / A จะไม่ทำงานบนแพลตฟอร์มดังกล่าว มันเป็นวิธีการรหัสไม่พกพา
mlt

@mlt ที่น่าสนใจ ฉันใช้ Windows และดูเหมือนว่าจะทำงานได้ดีสำหรับฉัน - ไม่นานหลังจากที่ฉันแสดงความคิดเห็นครั้งสุดท้ายที่ฉันตั้งกลุ่มของกระบวนการที่ใช้ร่วมกันmultiprocessing.Queueกับกระบวนการหลักและฉันได้ใช้มันอย่างต่อเนื่องตั้งแต่ จะไม่อ้างว่าเข้าใจว่าทำไมมันถึงได้ผล
เพ้อฝัน

10

โซลูชันปัจจุบันทั้งหมดเชื่อมต่อกับการกำหนดค่าการบันทึกโดยใช้ตัวจัดการ โซลูชันของฉันมีสถาปัตยกรรมและคุณสมบัติต่อไปนี้:

  • คุณสามารถใช้การกำหนดค่าการบันทึกใด ๆ ที่คุณต้องการ
  • การบันทึกเสร็จสิ้นในเธรด daemon
  • การปิดระบบ daemon อย่างปลอดภัยโดยใช้ตัวจัดการบริบท
  • การสื่อสารกับเธรดการบันทึกทำโดย multiprocessing.Queue
  • ในกระบวนการย่อยlogging.Logger(และอินสแตนซ์ที่กำหนดไว้แล้ว) จะได้รับการแก้ไขเพื่อส่งระเบียนทั้งหมดไปยังคิว
  • ใหม่ : จัดรูปแบบการติดตามย้อนกลับและข้อความก่อนที่จะส่งไปยังคิวเพื่อป้องกันข้อผิดพลาดการหยิบ

โค้ดที่มีตัวอย่างการใช้งานและเอาต์พุตสามารถดูได้ที่ Gist ดังต่อไปนี้: https://gist.github.com/schlamar/7003737


เว้นแต่ฉันหายไปบางสิ่งบางอย่างนี้ไม่เป็นความจริงด้ายภูตเนื่องจากคุณไม่เคยตั้งไปdaemon_thread.daemon Trueฉันต้องทำเช่นนั้นเพื่อให้โปรแกรม Python ของฉันออกจากระบบอย่างถูกต้องเมื่อมีข้อยกเว้นเกิดขึ้นภายในตัวจัดการบริบท
blah238

ฉันยังต้องการที่จะจับบันทึกและกลืนข้อยกเว้นโยนโดยเป้าหมายfuncในlogged_callมิฉะนั้นข้อยกเว้นจะได้รับการอ่านไม่ออกด้วยการเข้าสู่ระบบอื่น ๆ นี่คือรุ่นที่แก้ไขของฉัน: gist.github.com/blah238/8ab79c4fe9cdb254f5c37abfc5dc85bf
blah238

8

เนื่องจากเราสามารถแสดงการบันทึกหลายกระบวนการในฐานะผู้เผยแพร่โฆษณาจำนวนมากและผู้สมัครสมาชิกหนึ่งคน (ผู้ฟัง) การใช้ZeroMQเพื่อใช้การส่งข้อความ PUB-SUB จึงเป็นตัวเลือก

นอกจากนี้โมดูลPyZMQซึ่งเป็น Python bindings สำหรับ ZMQ จะใช้PUBHandlerซึ่งเป็นวัตถุสำหรับการเผยแพร่ข้อความการบันทึกผ่านซ็อกเก็ต zmq.PUB

มีวิธีแก้ปัญหาบนเว็บสำหรับการบันทึกจากส่วนกลางจากแอพพลิเคชั่นกระจายโดยใช้ PyZMQ และ PUBHandler ซึ่งสามารถนำไปใช้งานได้ง่ายสำหรับการทำงานในพื้นที่ที่มีกระบวนการเผยแพร่หลายอย่าง

formatters = {
    logging.DEBUG: logging.Formatter("[%(name)s] %(message)s"),
    logging.INFO: logging.Formatter("[%(name)s] %(message)s"),
    logging.WARN: logging.Formatter("[%(name)s] %(message)s"),
    logging.ERROR: logging.Formatter("[%(name)s] %(message)s"),
    logging.CRITICAL: logging.Formatter("[%(name)s] %(message)s")
}

# This one will be used by publishing processes
class PUBLogger:
    def __init__(self, host, port=config.PUBSUB_LOGGER_PORT):
        self._logger = logging.getLogger(__name__)
        self._logger.setLevel(logging.DEBUG)
        self.ctx = zmq.Context()
        self.pub = self.ctx.socket(zmq.PUB)
        self.pub.connect('tcp://{0}:{1}'.format(socket.gethostbyname(host), port))
        self._handler = PUBHandler(self.pub)
        self._handler.formatters = formatters
        self._logger.addHandler(self._handler)

    @property
    def logger(self):
        return self._logger

# This one will be used by listener process
class SUBLogger:
    def __init__(self, ip, output_dir="", port=config.PUBSUB_LOGGER_PORT):
        self.output_dir = output_dir
        self._logger = logging.getLogger()
        self._logger.setLevel(logging.DEBUG)

        self.ctx = zmq.Context()
        self._sub = self.ctx.socket(zmq.SUB)
        self._sub.bind('tcp://*:{1}'.format(ip, port))
        self._sub.setsockopt(zmq.SUBSCRIBE, "")

        handler = handlers.RotatingFileHandler(os.path.join(output_dir, "client_debug.log"), "w", 100 * 1024 * 1024, 10)
        handler.setLevel(logging.DEBUG)
        formatter = logging.Formatter("%(asctime)s;%(levelname)s - %(message)s")
        handler.setFormatter(formatter)
        self._logger.addHandler(handler)

  @property
  def sub(self):
      return self._sub

  @property
  def logger(self):
      return self._logger

#  And that's the way we actually run things:

# Listener process will forever listen on SUB socket for incoming messages
def run_sub_logger(ip, event):
    sub_logger = SUBLogger(ip)
    while not event.is_set():
        try:
            topic, message = sub_logger.sub.recv_multipart(flags=zmq.NOBLOCK)
            log_msg = getattr(logging, topic.lower())
            log_msg(message)
        except zmq.ZMQError as zmq_error:
            if zmq_error.errno == zmq.EAGAIN:
                pass


# Publisher processes loggers should be initialized as follows:

class Publisher:
    def __init__(self, stop_event, proc_id):
        self.stop_event = stop_event
        self.proc_id = proc_id
        self._logger = pub_logger.PUBLogger('127.0.0.1').logger

     def run(self):
         self._logger.info("{0} - Sending message".format(proc_id))

def run_worker(event, proc_id):
    worker = Publisher(event, proc_id)
    worker.run()

# Starting subscriber process so we won't loose publisher's messages
sub_logger_process = Process(target=run_sub_logger,
                                 args=('127.0.0.1'), stop_event,))
sub_logger_process.start()

#Starting publisher processes
for i in range(MAX_WORKERS_PER_CLIENT):
    processes.append(Process(target=run_worker,
                                 args=(stop_event, i,)))
for p in processes:
    p.start()

6

ฉันชอบคำตอบของ zzzeek แต่ Andre ถูกต้องว่าต้องมีคิวเพื่อป้องกันการสับสน ฉันโชคดีไปกับท่อ แต่ก็เห็นการผสมปนเปซึ่งคาดว่าค่อนข้าง การปรับใช้นั้นยากกว่าที่ฉันคิดโดยเฉพาะอย่างยิ่งเนื่องจากการทำงานบน Windows ซึ่งมีข้อ จำกัด เพิ่มเติมบางประการเกี่ยวกับตัวแปรและสิ่งของทั่วโลก (ดู: Python การประมวลผลมัลติโพรเซสซิงติดตั้งบน Windows เป็นอย่างไร )

แต่ในที่สุดฉันก็ทำให้มันใช้งานได้ ตัวอย่างนี้อาจไม่สมบูรณ์ดังนั้นยินดีรับความคิดเห็นและคำแนะนำ นอกจากนี้ยังไม่รองรับการตั้งค่าตัวจัดรูปแบบหรือสิ่งอื่นนอกเหนือจากตัวบันทึกรูท โดยทั่วไปคุณต้องเริ่มต้นตัวบันทึกใหม่ในกระบวนการพูลแต่ละรายการด้วยคิวและตั้งค่าคุณลักษณะอื่น ๆ บนตัวบันทึก

อีกครั้งคำแนะนำใด ๆ เกี่ยวกับวิธีการทำให้โค้ดดีขึ้นยินดีต้อนรับ แน่นอนว่าฉันยังไม่รู้จักกลอุบาย Python ทั้งหมด :-)

import multiprocessing, logging, sys, re, os, StringIO, threading, time, Queue

class MultiProcessingLogHandler(logging.Handler):
    def __init__(self, handler, queue, child=False):
        logging.Handler.__init__(self)

        self._handler = handler
        self.queue = queue

        # we only want one of the loggers to be pulling from the queue.
        # If there is a way to do this without needing to be passed this
        # information, that would be great!
        if child == False:
            self.shutdown = False
            self.polltime = 1
            t = threading.Thread(target=self.receive)
            t.daemon = True
            t.start()

    def setFormatter(self, fmt):
        logging.Handler.setFormatter(self, fmt)
        self._handler.setFormatter(fmt)

    def receive(self):
        #print "receive on"
        while (self.shutdown == False) or (self.queue.empty() == False):
            # so we block for a short period of time so that we can
            # check for the shutdown cases.
            try:
                record = self.queue.get(True, self.polltime)
                self._handler.emit(record)
            except Queue.Empty, e:
                pass

    def send(self, s):
        # send just puts it in the queue for the server to retrieve
        self.queue.put(s)

    def _format_record(self, record):
        ei = record.exc_info
        if ei:
            dummy = self.format(record) # just to get traceback text into record.exc_text
            record.exc_info = None  # to avoid Unpickleable error

        return record

    def emit(self, record):
        try:
            s = self._format_record(record)
            self.send(s)
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self.handleError(record)

    def close(self):
        time.sleep(self.polltime+1) # give some time for messages to enter the queue.
        self.shutdown = True
        time.sleep(self.polltime+1) # give some time for the server to time out and see the shutdown

    def __del__(self):
        self.close() # hopefully this aids in orderly shutdown when things are going poorly.

def f(x):
    # just a logging command...
    logging.critical('function number: ' + str(x))
    # to make some calls take longer than others, so the output is "jumbled" as real MP programs are.
    time.sleep(x % 3)

def initPool(queue, level):
    """
    This causes the logging module to be initialized with the necessary info
    in pool threads to work correctly.
    """
    logging.getLogger('').addHandler(MultiProcessingLogHandler(logging.StreamHandler(), queue, child=True))
    logging.getLogger('').setLevel(level)

if __name__ == '__main__':
    stream = StringIO.StringIO()
    logQueue = multiprocessing.Queue(100)
    handler= MultiProcessingLogHandler(logging.StreamHandler(stream), logQueue)
    logging.getLogger('').addHandler(handler)
    logging.getLogger('').setLevel(logging.DEBUG)

    logging.debug('starting main')

    # when bulding the pool on a Windows machine we also have to init the logger in all the instances with the queue and the level of logging.
    pool = multiprocessing.Pool(processes=10, initializer=initPool, initargs=[logQueue, logging.getLogger('').getEffectiveLevel()] ) # start worker processes
    pool.map(f, range(0,50))
    pool.close()

    logging.debug('done')
    logging.shutdown()
    print "stream output is:"
    print stream.getvalue()

1
ฉันสงสัยว่าif 'MainProcess' == multiprocessing.current_process().name:สามารถใช้แทนได้childหรือไม่?
mlt

ในกรณีที่คนอื่นพยายามใช้กลุ่มการประมวลผลแทนที่จะแยกวัตถุกระบวนการบน Windows มันมีค่ากล่าวถึงว่าผู้จัดการจะต้องใช้ในการผ่านคิวเพื่อประมวลผลย่อยเนื่องจากมันไม่สามารถเลือกได้โดยตรง
mlt

การติดตั้งใช้งานได้ดีสำหรับฉัน ฉันแก้ไขมันให้ทำงานกับตัวจัดการจำนวนโดยพลการ วิธีนี้คุณสามารถกำหนดค่าตัวจัดการรูทของคุณในรูปแบบที่ไม่ใช้กระบวนการหลายตัวประมวลผลจากนั้นจะปลอดภัยที่จะสร้างคิวส่งตัวจัดการรูทไปยังสิ่งนี้ลบพวกมันและทำให้เป็นตัวจัดการเท่านั้น
Jaxor24

3

เพียงเผยแพร่ที่ไหนสักแห่งอินสแตนซ์ของคุณของคนตัดไม้ ว่าวิธีการที่โมดูลอื่น ๆ และลูกค้าสามารถใช้ API import multiprocessingของคุณจะได้รับการตัดไม้โดยไม่ต้อง


1
ปัญหานี้คือตัวบันทึกมัลติโพรเซสซิงไม่มีชื่อดังนั้นคุณจะไม่สามารถถอดรหัสสตรีมข้อความได้อย่างง่ายดาย อาจเป็นไปได้ที่จะตั้งชื่อพวกเขาหลังจากการสร้างซึ่งจะทำให้มีเหตุผลมากขึ้นที่จะดู
cdleary

ดีเผยแพร่หนึ่ง logger สำหรับแต่ละโมดูลหรือดีกว่าส่งออกปิดที่แตกต่างกันที่ใช้ logger ด้วยชื่อโมดูล จุดคือการปล่อยให้โมดูลอื่น ๆ ที่ใช้ API ของคุณ
ฮาเวียร์

สมเหตุสมผลอย่างแน่นอน (และ +1 จากฉัน!) แต่ฉันจะพลาดที่จะสามารถไปimport logging; logging.basicConfig(level=logging.DEBUG); logging.debug('spam!')ได้ทุกที่และทำงานได้อย่างถูกต้อง
cdleary

3
มันเป็นปรากฏการณ์ที่น่าสนใจที่ฉันเห็นเมื่อฉันใช้ Python เราเคยชินกับการทำสิ่งที่เราต้องการใน 1 หรือ 2 เส้นอย่างง่าย ๆ ซึ่งเป็นวิธีที่ง่ายและมีตรรกะในภาษาอื่น ๆ (เช่นเพื่อเผยแพร่ตัวบันทึกหลายตัว ใน accessor) ยังรู้สึกเหมือนเป็นภาระ :)
Kylotan

3

ฉันชอบคำตอบของ zzzeek ฉันจะแทนที่ Pipe สำหรับคิวเนื่องจากหากหลายเธรด / กระบวนการใช้ปลายท่อเดียวกันเพื่อสร้างข้อความบันทึกพวกเขาจะได้รับข้อมูลที่อ่านไม่ออก


ฉันกำลังมีปัญหาบางอย่างกับตัวจัดการแม้ว่ามันจะไม่ใช่ข้อความที่อ่านไม่ออก แต่สิ่งทั้งหมดก็หยุดทำงาน ฉันเปลี่ยน Pipe เป็น Queue เพราะนั่นเหมาะสมกว่า อย่างไรก็ตามข้อผิดพลาดที่ฉันได้รับไม่ได้รับการแก้ไข - ในที่สุดฉันได้เพิ่มลอง / ยกเว้นวิธีรับ () - บ่อยมากความพยายามในการบันทึกข้อยกเว้นจะล้มเหลวและจบลงด้วยการถูกจับที่นั่น เมื่อฉันเพิ่มลอง / ยกเว้นมันจะทำงานเป็นเวลาหลายสัปดาห์โดยไม่มีปัญหาและไฟล์มาตรฐานจะคว้าสองข้อยกเว้นที่ผิดพลาดต่อสัปดาห์
zzzeek

2

วิธีการเกี่ยวกับการมอบหมายการบันทึกทั้งหมดไปยังกระบวนการอื่นที่อ่านรายการบันทึกทั้งหมดจากคิว?

LOG_QUEUE = multiprocessing.JoinableQueue()

class CentralLogger(multiprocessing.Process):
    def __init__(self, queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue
        self.log = logger.getLogger('some_config')
        self.log.info("Started Central Logging process")

    def run(self):
        while True:
            log_level, message = self.queue.get()
            if log_level is None:
                self.log.info("Shutting down Central Logging process")
                break
            else:
                self.log.log(log_level, message)

central_logger_process = CentralLogger(LOG_QUEUE)
central_logger_process.start()

เพียงแบ่งปัน LOG_QUEUE ผ่านกลไกมัลติโพรเซสหรือแม้แต่การสืบทอดและมันก็ใช้ได้ดี!


1

ฉันมีวิธีแก้ปัญหาที่คล้ายกับ ironhacker's ยกเว้นว่าฉันใช้ logging.exception ในบางรหัสของฉันและพบว่าฉันต้องการจัดรูปแบบข้อยกเว้นก่อนที่จะส่งกลับไปที่คิวเนื่องจากการสืบค้นกลับไม่สามารถเลือกได้:

class QueueHandler(logging.Handler):
    def __init__(self, queue):
        logging.Handler.__init__(self)
        self.queue = queue
    def emit(self, record):
        if record.exc_info:
            # can't pass exc_info across processes so just format now
            record.exc_text = self.formatException(record.exc_info)
            record.exc_info = None
        self.queue.put(record)
    def formatException(self, ei):
        sio = cStringIO.StringIO()
        traceback.print_exception(ei[0], ei[1], ei[2], None, sio)
        s = sio.getvalue()
        sio.close()
        if s[-1] == "\n":
            s = s[:-1]
        return s

ผมพบว่าตัวอย่างที่สมบูรณ์ตามบรรทัดเหล่านี้ที่นี่
Aryeh Leib Taurog

1

ด้านล่างนี้เป็นคลาสที่สามารถใช้ในสภาพแวดล้อม Windows ต้องใช้ ActivePython คุณยังสามารถสืบทอดตัวจัดการการบันทึกอื่น ๆ (StreamHandler เป็นต้น)

class SyncronizedFileHandler(logging.FileHandler):
    MUTEX_NAME = 'logging_mutex'

    def __init__(self , *args , **kwargs):

        self.mutex = win32event.CreateMutex(None , False , self.MUTEX_NAME)
        return super(SyncronizedFileHandler , self ).__init__(*args , **kwargs)

    def emit(self, *args , **kwargs):
        try:
            win32event.WaitForSingleObject(self.mutex , win32event.INFINITE)
            ret = super(SyncronizedFileHandler , self ).emit(*args , **kwargs)
        finally:
            win32event.ReleaseMutex(self.mutex)
        return ret

และนี่คือตัวอย่างที่แสดงให้เห็นถึงการใช้งาน:

import logging
import random , time , os , sys , datetime
from string import letters
import win32api , win32event
from multiprocessing import Pool

def f(i):
    time.sleep(random.randint(0,10) * 0.1)
    ch = random.choice(letters)
    logging.info( ch * 30)


def init_logging():
    '''
    initilize the loggers
    '''
    formatter = logging.Formatter("%(levelname)s - %(process)d - %(asctime)s - %(filename)s - %(lineno)d - %(message)s")
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    file_handler = SyncronizedFileHandler(sys.argv[1])
    file_handler.setLevel(logging.INFO)
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)

#must be called in the parent and in every worker process
init_logging() 

if __name__ == '__main__':
    #multiprocessing stuff
    pool = Pool(processes=10)
    imap_result = pool.imap(f , range(30))
    for i , _ in enumerate(imap_result):
        pass

อาจใช้multiprocessing.Lock()แทน Windows Mutex จะทำให้การแก้ปัญหาแบบพกพา
xmedeko

1

นี่คือการแฮ็ค / วิธีแก้ปัญหาที่เรียบง่ายของฉัน ... ไม่ครอบคลุมที่สุด แต่สามารถแก้ไขได้ง่ายและเข้าใจง่ายกว่าที่ฉันคิดว่าฉันคิดว่าคำตอบอื่น ๆ ที่ฉันพบก่อนเขียนนี้:

import logging
import multiprocessing

class FakeLogger(object):
    def __init__(self, q):
        self.q = q
    def info(self, item):
        self.q.put('INFO - {}'.format(item))
    def debug(self, item):
        self.q.put('DEBUG - {}'.format(item))
    def critical(self, item):
        self.q.put('CRITICAL - {}'.format(item))
    def warning(self, item):
        self.q.put('WARNING - {}'.format(item))

def some_other_func_that_gets_logger_and_logs(num):
    # notice the name get's discarded
    # of course you can easily add this to your FakeLogger class
    local_logger = logging.getLogger('local')
    local_logger.info('Hey I am logging this: {} and working on it to make this {}!'.format(num, num*2))
    local_logger.debug('hmm, something may need debugging here')
    return num*2

def func_to_parallelize(data_chunk):
    # unpack our args
    the_num, logger_q = data_chunk
    # since we're now in a new process, let's monkeypatch the logging module
    logging.getLogger = lambda name=None: FakeLogger(logger_q)
    # now do the actual work that happens to log stuff too
    new_num = some_other_func_that_gets_logger_and_logs(the_num)
    return (the_num, new_num)

if __name__ == '__main__':
    multiprocessing.freeze_support()
    m = multiprocessing.Manager()
    logger_q = m.Queue()
    # we have to pass our data to be parallel-processed
    # we also need to pass the Queue object so we can retrieve the logs
    parallelable_data = [(1, logger_q), (2, logger_q)]
    # set up a pool of processes so we can take advantage of multiple CPU cores
    pool_size = multiprocessing.cpu_count() * 2
    pool = multiprocessing.Pool(processes=pool_size, maxtasksperchild=4)
    worker_output = pool.map(func_to_parallelize, parallelable_data)
    pool.close() # no more tasks
    pool.join()  # wrap up current tasks
    # get the contents of our FakeLogger object
    while not logger_q.empty():
        print logger_q.get()
    print 'worker output contained: {}'.format(worker_output)

1

มีแพ็คเกจที่ยอดเยี่ยมนี้

แพคเกจ: https://pypi.python.org/pypi/multiprocessing-logging/

รหัส: https://github.com/jruere/multiprocessing-logging

ติดตั้ง:

pip install multiprocessing-logging

จากนั้นเพิ่ม:

import multiprocessing_logging

# This enables logs inside process
multiprocessing_logging.install_mp_handler()

3
ห้องสมุดนี้เป็นไปตามตัวอักษรออกจากความคิดเห็นอื่นในการโพสต์ SO ปัจจุบัน: stackoverflow.com/a/894284/1698058
Chris Hunt

ต้นกำเนิด: stackoverflow.com/a/894284/1663382ฉันขอขอบคุณตัวอย่างการใช้โมดูลนอกเหนือจากเอกสารประกอบในหน้าแรก
Liquidgenius

0

หนึ่งในทางเลือกคือการเขียนการบันทึก mutliprocessing ไปยังไฟล์ที่รู้จักและลงทะเบียนatexitตัวจัดการเพื่อเข้าร่วมในกระบวนการเหล่านั้นอ่านกลับบน stderr; อย่างไรก็ตามคุณจะไม่ได้รับข้อความตามเวลาจริงใน stderr


เป็นวิธีที่คุณเสนอด้านล่างเหมือนกับวิธีหนึ่งจากความคิดเห็นของคุณที่นี่stackoverflow.com/questions/641420/…
iruvar

0

หากคุณมีการหยุดชะงักเกิดขึ้นในการรวมกันของล็อคกระทู้และส้อมในloggingโมดูลที่มีการรายงานในรายงานข้อผิดพลาด 6721 (ดูคำถาม SO ที่เกี่ยวข้องด้วย )

มีวิธี fixup เล็ก ๆ โพสต์ที่นี่

loggingแต่ที่เพิ่งจะแก้ไขปัญหาการติดตายที่อาจเกิดขึ้นใน ที่จะไม่แก้ไขสิ่งที่อาจจะอ่านไม่ออก ดูคำตอบอื่น ๆ ที่นำเสนอที่นี่


0

แนวคิดที่ง่ายที่สุดดังกล่าว:

  • หยิบชื่อไฟล์และรหัสกระบวนการของกระบวนการปัจจุบัน
  • [WatchedFileHandler][1]ตั้งค่า เหตุผลของการจัดการนี้จะกล่าวถึงในรายละเอียดที่นี่แต่ในระยะสั้นมีเงื่อนไขการแข่งขันที่เลวร้ายยิ่งขึ้นกับตัวจัดการการบันทึกอื่น ๆ หน้าต่างนี้มีหน้าต่างที่สั้นที่สุดสำหรับสภาพการแข่งขัน
    • เลือกเส้นทางที่จะบันทึกบันทึกเช่น / var / log / ...

0

สำหรับใครก็ตามที่อาจต้องการสิ่งนี้ฉันได้เขียนมัณฑนากรสำหรับแพ็คเกจการประมวลผลหลายตัวที่เพิ่มชื่อกระบวนการปัจจุบันลงในบันทึกดังนั้นจึงชัดเจนว่าใครบันทึกสิ่งใด

นอกจากนี้ยังรัน install_mp_handler () ดังนั้นจึงไม่ควรเรียกใช้ก่อนที่จะสร้างพูล

สิ่งนี้ทำให้ฉันเห็นคนงานที่สร้างข้อความบันทึก

นี่คือพิมพ์เขียวพร้อมตัวอย่าง:

import sys
import logging
from functools import wraps
import multiprocessing
import multiprocessing_logging

# Setup basic console logger as 'logger'
logger = logging.getLogger()
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(logging.Formatter(u'%(asctime)s :: %(levelname)s :: %(message)s'))
logger.setLevel(logging.DEBUG)
logger.addHandler(console_handler)


# Create a decorator for functions that are called via multiprocessing pools
def logs_mp_process_names(fn):
    class MultiProcessLogFilter(logging.Filter):
        def filter(self, record):
            try:
                process_name = multiprocessing.current_process().name
            except BaseException:
                process_name = __name__
            record.msg = f'{process_name} :: {record.msg}'
            return True

    multiprocessing_logging.install_mp_handler()
    f = MultiProcessLogFilter()

    # Wraps is needed here so apply / apply_async know the function name
    @wraps(fn)
    def wrapper(*args, **kwargs):
        logger.removeFilter(f)
        logger.addFilter(f)
        return fn(*args, **kwargs)

    return wrapper


# Create a test function and decorate it
@logs_mp_process_names
def test(argument):
    logger.info(f'test function called via: {argument}')


# You can also redefine undecored functions
def undecorated_function():
    logger.info('I am not decorated')


@logs_mp_process_names
def redecorated(*args, **kwargs):
    return undecorated_function(*args, **kwargs)


# Enjoy
if __name__ == '__main__':
    with multiprocessing.Pool() as mp_pool:
        # Also works with apply_async
        mp_pool.apply(test, ('mp pool',))
        mp_pool.apply(redecorated)
        logger.info('some main logs')
        test('main program')

-5

สำหรับเด็กที่พบปัญหาเดียวกันในทศวรรษและพบคำถามนี้ในเว็บไซต์นี้ฉันทิ้งคำตอบนี้ไว้

ความเรียบง่ายกับการ overcomplicating เพียงใช้เครื่องมืออื่น ๆ Python ยอดเยี่ยม แต่ไม่ได้ออกแบบมาเพื่อทำบางสิ่ง

ตัวอย่างต่อไปนี้สำหรับdaemon แบบรอทเทรทใช้งานได้สำหรับฉันและไม่ซับซ้อนเกินไป กำหนดเวลาให้ทำงานทุกชั่วโมงและ

/var/log/mylogfile.log {
    size 1
    copytruncate
    create
    rotate 10
    missingok
    postrotate
        timeext=`date -d '1 hour ago' "+%Y-%m-%d_%H"`
        mv /var/log/mylogfile.log.1 /var/log/mylogfile-$timeext.log
    endscript
}

นี่คือวิธีที่ฉันติดตั้ง (symlink ไม่ทำงานสำหรับ logrotate):

sudo cp /directpath/config/logrotate/myconfigname /etc/logrotate.d/myconfigname
sudo cp /etc/cron.daily/logrotate /etc/cron.hourly/logrotate
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.