Configurazione elegante della registrazione di Python in Django


101

Devo ancora trovare un modo per impostare la registrazione di Python con Django di cui sono soddisfatto. I miei requisiti sono abbastanza semplici:

  • Gestori di log diversi per eventi diversi, ovvero voglio essere in grado di accedere a file diversi
  • Facile accesso ai logger nei miei moduli. Il modulo dovrebbe essere in grado di trovare il suo logger con poco sforzo.
  • Dovrebbe essere facilmente applicabile ai moduli della riga di comando. Parti del sistema sono processi da riga di comando o daemon autonomi. La registrazione dovrebbe essere facilmente utilizzabile con questi moduli.

La mia configurazione attuale prevede l'utilizzo di un logging.conffile e la registrazione della configurazione in ogni modulo da cui accedo. Non mi sembra giusto.

Hai una configurazione di registrazione che ti piace? Si prega di descriverlo in dettaglio: come si imposta la configurazione (la si utilizza logging.confo la si imposta in codice), dove / quando si iniziano i logger e come si accede ad essi nei moduli, ecc.


1
Potresti trovare utile il seguente screencast: ericholscher.com/blog/2008/aug/29/… . Inoltre, un migliore supporto per il login in Django è stato proposto da Simon Willison (vedi simonwillison.net/2009/Sep/28/ponies ).
Dominic Rodger

@Dominic Rodger - Puoi già eseguire la registrazione flessibile delle app in Django, proposta di Simon principalmente per facilitare la registrazione negli interni di Django. C'è del lavoro in corso in Python per aggiungere una configurazione basata su dizionario alla registrazione di Python, da cui Django potrebbe trarre vantaggio.
Vinay Sajip

Risposte:


57

Il modo migliore che ho trovato finora è inizializzare la configurazione della registrazione in settings.py, da nessun'altra parte. Puoi utilizzare un file di configurazione o farlo in modo programmatico passo dopo passo: dipende solo dalle tue esigenze. La cosa fondamentale è che di solito aggiungo i gestori che desidero al logger di root, usando i livelli e talvolta i logging.Filtri per ottenere gli eventi che desidero nei file, console, syslog appropriati ecc. Ovviamente puoi aggiungere gestori a qualsiasi altro logger anche, ma comunemente non ce n'è bisogno nella mia esperienza.

In ogni modulo, definisco un logger usando

logger = logging.getLogger(__name__)

e usalo per registrare gli eventi nel modulo (e, se voglio differenziare ulteriormente) usa un logger che è un figlio del logger creato sopra.

Se la mia app verrà potenzialmente utilizzata in un sito che non configura l'accesso in settings.py, definisco un NullHandler da qualche parte come segue:

#someutils.py

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

null_handler = NullHandler()

e assicurati che un'istanza di esso venga aggiunta a tutti i logger creati nei moduli nelle mie app che utilizzano la registrazione. (Nota: NullHandler è già nel pacchetto di registrazione per Python 3.1 e sarà in Python 2.7.) Quindi:

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

Questo viene fatto per assicurarti che i tuoi moduli funzionino bene in un sito che non configura l'accesso in settings.py, e che tu non riceva alcun fastidioso messaggio "Impossibile trovare gestori per il logger XYZ" (che sono avvertimenti sui potenziali registrazione errata).

In questo modo si soddisfano i requisiti dichiarati:

  • Puoi impostare diversi gestori di log per eventi diversi, come fai attualmente.
  • Facile accesso ai logger nei tuoi moduli: usa getLogger(__name__).
  • Facilmente applicabile ai moduli della riga di comando: importano anche settings.py.

Aggiornamento: si noti che a partire dalla versione 1.3, Django ora incorpora il supporto per la registrazione .


Non sarà necessario che ogni modulo abbia un gestore definito nella configurazione (non è possibile utilizzare un gestore per foo per gestire foo.bar)? Guarda la conversazione che abbiamo avuto anni fa su groups.google.com/group/comp.lang.python/browse_thread/thread/…
andrew cooke

1
@andrew cooke: puoi utilizzare un gestore per foogestire gli eventi registrati foo.bar. Ri. quel thread - sia fileConfig che dictConfig ora hanno opzioni per impedire la disabilitazione dei vecchi logger. Vedi questo problema: bugs.python.org/issue3136 , che è arrivato un paio di mesi dopo il tuo problema bugs.python.org/issue2697 - in ogni caso, è stato risolto da giugno 2008.
Vinay Sajip

non sarebbe meglio fare da logger = someutils.getLogger(__name__)dove someutils.getLoggerrestituisce il logger logging.getLoggercon un null_handler già aggiunto?
7yl4r

1
@ 7yl4r Non è necessario che ogni logger abbia un logger NullHandleraggiunto, di solito solo il logger di livello superiore per la gerarchia dei pacchetti. Quindi sarebbe eccessivo, IMO.
Vinay Sajip

122

So che questa è già una risposta risolta, ma secondo django> = 1.3 c'è una nuova impostazione di registrazione.

Passare dal vecchio al nuovo non è automatico, quindi ho pensato di scriverlo qui.

E ovviamente controlla il django doc per ulteriori informazioni.

Questa è la configurazione di base, creata per impostazione predefinita con django-admin createproject v1.3 - il chilometraggio potrebbe cambiare con le ultime versioni di django:

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,
        }
    }
}

Questa struttura si basa sullo standard di registrazione Python dictConfig , che detta i seguenti blocchi:

  • formatters - il valore corrispondente sarà un dict in cui ogni chiave è un id del formattatore e ogni valore è un dict che descrive come configurare l'istanza del formattatore corrispondente.
  • filters - il valore corrispondente sarà un dict in cui ogni chiave è un id del filtro e ogni valore è un dict che descrive come configurare l'istanza del filtro corrispondente.
  • handlers- il valore corrispondente sarà un dict in cui ogni chiave è un id del gestore e ogni valore è un dict che descrive come configurare l'istanza del gestore corrispondente. Ogni gestore ha le seguenti chiavi:

    • class(obbligatorio). Questo è il nome completo della classe gestore.
    • level(opzionale). Il livello del conduttore.
    • formatter(opzionale). L'id del formattatore per questo gestore.
    • filters(opzionale). Un elenco di ID dei filtri per questo gestore.

Di solito faccio almeno questo:

  • aggiungi un file .log
  • configurare le mie app per scrivere in questo registro

Che si traduce in:

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'
    },
}

modificare

Vedi le eccezioni alle richieste ora sono sempre registrate e il biglietto n. 16288 :

Ho aggiornato la configurazione di esempio sopra per includere esplicitamente il filtro corretto per mail_admins in modo che, per impostazione predefinita, le e-mail non vengano inviate quando il debug è True.

Dovresti aggiungere un filtro:

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

e applicalo al gestore mail_admins:

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

Altrimenti django.core.handers.base.handle_uncaught_exceptionnon passa gli errori al logger "django.request" se settings.DEBUG è True.

Se non lo fai in Django 1.5 otterrai un file

Avviso: non sono stati definiti filtri nel gestore di registrazione "mail_admins": aggiunta di un filtro di solo debug implicito

ma le cose continueranno a funzionare correttamente SIA in django 1.4 che in django 1.5.

** fine modifica **

Questa configurazione è fortemente ispirata all'esempio di configurazione nel documento django, ma aggiungendo la parte del file di registro.

Spesso faccio anche quanto segue:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

Quindi nel mio codice Python aggiungo sempre un NullHandler nel caso in cui non sia definita alcuna configurazione di registrazione. Questo evita avvisi per nessun gestore specificato. Particolarmente utile per librerie che non sono necessariamente chiamate solo in Django ( 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.')

Spero che questo ti aiuti!


Stefano, molte grazie per la risposta dettagliata, molto disponibile. Questo potrebbe rendere utile l'aggiornamento alla 1.3.
Parand

Parand, è sicuramente (IMHO!) Vale la pena passare a django 1.3, anche se ci sono alcuni punti di cui occuparsi per una transizione graduale: apri una nuova domanda SO se ti trovi nei guai ;-)
Stefano

a proposito: uso ancora questo tipo di impostazioni e il log dei file, ma sono passato alla sentinella per la produzione!
Stefano

@clime bene ho provato a spiegarlo nella risposta stessa: nel caso in cui non sia stata definita alcuna conf di registrazione. Questo evita avvisi per nessun gestore specificato. Particolarmente utile per librerie che non sono necessariamente chiamate solo in Django (ref)
Stefano

Non vedo come usi questa definizione: 'null': {'level': 'DEBUG', 'class': 'django.utils.log.NullHandler',}
clime

9

Inizializziamo la registrazione nel livello principale urls.pyutilizzando un logging.inifile.

La posizione del logging.iniè fornita in settings.py, ma questo è tutto.

Ogni modulo quindi fa

logger = logging.getLogger(__name__)

Per distinguere le istanze di test, sviluppo e produzione, abbiamo diversi file logging.ini. Per la maggior parte, abbiamo un "log della console" che va a stderr solo con errori. Abbiamo un "registro dell'applicazione" che utilizza un normale file di registro a rotazione che va a una directory dei registri.


Ho finito per usarlo, tranne l'inizializzazione in settings.py invece di urls.py
Parand

Come usi le impostazioni da settings.py nel tuo file logging.ini? Ad esempio, ho bisogno dell'impostazione BASE_DIR, così posso dirgli dove memorizzare i miei file di registro.
slypete

@slypete: non usiamo le impostazioni nel logging.ini. Poiché la registrazione è in gran parte indipendente, non usiamo nessuna delle impostazioni di Django. Sì, c'è la possibilità di ripetere qualcosa. No, non fa molta differenza pratica.
S.Lott

In tal caso, creerei un file logging.ini separato in ogni installazione della mia app.
slypete

@slypete: hai un settings.py per ogni installazione. Hai anche un logging.ini per ogni installazione. Inoltre, probabilmente hai anche un file di configurazione di Apache per ogni installazione. Inoltre un file di interfaccia WSGI. Non sono sicuro di quale sia il tuo punto.
S.Lott

6

Attualmente sto utilizzando un sistema di registrazione, che ho creato io stesso. Utilizza il formato CSV per la registrazione.

django-csvlog

Questo progetto non ha ancora la documentazione completa, ma ci sto lavorando.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.