Python logging in Django
Asked Answered
M

11

40

I'm developing a Django app, and I'm trying to use Python's logging module for error/trace logging. Ideally I'd like to have different loggers configured for different areas of the site. So far I've got all of this working, but one thing has me scratching my head.

I have the root logger going to sys.stderr, and I have configured another logger to write to a file. This is in my settings.py file:

sviewlog = logging.getLogger('MyApp.views.scans')
view_log_handler = logging.FileHandler('C:\\MyApp\\logs\\scan_log.log')
view_log_handler.setLevel(logging.INFO)
view_log_handler.setFormatter(logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s'))
sviewlog.addHandler(view_log_handler)

Seems pretty simple. Here's the problem, though: whatever I write to the sviewlog gets written to the log file twice. The root logger only prints it once. It's like addHandler() is being called twice. And when I put my code through a debugger, this is exactly what I see. The code in settings.py is getting executed twice, so two FileHandlers are created and added to the same logger instance. But why? And how do I get around this?

Can anyone tell me what's going on here? I've tried moving the sviewlog logger/handler instantiation code to the file where it's used (since that actually seems like the appropriate place to me), but I have the same problem there. Most of the examples I've seen online use only the root logger, and I'd prefer to have multiple loggers.

Monstrous answered 4/12, 2008 at 23:24 Comment(0)
M
30

Allow me to answer my own question. The underlying problem here is that settings.py gets imported twice, or maybe even more (See here). (I still don't understand why this is. Maybe some Django expert could explain that to me.) This seems to be true of some other modules as well. At this point I don't think it's wise to make assumptions about how many times settings.py will be imported. For that matter, such assumptions aren't safe in general. I've had this code in places other than settings.py, and the results are similar.

You have to code around this. That is, you have to check your logger for existing handlers before adding additional handlers to it. This is a bit ugly because it's perfectly reasonable to have multiple handlers -- even of the same type -- attached to one logger. There are a few solutions to dealing with this. One is check the handlers property of your logger object. If you only want one handler and your length > 0, then don't add it. Personally I don't love this solution, because it gets messy with more handlers.

I prefer something like this (thanks to Thomas Guettler):

# file logconfig.py
if not hasattr(logging, "set_up_done"):
    logging.set_up_done=False

def set_up(myhome):
    if logging.set_up_done:
        return
    # set up your logging here
    # ...
    logging.set_up_done=True

I must say, I wish the fact that Django imports settings.py multiple times were better documented. And I would imagine that my configuration is somehow cause this multiple import, but I'm having trouble finding out what is causing the problem and why. Maybe I just couldn't find that in their documents, but I would think that's the sort of thing you need to warn your users about.

Monstrous answered 6/12, 2008 at 1:6 Comment(3)
I'm no Django expert, but I imagine that settings.py would get imported each time a module is loaded that has an import statement with "settings" in it. The more modules that use settings.py, the more times it's imported.Saxena
@HartleyBrody python caches modules as they are loaded, so usually a given module is executed only once, regardless of the number of import statements in which it appears.Replenish
According to the Django 1.4 release notes this issue may have been addressed.Replenish
R
24

As of version 1.3, Django uses standard python logging, configured with the LOGGING setting (documented here: 1.3, dev).

Django logging reference: 1.3, dev.

Raptorial answered 20/10, 2010 at 23:23 Comment(0)
S
14

Difficult to comment on your specific case. If settings.py is executed twice, then it's normal that you get two lines for every log sent.

We had the same issue, so we set it up in our projects to have one module dedicated to logging. That modules has a "module singleton" pattern, so that we only execute the interesting code once.

It looks like this:

def init_logging():
    stdoutHandler = logging.StreamHandler( sys.stdout )
    stdoutHandler.setLevel( DEBUG )
    stdoutHandler.setFormatter( logging.Formatter( LOG_FORMAT_WITH_TIME ) )
    logging.getLogger( LOG_AREA1 ).addHandler( stdoutHandler )

logInitDone=False #global variable controlling the singleton.
if not logInitDone:
    logInitDone = True
    init_logging()

Importing the log.py the first time will configure the logging correctly.

Soricine answered 5/12, 2008 at 11:35 Comment(5)
Am i right - this way you have to import log, logging than write smthng like logger = logging.getLogger(LOG_AREA1) and only then use it like logger.info(bla-bla-bla)? Is it better to return logger from init_logging() (return logging.getLogger...) and set it to module var? Say logger = init_logging(). This way you can import log.py and just start use it import log; log.logger.info(bla-bla-bla)?Cirrhosis
Oh! And of course you can use module level logger if you want to: import log; module_logger = log.logger.getLogger(__name__)Cirrhosis
This whole logInitDone stuff is useless. Look at it, when executed twice logInitDone is always set back to False so init_logging() would still be called twice. This only works because the module is only ever executed once while imported as explained in this answer.Scavenger
@RickyA, you are right in the generic case of python programming but wrong in the case of the question asked. When using django, I don't know why but settings.py is executed twice in the same program space. Hence the need to protect it with a global guard.Soricine
This can be followed in Django's modular structure. How to use it in Django's generic views?Bristling
C
11

Reviving an old thread, but I was experiencing duplicate messages while using Django 1.3 Python logging with the dictConfig format.

The disable_existing_loggers gets rid of the duplicate handler/logging problem with multiple settings.py loads, but you can still get duplicate log messages if you don't specify the propagate boolean appropriately on the specific logger. Namely, make sure you set propagate=False for child loggers. E.g.,

'loggers': {
    'django': {
        'handlers':['null'],
        'propagate': True,
        'level':'INFO',
    },
    'django.request': {
        'handlers': ['console'],
        'level': 'ERROR',
        'propagate': False,
    },
    'project': {
        'handlers': ['console', 'project-log-file'],
        'level': 'DEBUG',
        'propagate': True,
    },
    'project.customapp': {
        'handlers': ['console', 'customapp-log-file'],
        'level': 'DEBUG',
        'propagate': False,
    },
}

Here, project.customapp sets propagate=False so that it won't be caught by the project logger as well. The Django logging docs are excellent, as always.

Christman answered 4/5, 2011 at 21:9 Comment(2)
This applies to Django 1.4 as well.Armalda
Likewise for 1.6> BTW, thanks for the propagate=False tip, that solved my problem.Attachment
S
6

To answer the question about why does "Django imports settings.py multiple times": it does not.

Actually, it does get imported twice (skip past the first code chunk to get right into it but a good read if you've got the time):

http://blog.dscpl.com.au/2010/03/improved-wsgi-script-for-use-with.html

PS– Sorry to revive an old thread.

Sculpsit answered 17/4, 2010 at 6:12 Comment(0)
E
3

You could get around your problem by checking for the number of handlers when you are doing your init.

def init_logging():
    stdoutHandler = logging.StreamHandler( sys.stdout )
    stdoutHandler.setLevel( DEBUG )
    stdoutHandler.setFormatter( logging.Formatter( LOG_FORMAT_WITH_TIME ) )
    logger = logging.getLogger( LOG_AREA1 )
    if len(logger.handlers) < 1:
        logger.addHandler( stdoutHandler )

I don't think this is a great way to handle it. Personally, for logging in django with the python logging module, I create a logger in views.py for each application I'm interested in, then grab the logger in each view function.

from django.http import HttpResponse
from magic import makeLogger
from magic import getLogger

makeLogger('myLogName', '/path/to/myLogName.log')
def testLogger(request):
    logger = getLogger('myLogName')
    logger.debug('this worked')
    return HttpResponse('TEXT, HTML or WHATEVER')

This is a pretty good article about debugging django and covers some logging: http://simonwillison.net/2008/May/22/debugging/

Embraceor answered 6/12, 2008 at 16:55 Comment(0)
D
3

To answer the question about why does "Django imports settings.py multiple times": it does not.

You are probably running a multiprocess/multithread web server which creates several python sub-interpreters, where each of those imports the code from your django app once.

Test that on the django test server and you should see that settings are not imported many times.

Some time ago, I had designed a nice singleton (python borg idiom version to be more precise) with my first django/apache app, before I quickly realised that yes, I had more than one instances of my singleton created...

Dira answered 8/7, 2009 at 16:5 Comment(0)
G
3

You can also use a run-once Middleware to get a similar effect, without the private variables. Note that this will only configure the logging for web-requests - you'll need to find a different solution if you want logging in your shell or command runs.

from django.conf import settings
from django.core.exceptions import MiddlewareNotUsed
import logging
import logging.handlers
import logging.config

__all__ = ('LoggingConfigMiddleware',)


class LoggingConfigMiddleware:
    def __init__(self):
        '''Initialise the logging setup from settings, called on first request.'''
        if hasattr(settings, 'LOGGING'):
            logging.config.dictConfig(settings.LOGGING)
        elif getattr(settings, 'DEBUG', False):
            print 'No logging configured.'
        raise MiddlewareNotUsed('Logging setup only.')
Gondar answered 8/2, 2011 at 12:43 Comment(0)
X
1

Why using python logger instead of django-logging? Give it a try it might just solve your problem.

http://code.google.com/p/django-logging/wiki/Overview

At the moment it would only allow to view the root logger, but you can sure write to multiple loggers.

Xyloid answered 5/12, 2008 at 0:21 Comment(2)
As I understand it, django-logging doesn't have anything to do with logging to separate files. Its primary use is to output log messages on your app pages, which is not a feature I need at this point. So I can't think of why it would solve my problem.Monstrous
django-logging no longer maintained. There is a recommendation on the project page to use github.com/robhudson/django-debug-toolbar for client-side loggingReduced
W
0

A hackish way, but you can try to put the logging code inside an admin.py. It is supposed to be imported only once.

Alternatively; can you first check if MyApp.views.scans log exists? If it exists (maybe an error is raised) you can simply skip creation (and therefore not add the handler again). A cleaner way but I haven't tried this though.

Also there must be a more appropriate place to put this code (__init__.py?). settings.py is for settings.

Winfrid answered 5/12, 2008 at 12:42 Comment(0)
C
0

To add to A Lee post, python logging documentation states this about propagate:

Logger.propagate

If this evaluates to false, logging messages are not passed by this logger or by its child loggers to the handlers of higher level (ancestor) loggers. The constructor sets this attribute to 1.

This means that if propagate == False then child logger will NOT pass logging message to its parent logger

Casto answered 5/10, 2011 at 8:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.