Tôi nên đăng nhập như thế nào trong khi sử dụng đa xử lý trong Python?


238

Ngay bây giờ tôi có một mô-đun trung tâm trong một khung công tác sinh ra nhiều quy trình bằng multiprocessingmô-đun Python 2.6 . Bởi vì nó sử dụng multiprocessing, có nhật ký nhận biết đa xử lý cấp mô-đun , LOG = multiprocessing.get_logger(). Theo các tài liệu , trình ghi nhật ký này có các khóa chia sẻ quy trình để bạn không cắt xén mọi thứ trong sys.stderr(hoặc bất kỳ tệp nào) bằng cách có nhiều quy trình ghi đồng thời.

Vấn đề tôi có bây giờ là các mô-đun khác trong khung không nhận thức được đa xử lý. Theo cách tôi thấy, tôi cần phải làm cho tất cả các phụ thuộc vào mô-đun trung tâm này sử dụng ghi nhật ký nhận biết đa xử lý. Điều đó gây khó chịu trong khuôn khổ, hãy để một mình cho tất cả các khách hàng của khung. Có những lựa chọn thay thế mà tôi không nghĩ đến?


10
Các tài liệu bạn liên kết đến, nêu chính xác điều ngược lại với những gì bạn nói, bộ ghi chép không có quá trình chia sẻ khóa và mọi thứ bị lẫn lộn - một vấn đề tôi cũng gặp phải.
Sebastian Blask

3
xem các ví dụ trong tài liệu stdlib: Đăng nhập vào một tệp từ nhiều quy trình . Các công thức nấu ăn không yêu cầu các mô-đun khác phải nhận thức được đa xử lý.
jfs

Vì vậy, trường hợp sử dụng là multiprocessing.get_logger()gì? Dường như dựa trên những cách khác để thực hiện ghi nhật ký là chức năng ghi nhật ký multiprocessingít có giá trị.
Tim Ludwinski

4
get_logger()là logger được sử dụng bởi multiprocessingchính mô-đun. Nó rất hữu ích nếu bạn muốn gỡ lỗi một multiprocessingvấn đề.
jfs

Câu trả lời:


69

Cách duy nhất để đối phó với điều này không xâm phạm là:

  1. Sinh ra mỗi công nhân xử lý sao cho nhật ký của nó đi đến một bộ mô tả tệp khác nhau (vào đĩa hoặc vào đường ống.) Lý tưởng nhất, tất cả các mục nhật ký nên được đánh dấu thời gian.
  2. Quá trình điều khiển của bạn sau đó có thể thực hiện một trong những điều sau đây:
    • Nếu sử dụng tệp đĩa: Kết hợp các tệp nhật ký khi kết thúc hoạt động, được sắp xếp theo dấu thời gian
    • Nếu sử dụng đường ống (được khuyến nghị): Các mục nhật ký kết hợp đang hoạt động từ tất cả các đường ống, vào một tệp nhật ký trung tâm. (Ví dụ: Định kỳ selecttừ các bộ mô tả tệp của ống, thực hiện sắp xếp hợp nhất trên các mục nhật ký có sẵn và chuyển sang nhật ký tập trung. Lặp lại.)

Thật tuyệt, đó là 35 giây trước khi tôi nghĩ về điều đó (tôi nghĩ tôi sẽ sử dụng atexit:-). Vấn đề là nó sẽ không cung cấp cho bạn đọc thời gian thực. Đây có thể là một phần của giá của đa xử lý trái ngược với đa luồng.
cdleary

@cdleary, sử dụng phương pháp đường ống, nó sẽ gần như thời gian thực như người ta có thể nhận được (đặc biệt là nếu stderr không được đệm trong các quy trình được sinh ra.)
vladr

1
Ngẫu nhiên, giả định lớn ở đây: không phải Windows. Bạn có trên Windows không?
vladr

22
Tại sao không chỉ sử dụng đa xử lý.Queue và một luồng đăng nhập trong quy trình chính thay thế? Có vẻ đơn giản hơn.
Brandon Rhodes

1
@BrandonRhodes - Như tôi đã nói, không xâm phạm . Việc sử dụng multiprocessing.Queuesẽ không đơn giản hơn nếu có nhiều mã để sử dụng lại multiprocessing.Queuevà / hoặc nếu hiệu suất là một vấn đề
vladr

122

Bây giờ tôi chỉ viết một trình xử lý nhật ký của riêng mình, nó chỉ cung cấp mọi thứ cho tiến trình cha mẹ thông qua một đường ống. Tôi mới chỉ thử nghiệm trong mười phút nhưng có vẻ như nó hoạt động khá tốt.

( Lưu ý: Đây là mã hóa cứng RotatingFileHandler, là trường hợp sử dụng của riêng tôi.)


Cập nhật: @javier hiện duy trì cách tiếp cận này như một gói có sẵn trên Pypi - xem đăng nhập đa xử lý trên Pypi, github tại https://github.com/jruere/multiprocessing-logging


Cập nhật: Thực hiện!

Điều này hiện sử dụng một hàng đợi để xử lý chính xác đồng thời và cũng phục hồi chính xác từ các lỗi. Bây giờ tôi đã sử dụng điều này trong sản xuất trong vài tháng và phiên bản hiện tại bên dưới hoạt động mà không gặp vấn đề gì.

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
Trình xử lý ở trên thực hiện tất cả các tệp ghi từ tiến trình cha và chỉ sử dụng một luồng để nhận các thông điệp được truyền từ các tiến trình con. Nếu bạn gọi chính trình xử lý từ quy trình con được sinh ra thì đó là sử dụng nó không chính xác và bạn sẽ gặp tất cả các vấn đề tương tự như RotFileHandler. Tôi đã sử dụng mã trên trong nhiều năm mà không có vấn đề gì.
zzzeek

9
Thật không may, phương pháp này không hoạt động trên Windows. Từ docs.python.org/library/multiprocessing.html 16.6.2.12 "Lưu ý rằng trên tiến trình con Windows sẽ chỉ kế thừa mức độ logger quá trình cha mẹ - bất kỳ tùy biến khác của logger sẽ không được thừa hưởng." Các quy trình con sẽ không kế thừa trình xử lý và bạn không thể vượt qua nó một cách rõ ràng vì nó không thể chọn được.

2
Nó đáng chú ý rằng multiprocessing.Queuesử dụng một chủ đề để trong put(). Vì vậy, đừng gọi put(tức là đăng nhập một thông điệp bằng MultiProcessingLogtrình xử lý) trước khi tạo tất cả các quy trình con. Nếu không, các chủ đề sẽ chết trong quá trình con. Một giải pháp là gọi Queue._after_fork()khi bắt đầu mỗi tiến trình con hoặc sử dụng multiprocessing.queues.SimpleQueuethay vào đó, không liên quan đến luồng mà đang chặn.
Danqi Wang

5
Bạn có thể thêm một ví dụ đơn giản cho thấy khởi tạo, cũng như việc sử dụng từ một quá trình con giả định? Tôi không chắc chắn làm thế nào quá trình con được yêu cầu để có quyền truy cập vào hàng đợi mà không cần khởi tạo một thể hiện khác của lớp bạn.
JesseBuesking

11
@zzzeek, ​​giải pháp này tốt nhưng tôi không thể tìm thấy một gói với nó hoặc một cái gì đó tương tự vì vậy tôi đã tạo một gói được gọi multiprocessing-logging.
Javier

30

QueueHandlercó nguồn gốc trong Python 3.2+ và thực hiện chính xác điều này. Nó dễ dàng được nhân rộng trong các phiên bản trước.

Tài liệu Python có hai ví dụ hoàn chỉnh: Đăng nhập vào một tệp từ nhiều quy trình

Đối với những người sử dụng Python <3.2, chỉ cần sao chép QueueHandlervào mã của riêng bạn từ: https://gist.github.com/vsajip/591589 hoặc nhập logutils thay thế .

Mỗi quy trình (bao gồm cả quy trình cha mẹ) đăng nhập vào Queue, và sau đó một listenerluồng hoặc quy trình (một ví dụ được cung cấp cho mỗi) chọn chúng và ghi tất cả chúng vào một tệp - không có nguy cơ tham nhũng hoặc bị cắt xén.


21

Dưới đây là một giải pháp khác tập trung vào sự đơn giản cho bất kỳ ai khác (như tôi), những người đến đây từ Google. Đăng nhập nên dễ dàng! Chỉ dành cho 3,2 trở lên.

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
Các lớp QueueHandlerQueueListenercũng có thể được sử dụng trên Python 2.7, có sẵn trong logutilsgói.
Lev Levitsky

5
Trình ghi nhật ký của quy trình chính cũng nên sử dụng QueueHandler. Trong mã hiện tại của bạn, quy trình chính là bỏ qua hàng đợi để có thể có các điều kiện chạy đua giữa quy trình chính và công nhân. Mọi người nên đăng nhập vào hàng đợi (thông qua QueueHandler) và chỉ QueueListener mới được phép đăng nhập vào StreamHandler.
Ismael EL ATify

Ngoài ra, bạn không phải khởi tạo logger ở mỗi đứa trẻ. Chỉ cần khởi tạo logger trong tiến trình cha và lấy logger trong mỗi tiến trình con.
okwap

20

Một thay thế khác có thể là các trình xử lý ghi nhật ký không dựa trên tệp khác nhau trong logginggói :

  • SocketHandler
  • DatagramHandler
  • SyslogHandler

(và những người khác)

Bằng cách này, bạn có thể dễ dàng có một trình nền đăng nhập ở đâu đó mà bạn có thể viết vào một cách an toàn và sẽ xử lý kết quả chính xác. (Ví dụ: một máy chủ ổ cắm đơn giản chỉ cần giải nén tin nhắn và phát ra nó để xử lý tệp xoay vòng của chính nó.)

Việc SyslogHandlerchăm sóc này cũng sẽ giúp bạn. Tất nhiên, bạn có thể sử dụng ví dụ của riêng bạn syslogchứ không phải hệ thống.


13

Một biến thể của những cái khác giữ cho luồng đăng nhập và hàng đợi riêng biệt.

"""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)

Tôi thích một ý tưởng tìm nạp tên logger từ bản ghi hàng đợi. Điều này cho phép sử dụng thông thường fileConfig()trong MainProcess và một trình ghi nhật ký được cấu hình hầu như không có trong PoolWorkers (chỉ với setLevel(logging.NOTSET)). Như tôi đã đề cập trong một nhận xét khác, tôi đang sử dụng Pool nên tôi phải lấy Hàng đợi (proxy) từ Trình quản lý thay vì đa xử lý để có thể được xử lý. Điều này cho phép tôi chuyển hàng đợi cho một công nhân bên trong một từ điển (hầu hết trong số đó có nguồn gốc từ đối tượng không rõ ràng bằng cách sử dụng vars()). Tôi cảm thấy như cuối cùng đây là cách tiếp cận tốt nhất cho MS Windows thiếu fork () và phá vỡ giải pháp @zzzeak.
mlt

@mlt Tôi nghĩ rằng bạn cũng có thể đặt Hàng đợi đa xử lý trong init thay vì sử dụng Trình quản lý (xem câu trả lời cho stackoverflow.com/questions/25557686/ - đó là về Khóa nhưng tôi tin rằng nó cũng hoạt động cho Hàng đợi)
hãy tưởng tượng vào

@fantabolous Điều đó sẽ không hoạt động trên MS Windows hoặc bất kỳ nền tảng nào khác thiếu fork. Bằng cách đó, mỗi quá trình sẽ có hàng đợi vô dụng độc lập của riêng mình. Cách tiếp cận thứ hai trong Q / A được liên kết sẽ không hoạt động trên các nền tảng như vậy. Đó là một cách để mã không di động.
mlt

@mlt Thú vị. Tôi đang sử dụng Windows và nó có vẻ hoạt động tốt đối với tôi - không lâu sau khi tôi nhận xét lần cuối, tôi đã thiết lập một nhóm các quy trình chia sẻ multiprocessing.Queuevới quy trình chính và tôi đã sử dụng nó liên tục kể từ đó. Không yêu cầu để hiểu tại sao nó hoạt động mặc dù.
tưởng tượng

10

Tất cả các giải pháp hiện tại quá gắn liền với cấu hình ghi nhật ký bằng cách sử dụng trình xử lý. Giải pháp của tôi có kiến ​​trúc và tính năng sau:

  • Bạn có thể sử dụng bất kỳ cấu hình đăng nhập nào bạn muốn
  • Ghi nhật ký được thực hiện trong một chủ đề daemon
  • Tắt trình nền an toàn bằng cách sử dụng trình quản lý bối cảnh
  • Truyền thông đến chủ đề đăng nhập được thực hiện bởi multiprocessing.Queue
  • Trong các quy trình con, logging.Logger(và các trường hợp đã được xác định) được vá để gửi tất cả các bản ghi vào hàng đợi
  • Mới : định dạng truy nguyên và thông báo trước khi gửi đến hàng đợi để tránh lỗi tẩy

Mã với ví dụ sử dụng và đầu ra có thể được tìm thấy tại Gist sau: https://gist.github.com/schlamar/7003737


Trừ khi tôi thiếu một cái gì đó, đây thực sự không phải là một chủ đề daemon, vì bạn không bao giờ được đặt daemon_thread.daemonthành True. Tôi cần phải làm điều đó để khiến chương trình Python của tôi thoát đúng khi có ngoại lệ xảy ra trong trình quản lý bối cảnh.
blah238

Tôi cũng cần thiết để nắm bắt, ghi và nuốt ngoại lệ ném bởi mục tiêu functrong logged_call, nếu không ngoại lệ sẽ được cắt xén với sản lượng Logged khác. Đây là phiên bản sửa đổi của tôi về điều này: gist.github.com/blah238/8ab79c4fe9cdb254f5c37abfc5dc85bf
blah238

8

Vì chúng tôi có thể đại diện cho ghi nhật ký đa xử lý như nhiều nhà xuất bản và một người đăng ký (người nghe), sử dụng ZeroMQ để thực hiện nhắn tin PUB-SUB thực sự là một lựa chọn.

Hơn nữa, mô-đun PyZMQ , các ràng buộc Python cho ZMQ, thực hiện PUBHandler , là đối tượng để xuất bản thông điệp đăng nhập qua ổ cắm zmq.PUB.

Có một giải pháp trên web , để ghi nhật ký tập trung từ ứng dụng phân tán bằng PyZMQ và PUBHandler, có thể dễ dàng được chấp nhận để làm việc cục bộ với nhiều quy trình xuất bản.

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

Tôi cũng thích câu trả lời của zzzeek nhưng Andre chính xác rằng cần phải xếp hàng để tránh bị cắt xén. Tôi đã có một số may mắn với đường ống, nhưng đã thấy sự cắt xén có phần được mong đợi. Việc triển khai nó hóa ra khó hơn tôi nghĩ, đặc biệt là do chạy trên Windows, nơi có một số hạn chế bổ sung về các biến và công cụ toàn cầu (xem: Xử lý đa nhân Python được triển khai trên Windows như thế nào? )

Nhưng, cuối cùng tôi đã làm cho nó hoạt động. Ví dụ này có lẽ không hoàn hảo, vì vậy các ý kiến ​​và đề xuất đều được chào đón. Nó cũng không hỗ trợ thiết lập trình định dạng hoặc bất cứ thứ gì khác ngoài bộ ghi nhật ký gốc. Về cơ bản, bạn phải khởi động lại bộ ghi trong mỗi quy trình nhóm với hàng đợi và thiết lập các thuộc tính khác trên bộ ghi.

Một lần nữa, bất kỳ đề xuất về cách làm cho mã tốt hơn đều được chào đón. Tôi chắc chắn không biết tất cả các thủ thuật 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
Tôi tự hỏi nếu if 'MainProcess' == multiprocessing.current_process().name:có thể được sử dụng thay childthế?
mlt

Trong trường hợp người khác đang cố gắng sử dụng nhóm quy trình thay vì các đối tượng quy trình riêng biệt trên Windows, điều đáng nói là Trình quản lý sẽ được sử dụng để chuyển hàng đợi đến các quy trình con vì nó không thể chọn trực tiếp.
mlt

Việc thực hiện này đã làm việc tốt cho tôi. Tôi đã sửa đổi nó để làm việc với một số xử lý tùy ý. Bằng cách này, bạn có thể định cấu hình trình xử lý gốc của mình theo cách không đa xử lý, sau đó nơi an toàn để thực hiện hàng đợi, chuyển trình xử lý gốc đến điều này, xóa chúng và biến trình xử lý này thành trình xử lý duy nhất.
Jaxor24

3

chỉ cần xuất bản ở đâu đó ví dụ của logger. bằng cách đó, các mô-đun và ứng dụng khách khác có thể sử dụng API của bạn để lấy bộ ghi mà không cần phải import multiprocessing.


1
Vấn đề với điều này là các logger đa xử lý xuất hiện không được đặt tên, vì vậy bạn sẽ không thể giải mã luồng tin nhắn một cách dễ dàng. Có lẽ có thể đặt tên cho chúng sau khi tạo, điều này sẽ hợp lý hơn khi xem xét.
cdleary

tốt, xuất bản một logger cho mỗi mô-đun, hoặc tốt hơn, xuất các bao đóng khác nhau sử dụng bộ ghi với tên mô-đun. vấn đề là để cho các mô-đun khác sử dụng API của bạn
Javier

Chắc chắn hợp lý (và +1 từ tôi!), Nhưng tôi sẽ bỏ lỡ việc có thể import logging; logging.basicConfig(level=logging.DEBUG); logging.debug('spam!')từ bất cứ đâu và làm cho nó hoạt động đúng.
cdleary

3
Đó là một hiện tượng thú vị mà tôi thấy khi tôi sử dụng Python, rằng chúng ta đã quen với việc có thể làm những gì chúng ta muốn trong 1 hoặc 2 dòng đơn giản mà cách tiếp cận đơn giản và hợp lý trong các ngôn ngữ khác (ví dụ: để xuất bản trình ghi hoặc xử lý đa xử lý nó trong một phụ kiện) vẫn cảm thấy như một gánh nặng. :)
Kylotan

3

Tôi thích câu trả lời của zzzeek. Tôi sẽ chỉ thay thế Ống cho Hàng đợi vì nếu nhiều luồng / quy trình sử dụng cùng một đầu ống để tạo thông điệp tường trình, chúng sẽ bị cắt xén.


Tôi đã có một số vấn đề với trình xử lý, mặc dù không phải là tin nhắn bị cắt xén, nhưng tất cả mọi thứ sẽ ngừng hoạt động. Tôi đã thay đổi ống thành Hàng đợi vì điều đó phù hợp hơn. Tuy nhiên, các lỗi tôi đã không được khắc phục bằng cách đó - cuối cùng tôi đã thêm một thử / ngoại trừ phương thức receive () - rất hiếm khi, một nỗ lực để ghi lại các ngoại lệ sẽ thất bại và kết thúc ở đó. Khi tôi đã thêm thử / ngoại trừ, nó chạy trong nhiều tuần mà không gặp vấn đề gì, và một tệp tiêu chuẩn sẽ lấy khoảng hai trường hợp ngoại lệ mỗi tuần.
zzzeek

2

Làm thế nào về việc ủy ​​quyền tất cả việc ghi nhật ký cho một quy trình khác đọc tất cả các mục nhật ký từ Hàng đợi?

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()

Đơn giản chỉ cần chia sẻ LOG_QUEUE thông qua bất kỳ cơ chế đa xử lý hoặc thậm chí thừa kế và tất cả đều hoạt động tốt!


1

Tôi có một giải pháp tương tự như ironhacker, ngoại trừ việc tôi sử dụng log.exception trong một số mã của tôi và thấy rằng tôi cần định dạng ngoại lệ trước khi gửi lại hàng đợi vì Tracebacks không thể truy xuất được:

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

Tôi tìm thấy một ví dụ hoàn chỉnh dọc theo những dòng này ở đây .
Aryeh Leib Taurog

1

Dưới đây là một lớp có thể được sử dụng trong môi trường Windows, yêu cầu ActivePython. Bạn cũng có thể kế thừa cho các trình xử lý ghi nhật ký khác (StreamHandler, v.v.)

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

Và đây là một ví dụ minh chứng cho việc sử dụng:

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

Có lẽ sử dụng multiprocessing.Lock()thay vì Windows Mutex sẽ làm cho giải pháp di động.
xmedeko

1

Đây là cách hack / giải pháp đơn giản của tôi ... không phải là toàn diện nhất, nhưng dễ sửa đổi và đơn giản để đọc và hiểu tôi nghĩ hơn bất kỳ câu trả lời nào khác tôi tìm thấy trước khi viết bài này:

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

Có gói tuyệt vời này

Gói: https://pypi.python.org/pypi/multiprocessing-logging/

mã: https://github.com/jruere/multiprocessing-logging

Tải về:

pip install multiprocessing-logging

Sau đó thêm:

import multiprocessing_logging

# This enables logs inside process
multiprocessing_logging.install_mp_handler()

3
Thư viện này thực sự dựa trên một nhận xét khác về bài viết SO hiện tại: stackoverflow.com/a/894284/1698058 .
Chris Hunt

Nguồn gốc: stackoverflow.com/a/894284/1663382 Tôi đánh giá cao việc sử dụng ví dụ của mô-đun, ngoài các tài liệu trên trang chủ.
Liquidgenius

0

Một trong những lựa chọn thay thế là ghi nhật ký đột biến vào một tệp đã biết và đăng ký một atexittrình xử lý để tham gia vào các quy trình đó đọc lại trên stderr; tuy nhiên, bạn sẽ không nhận được luồng thời gian thực tới các thông báo đầu ra trên stderr theo cách đó.


là cách tiếp cận mà bạn đang đề xuất dưới đây giống hệt với cách tiếp cận từ nhận xét của bạn ở đây stackoverflow.com/questions/641420/
mẹo

0

Nếu bạn có các bế tắc xảy ra trong sự kết hợp của khóa, luồng và nhánh trong loggingmô-đun, điều đó được báo cáo trong báo cáo lỗi 6721 (xem thêm câu hỏi SO liên quan ).

Có một giải pháp sửa lỗi nhỏ được đăng ở đây .

Tuy nhiên, điều đó sẽ chỉ khắc phục bất kỳ bế tắc tiềm năng trong logging. Điều đó sẽ không khắc phục được rằng mọi thứ có thể bị cắt xén. Xem các câu trả lời khác được trình bày ở đây.


0

Ý tưởng đơn giản nhất như đã đề cập:

  • Lấy tên tệp và id tiến trình của quy trình hiện tại.
  • Thiết lập a [WatchedFileHandler][1]. Các lý do cho trình xử lý này được thảo luận chi tiết ở đây , nhưng trong ngắn hạn, có một số điều kiện chủng tộc tồi tệ hơn với các trình xử lý đăng nhập khác. Cái này có cửa sổ ngắn nhất cho điều kiện cuộc đua.
    • Chọn một đường dẫn để lưu các bản ghi vào như / var / log / ...

0

Đối với bất cứ ai có thể cần điều này, tôi đã viết một trình trang trí cho gói đa xử lý_logging thêm tên quy trình hiện tại vào nhật ký, để nó rõ ràng ai sẽ ghi nhật ký gì.

Nó cũng chạy install_mp_handler () để chạy nó trước khi tạo một pool.

Điều này cho phép tôi xem nhân viên nào tạo ra thông điệp nào.

Đây là bản thiết kế với một ví dụ:

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

Đối với những đứa trẻ của tôi gặp vấn đề tương tự trong nhiều thập kỷ và tìm thấy câu hỏi này trên trang web này, tôi để lại câu trả lời này.

Đơn giản vs quá phức tạp. Chỉ cần sử dụng các công cụ khác. Python là tuyệt vời, nhưng nó không được thiết kế để làm một số điều.

Đoạn mã sau đây cho daemon logrotate hoạt động với tôi và không quá phức tạp. Lịch trình để chạy hàng giờ và

/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
}

Đây là cách tôi cài đặt nó (symlink không hoạt động cho logrotate):

sudo cp /directpath/config/logrotate/myconfigname /etc/logrotate.d/myconfigname
sudo cp /etc/cron.daily/logrotate /etc/cron.hourly/logrotate
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.