Celery + Python logging: problems and solutions


In my current project, I had a few logging requirements for which I had to look around a bit.

First off,  Celery, by default doesn’t respect your application’s logging and redirects all the application’s existing logging to its own logger. I wanted to preserve my application’s logging and this is how I went about doing it based on the answer by Ask Sol on the celery-users list [1]. It worked great.

Second, I wanted the log file to be different everytime a new task was executed. With just the above modification, that won’t happen. Since the after_setup_task_logger signal is invoked only when Celery is starting up. For this, every time a new task request was received, I modified the existing logger handler to a newly created FileHandler.

Finally, I also wanted a way to retrieve the name of the log file which the current logger was using. From the logging documentation, I found that this was not possible. So, I took a hint from this StackOverflow question’s answer [2] and extended the FileHandler class to implement a new method to return the name of the log file.

Here is the complete tasks.py file:

from __future__ import absolute_import
import json
import os
import logging
import time
import tempfile
from celery import Celery
from celery.signals import after_setup_task_logger

class myFileHandler(logging.FileHandler):

    def __init__(self, logfile, mode):
        self.logfile = logfile 
        super(myFileHandler,self).__init__(self.logfile,mode)

    def getlogfile(self):
        return self.logfile

celery = Celery()
celery.config_from_object('celeryconfig')

# Return a filename of the form imagebuild_.log
def getfilename():
    time_now = str(time.time()).split('.')
    logfile = tempfile.gettempdir() + '/imagebuild_{0:s}.log'.format(time_now[0]+time_now[1])
    return logfile

@after_setup_task_logger.connect
def augment_celery_log(**kwargs):
    logger = logging.getLogger('imagebuilder')
    logfile = getfilename()
    handler = myFileHandler(logfile,'w')
    formatter = logging.Formatter('%(asctime)s - %(message)s')

    if not logger.handlers:
        formatter = logging.Formatter(logging.BASIC_FORMAT)
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        logger.propagate = 0

        logger.setLevel(logging.DEBUG)

@celery.task
def build(buildconfig, kickstart):

    logger = logging.getLogger('imagebuilder')
    logfile = getfilename()
    handler = myFileHandler(logfile,'w')
    formatter = logging.Formatter('%(asctime)s - %(message)s')
    handler.setFormatter(formatter)    

    # replace the handler
    logger.handlers[0] = handler

    # Your custom code
    # ..

There may be some unused imports remaining. This solution seems to work for me as of now. Note that this is for Celery 3.0.

Links:

[1] https://groups.google.com/d/topic/celery-users/xNPYTobJ5Rg/discussion

[2] http://stackoverflow.com/questions/9405890/dynamic-filepath-filename-for-filehandler-in-logger-config-file-in-python

About these ads

One thought on “Celery + Python logging: problems and solutions

  1. I was in same trouble. So It’s very very useful for me. Thanks.

    Though I encountered a trouble, I fixed it.
    I try to write the problem and my solution.

    I couldn’t execute following line in “myFileHandler” class.

    > super(myFileHandler,self).__init__(self.logfile,mode)

    error message:
    “super() argument 1 must be type, not classobj: super() argument 1 must be type, not classobj”

    “myFileHandler” class inherits “logging.FileHandler” class.
    “logging.FileHandler” class is not written in ‘new-style class’ in my environment(CentOS 6.0, python 2.6.6).
    To be exact, “Filterer” class which is inherited “FileHandler” class is not written in ‘new-style class’.
    (“Filterer” class is defined in “/usr/lib64/python2.6/logging/__init__.py” at my environment(CentOS 6.0, python 2.6.6).)

    “super()” is needed to use in ‘new-style class’.

    So I thought that “Filterer” class(“logging.FileHandler” class) is needed to write in ‘new-style class’.

    I fixed this problem like followings.
    ———
    # diff -u /usr/lib64/python2.6/logging/__init__.py.orig /usr/lib64/python2.6/logging/__init__.py
    — /usr/lib64/python2.6/logging/__init__.py.orig 2012-09-03 17:13:08.374520006 +0900
    +++ /usr/lib64/python2.6/logging/__init__.py 2012-09-03 17:13:33.957516494 +0900
    @@ -538,7 +538,8 @@
    return 0
    return (record.name[self.nlen] == “.”)

    -class Filterer:
    +#class Filterer:
    +class Filterer(object):
    “”"
    A base class for loggers and handlers which allows them to share
    common code.
    ———-

    Sorry my broken English.
    Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s