Created
April 7, 2012 18:45
-
-
Save vsajip/2331314 to your computer and use it in GitHub Desktop.
Example of logging and multiprocessing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import logging | |
import logging.config | |
import logging.handlers | |
from multiprocessing import Process, Queue, Event, current_process | |
import os | |
import random | |
import time | |
class MyHandler(object): | |
""" | |
A simple handler for logging events. It runs in the listener process and | |
dispatches events to loggers based on the name in the received record, | |
which then get dispatched, by the logging system, to the handlers | |
configured for those loggers. | |
""" | |
def handle(self, record): | |
logger = logging.getLogger(record.name) | |
# The process name is transformed just to show that it's the listener | |
# doing the logging to files and console | |
record.processName = '%s (for %s)' % (current_process().name, record.processName) | |
logger.handle(record) | |
def listener_process(q, stop_event, config): | |
""" | |
This could be done in the main process, but is just done in a separate | |
process for illustrative purposes. | |
This initialises logging according to the specified configuration, | |
starts the listener and waits for the main process to signal completion | |
via the event. The listener is then stopped, and the process exits. | |
""" | |
logging.config.dictConfig(config) | |
listener = logging.handlers.QueueListener(q, MyHandler()) | |
listener.start() | |
if os.name == 'posix': | |
# On POSIX, the setup logger will have been configured in the | |
# parent process, but should have been disabled following the | |
# dictConfig call. | |
# On Windows, since fork isn't used, the setup logger won't | |
# exist in the child, so it would be created and the message | |
# would appear - hence the "if posix" clause. | |
logger = logging.getLogger('setup') | |
logger.critical('Should not appear, because of disabled logger ...') | |
stop_event.wait() | |
listener.stop() | |
def worker_process(config): | |
""" | |
A number of these are spawned for the purpose of illustration. In | |
practice, they could be a heterogenous bunch of processes rather than | |
ones which are identical to each other. | |
This initialises logging according to the specified configuration, | |
and logs a hundred messages with random levels to randomly selected | |
loggers. | |
A small sleep is added to allow other processes a chance to run. This | |
is not strictly needed, but it mixes the output from the different | |
processes a bit more than if it's left out. | |
""" | |
logging.config.dictConfig(config) | |
levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, | |
logging.CRITICAL] | |
loggers = ['foo', 'foo.bar', 'foo.bar.baz', | |
'spam', 'spam.ham', 'spam.ham.eggs'] | |
if os.name == 'posix': | |
# On POSIX, the setup logger will have been configured in the | |
# parent process, but should have been disabled following the | |
# dictConfig call. | |
# On Windows, since fork isn't used, the setup logger won't | |
# exist in the child, so it would be created and the message | |
# would appear - hence the "if posix" clause. | |
logger = logging.getLogger('setup') | |
logger.critical('Should not appear, because of disabled logger ...') | |
for i in range(100): | |
lvl = random.choice(levels) | |
logger = logging.getLogger(random.choice(loggers)) | |
logger.log(lvl, 'Message no. %d', i) | |
time.sleep(0.01) | |
def main(): | |
q = Queue() | |
# The main process gets a simple configuration which prints to the console. | |
config_initial = { | |
'version': 1, | |
'formatters': { | |
'detailed': { | |
'class': 'logging.Formatter', | |
'format': '%(asctime)s %(name)-15s %(levelname)-8s %(processName)-10s %(message)s' | |
} | |
}, | |
'handlers': { | |
'console': { | |
'class': 'logging.StreamHandler', | |
'level': 'INFO', | |
}, | |
}, | |
'root': { | |
'level': 'DEBUG', | |
'handlers': ['console'] | |
}, | |
} | |
# The worker process configuration is just a QueueHandler attached to the | |
# root logger, which allows all messages to be sent to the queue. | |
# We disable existing loggers to disable the "setup" logger used in the | |
# parent process. This is needed on POSIX because the logger will | |
# be there in the child following a fork(). | |
config_worker = { | |
'version': 1, | |
'disable_existing_loggers': True, | |
'handlers': { | |
'queue': { | |
'class': 'logging.handlers.QueueHandler', | |
'queue': q, | |
}, | |
}, | |
'root': { | |
'level': 'DEBUG', | |
'handlers': ['queue'] | |
}, | |
} | |
# The listener process configuration shows that the full flexibility of | |
# logging configuration is available to dispatch events to handlers however | |
# you want. | |
# We disable existing loggers to disable the "setup" logger used in the | |
# parent process. This is needed on POSIX because the logger will | |
# be there in the child following a fork(). | |
config_listener = { | |
'version': 1, | |
'disable_existing_loggers': True, | |
'formatters': { | |
'detailed': { | |
'class': 'logging.Formatter', | |
'format': '%(asctime)s %(name)-15s %(levelname)-8s %(processName)-10s %(message)s' | |
}, | |
'simple': { | |
'class': 'logging.Formatter', | |
'format': '%(name)-15s %(levelname)-8s %(processName)-10s %(message)s' | |
} | |
}, | |
'handlers': { | |
'console': { | |
'class': 'logging.StreamHandler', | |
'level': 'INFO', | |
'formatter': 'simple', | |
}, | |
'file': { | |
'class': 'logging.FileHandler', | |
'filename': 'mplog.log', | |
'mode': 'w', | |
'formatter': 'detailed', | |
}, | |
'foofile': { | |
'class': 'logging.FileHandler', | |
'filename': 'mplog-foo.log', | |
'mode': 'w', | |
'formatter': 'detailed', | |
}, | |
'errors': { | |
'class': 'logging.FileHandler', | |
'filename': 'mplog-errors.log', | |
'mode': 'w', | |
'level': 'ERROR', | |
'formatter': 'detailed', | |
}, | |
}, | |
'loggers': { | |
'foo': { | |
'handlers' : ['foofile'] | |
} | |
}, | |
'root': { | |
'level': 'DEBUG', | |
'handlers': ['console', 'file', 'errors'] | |
}, | |
} | |
# Log some initial events, just to show that logging in the parent works | |
# normally. | |
logging.config.dictConfig(config_initial) | |
logger = logging.getLogger('setup') | |
logger.info('About to create workers ...') | |
workers = [] | |
for i in range(5): | |
wp = Process(target=worker_process, name='worker %d' % (i + 1), | |
args=(config_worker,)) | |
workers.append(wp) | |
wp.start() | |
logger.info('Started worker: %s', wp.name) | |
logger.info('About to create listener ...') | |
stop_event = Event() | |
lp = Process(target=listener_process, name='listener', | |
args=(q, stop_event, config_listener)) | |
lp.start() | |
logger.info('Started listener') | |
# We now hang around for the workers to finish their work. | |
for wp in workers: | |
wp.join() | |
# Workers all done, listening can now stop. | |
# Logging in the parent still works normally. | |
logger.info('Telling listener to stop ...') | |
stop_event.set() | |
lp.join() | |
logger.info('All done.') | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Something similar that only works on POSIX systems was packaged in https://pypi.python.org/pypi/multiprocessing-logging/