How to share a configured logger between running processes?

I’m trying to build a small GUI app with a logger that will, at some point, be doing some time consuming manipulation of multiple sets of data (up to several hundred). Naturally, I wanted to use multiprocessing to help speed things up a bit. I was following the example given in the Logging Cookbook (second example) in the Python docs and trying to figure out how to work it into my code. In this stripped down minimal example, clicking the build button should simply log a few messages. The problem, which is hopefully obvious to someone more learned in the topics at hand, is that it doesn’t work as intended. The application only prints 3 of the 5 messages to the console and exactly zero are added to the log file.

What I was expecting, obviously, was that all 5 messages would be logged with the logger instance created in gui.py.

I’ve tried merging methods, moving methods out of the class into module level functions, creating the Queue/loggers in different places, and passing the first logger instance around as an argument. Everything I’ve tried up to this point either leads to the same results or throws a pickling error and eventually ends in an EOFError. The code given is just the most recent revision that doesn’t throw an exception.

I’m just trying to get some direction on “where” I’m messing up. In case it matters, this is on Windows 10 using 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])

Note: The configure method for the logger only existed to delay the configuration until after the GUI was created so that it had access to a Text widget for a custom handler to write to.

Sharing a logger object between processes can get weird fast.

I would recommend using the QueueHandler class as you have started to do this and then use a toplevel logging. Then set all the configuration settings on the main Queue logger. This works in python 3.11.9

For example

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

OUT:

[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

NOTES:

  • Imports for the logging library are super weird between versions. Keep this in mind.
  • If you are using homemade libraries (e.g., a my_tools.py) it can be very helpful to set a logger as a global variable and allow it to inherit the configured logger from whoever is calling the tool. This requires you to set “disable loggers” to False and manually remove the ones you want. I’ve found this snippet helpful:
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
  • Since this looks like it’s in a tkinter app be wise about how you manage your mp queues. Closing processes incorrectly can lead to hangs that cause frustration as they are dependent on the shared logging queue resource.

  • Check out mCoding’s Logging tutorial. it’s very informative and describes good practices: https://youtu.be/9L77QExPmI0?si=4ggPehbVIzJoegip

=== TLDR ===

Don’t share loggers between processes. Do use Queues to pass logging messages to a main logger. Probably make a function to configure the logger and only configure and get loggers once per process.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật