Elegantes Setup der Python-Protokollierung in Django

101

Ich habe noch keine Möglichkeit gefunden, die Python-Protokollierung mit Django einzurichten, mit der ich zufrieden bin. Meine Anforderungen sind ziemlich einfach:

  • Unterschiedliche Protokollhandler für unterschiedliche Ereignisse - das heißt, ich möchte in der Lage sein, in verschiedenen Dateien zu protokollieren
  • Einfacher Zugriff auf Logger in meinen Modulen. Das Modul sollte in der Lage sein, seinen Logger mit geringem Aufwand zu finden.
  • Sollte leicht auf Befehlszeilenmodule anwendbar sein. Teile des Systems sind eigenständige Befehlszeilen- oder Dämonprozesse. Die Protokollierung sollte mit diesen Modulen leicht zu verwenden sein.

Mein aktuelles Setup besteht darin, logging.confin jedem Modul, von dem ich mich anmelde, eine Datei und eine Setup-Protokollierung zu verwenden . Es fühlt sich nicht richtig an.

Haben Sie ein Protokollierungssetup, das Ihnen gefällt? Bitte erläutern Sie Folgendes: Wie logging.confrichten Sie die Konfiguration ein (verwenden Sie sie oder richten Sie sie im Code ein), wo / wann initiieren Sie die Logger und wie erhalten Sie in Ihren Modulen Zugriff darauf usw.

Parand
quelle
1
Der folgende Screencast ist möglicherweise hilfreich: ericholscher.com/blog/2008/aug/29/… . Eine bessere Unterstützung für die Anmeldung in Django wurde von Simon Willison vorgeschlagen (siehe simonwillison.net/2009/Sep/28/ponies ).
Dominic Rodger
@Dominic Rodger - Sie können Apps in Django bereits flexibel protokollieren. Simons Vorschlag besteht hauptsächlich darin, die Protokollierung in Django-Interna zu vereinfachen. In Python wird derzeit daran gearbeitet, der Python-Protokollierung eine wörterbuchbasierte Konfiguration hinzuzufügen, von der Django profitieren kann.
Vinay Sajip

Antworten:

57

Der beste Weg, den ich bisher gefunden habe, besteht darin, das Protokollierungssetup in settings.py zu initialisieren - nirgendwo anders. Sie können entweder eine Konfigurationsdatei verwenden oder diese Schritt für Schritt programmgesteuert ausführen - dies hängt nur von Ihren Anforderungen ab. Das Wichtigste ist, dass ich normalerweise die gewünschten Handler zum Root-Logger hinzufüge, indem ich Ebenen verwende und manchmal protokolliere. Filter, um die gewünschten Ereignisse zu den entsprechenden Dateien, Konsolen, Syslogs usw. zu erhalten. Sie können natürlich Handler zu anderen Loggern hinzufügen Auch, aber meiner Erfahrung nach besteht normalerweise keine Notwendigkeit dafür.

In jedem Modul definiere ich einen Logger mit

logger = logging.getLogger(__name__)

und verwenden Sie dies zum Protokollieren von Ereignissen im Modul (und, wenn ich weiter differenzieren möchte), verwenden Sie einen Logger, der ein Kind des oben erstellten Loggers ist.

Wenn meine App möglicherweise auf einer Site verwendet wird, auf der die Anmeldung in settings.py nicht konfiguriert ist, definiere ich einen NullHandler wie folgt:

#someutils.py

class NullHandler(logging.Handler):
    def emit(self, record):
        pass

null_handler = NullHandler()

und stellen Sie sicher, dass eine Instanz davon allen Protokollierern hinzugefügt wird, die in den Modulen in meinen Apps erstellt wurden, die die Protokollierung verwenden. (Hinweis: NullHandler befindet sich bereits im Protokollierungspaket für Python 3.1 und wird in Python 2.7 enthalten sein.) Also:

logger = logging.getLogger(__name__)
logger.addHandler(someutils.null_handler)

Dies geschieht, um sicherzustellen, dass Ihre Module auf einer Site, auf der die Protokollierung in settings.py nicht konfiguriert ist, einwandfrei funktionieren und dass keine störenden Meldungen "Für Logger XYZ konnten keine Handler gefunden werden" angezeigt werden (die möglicherweise Warnungen enthalten) falsch konfigurierte Protokollierung).

Auf diese Weise erfüllen Sie Ihre angegebenen Anforderungen:

  • Sie können wie derzeit verschiedene Protokollhandler für verschiedene Ereignisse einrichten.
  • Einfacher Zugriff auf Logger in Ihren Modulen - verwenden getLogger(__name__).
  • Einfach auf Befehlszeilenmodule anwendbar - sie werden auch importiert settings.py.

Update: Beachten Sie, dass Django ab Version 1.3 jetzt die Protokollierung unterstützt .

Vinay Sajip
quelle
Muss nicht für jedes Modul ein Handler in der Konfiguration definiert sein (Sie können keinen Handler für foo verwenden, um foo.bar zu handhaben)? Sehen Sie sich das Gespräch an, das wir vor Jahren unter groups.google.com/group/comp.lang.python/browse_thread/thread/… geführt haben
Andrew Cooke
1
@andrew cooke: Sie können einen Handler verwenden foo, um Ereignisse zu verarbeiten, bei denen protokolliert wurde foo.bar. Re. Dieser Thread - sowohl fileConfig als auch dictConfig haben jetzt Optionen, um zu verhindern, dass alte Logger deaktiviert werden. Siehe diese Ausgabe: bugs.python.org/issue3136 , die einige Monate nach Ihrer Ausgabe bugs.python.org/issue2697 kam - jedenfalls wurde sie seit Juni 2008 aussortiert.
Vinay Sajip
Wäre es nicht besser zu tun, logger = someutils.getLogger(__name__)woher someutils.getLoggerder Logger logging.getLoggermit einem bereits hinzugefügten null_handler zurückkehrt?
7yl4r
1
@ 7yl4r Sie brauchen nicht jeden Logger mit einem NullHandlerhinzugefügten Logger - normalerweise nur den Logger der obersten Ebene für Ihre Pakethierarchie. Das wäre also übertrieben, IMO.
Vinay Sajip
122

Ich weiß, dass dies bereits eine gelöste Antwort ist, aber gemäß django> = 1.3 gibt es eine neue Protokollierungseinstellung.

Der Wechsel von alt zu neu erfolgt nicht automatisch, daher dachte ich, ich schreibe ihn hier auf.

Und natürlich sehen Sie sich das Django-Dokument noch einmal an.

Dies ist die grundlegende Konfiguration, die standardmäßig mit django-admin createproject v1.3 erstellt wurde. Der Kilometerstand kann sich mit den neuesten django-Versionen ändern:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        }
    }
}

Diese Struktur basiert auf der Standard- Python-Protokollierung dictConfig , die die folgenden Blöcke vorschreibt:

  • formatters - Der entsprechende Wert ist ein Diktat, bei dem jeder Schlüssel eine Formatierungs-ID ist und jeder Wert ein Diktat ist, das beschreibt, wie die entsprechende Formatierungsinstanz konfiguriert wird.
  • filters - Der entsprechende Wert ist ein Diktat, bei dem jeder Schlüssel eine Filter-ID ist und jeder Wert ein Diktat ist, das beschreibt, wie die entsprechende Filterinstanz konfiguriert wird.
  • handlers- Der entsprechende Wert ist ein Diktat, bei dem jeder Schlüssel eine Handler-ID und jeder Wert ein Diktat ist, das beschreibt, wie die entsprechende Handler-Instanz konfiguriert wird. Jeder Handler verfügt über die folgenden Schlüssel:

    • class(verpflichtend). Dies ist der vollständig qualifizierte Name der Handlerklasse.
    • level(Optional). Das Level des Handlers.
    • formatter(Optional). Die ID des Formatierers für diesen Handler.
    • filters(Optional). Eine Liste der IDs der Filter für diesen Handler.

Normalerweise mache ich mindestens Folgendes:

  • Fügen Sie eine .log-Datei hinzu
  • Konfigurieren Sie meine Apps so, dass sie in dieses Protokoll schreiben

Was bedeutet:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'null': {
            'level':'DEBUG',
            'class':'django.utils.log.NullHandler',
        },
        'console':{
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        # I always add this handler to facilitate separating loggings
        'log_file':{
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(VAR_ROOT, 'logs/django.log'),
            'maxBytes': '16777216', # 16megabytes
            'formatter': 'verbose'
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
        'apps': { # I keep all my of apps under 'apps' folder, but you can also add them one by one, and this depends on how your virtualenv/paths are set
            'handlers': ['log_file'],
            'level': 'INFO',
            'propagate': True,
        },
    },
    # you can also shortcut 'loggers' and just configure logging for EVERYTHING at once
    'root': {
        'handlers': ['console', 'mail_admins'],
        'level': 'INFO'
    },
}

bearbeiten

Siehe Anforderungsausnahmen werden jetzt immer protokolliert und Ticket Nr. 16288 :

Ich habe das obige Beispiel conf so aktualisiert, dass es explizit den richtigen Filter für mail_admins enthält, sodass standardmäßig keine E-Mails gesendet werden, wenn das Debugging True ist.

Sie sollten einen Filter hinzufügen:

'filters': {
    'require_debug_false': {
        '()': 'django.utils.log.RequireDebugFalse'
    }
},

und wenden Sie es auf den handler mail_admins an:

    'mail_admins': {
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'django.utils.log.AdminEmailHandler',
        'include_html': True,
    }

Andernfalls werden django.core.handers.base.handle_uncaught_exceptionkeine Fehler an den Logger 'django.request' übergeben, wenn settings.DEBUG True ist.

Wenn Sie dies in Django 1.5 nicht tun, erhalten Sie eine

DeprecationWarning: Im Protokollierungshandler 'mail_admins' sind keine Filter definiert: Hinzufügen eines impliziten Debug-False-Only-Filters

Aber in Django 1.4 und Django 1.5 werden die Dinge immer noch korrekt funktionieren.

** Bearbeitung beenden **

Diese conf ist stark von der Beispielkonf im Django-Dokument inspiriert, fügt jedoch den Teil der Protokolldatei hinzu.

Ich mache oft auch folgendes:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

Dann füge ich in meinem Python-Code immer einen NullHandler hinzu, falls überhaupt keine Protokollierungskonfiguration definiert ist. Dadurch werden Warnungen vermieden, für die kein Handler angegeben wurde. Besonders nützlich für Bibliotheken, die nicht unbedingt nur in Django aufgerufen werden ( ref )

import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
class NullHandler(logging.Handler): #exists in python 3.1
    def emit(self, record):
        pass
nullhandler = logger.addHandler(NullHandler())

# here you can also add some local logger should you want: to stdout with streamhandler, or to a local file...

[...]

logger.warning('etc.etc.')

Hoffe das hilft!

Stefano
quelle
Stefano, vielen Dank für die ausführliche Antwort, sehr hilfreich. Dies könnte ein Upgrade auf 1.3 lohnenswert machen.
Parand
Parand, es lohnt sich auf jeden Fall (IMHO!), Auf Django 1.3 aufzusteigen, obwohl es einige Punkte gibt, die für einen reibungslosen Übergang zu beachten sind - öffnen Sie eine neue SO-Frage, wenn Sie in Schwierigkeiten geraten ;-)
Stefano
Übrigens: Ich verwende immer noch diese Art von Einstellungen und das Dateiprotokoll, bin aber für die Produktion zum Wachposten gewechselt !
Stefano
@clime gut Ich habe versucht, es in der Antwort selbst zu erklären: für den Fall, dass überhaupt keine Protokollierungskonfiguration definiert ist. Dadurch werden Warnungen vermieden, für die kein Handler angegeben wurde. Besonders nützlich für Bibliotheken, die nicht unbedingt nur in Django (ref) aufgerufen werden
Stefano
Ich verstehe nicht, wie Sie diese Definition verwenden: 'null': {'level': 'DEBUG', 'class': 'django.utils.log.NullHandler',}
Klima
9

Wir initialisieren die Protokollierung in der obersten Ebene urls.pymithilfe einer logging.iniDatei.

Der Standort des logging.iniist in angegeben settings.py, aber das ist alles.

Jedes Modul tut es dann

logger = logging.getLogger(__name__)

Zur Unterscheidung von Test-, Entwicklungs- und Produktionsinstanzen haben wir verschiedene logging.ini-Dateien. Zum größten Teil haben wir ein "Konsolenprotokoll", das nur mit Fehlern an stderr geht. Wir haben ein "Anwendungsprotokoll", das eine reguläre fortlaufende Protokolldatei verwendet, die in ein Protokollverzeichnis verschoben wird.

S.Lott
quelle
Ich habe dies letztendlich verwendet, außer in settings.py anstelle von urls.py zu initialisieren
Parand
Wie verwenden Sie die Einstellungen von settings.py in Ihrer logging.ini-Datei? Zum Beispiel benötige ich die Einstellung BASE_DIR, damit ich feststellen kann, wo meine Protokolldateien gespeichert werden sollen.
Slypete
@slypete: Wir verwenden keine Einstellungen in der logging.ini. Da die Protokollierung weitgehend unabhängig ist, verwenden wir keine der Django-Einstellungen. Ja, es besteht die Möglichkeit, etwas zu wiederholen. Nein, es macht keinen großen praktischen Unterschied.
S.Lott
In diesem Fall würde ich bei jeder Installation meiner App eine separate logging.ini-Datei verwenden.
Slypete
@slypete: Sie haben für jede Installation eine settings.py. Sie haben auch eine logging.ini für jede Installation. Außerdem haben Sie wahrscheinlich auch für jede Installation eine Apache-Conf-Datei. Plus eine wsgi-Schnittstellendatei. Ich bin mir nicht sicher, worum es dir geht.
S.Lott
6

Ich verwende derzeit ein Protokollierungssystem, das ich selbst erstellt habe. Es verwendet das CSV-Format für die Protokollierung.

django-csvlog

Dieses Projekt hat noch keine vollständige Dokumentation, aber ich arbeite daran.

Oduvan
quelle