Django Logging, The Right Way

Buna logare este esențială pentru depanarea și rezolvarea problemelor. Nu numai că este utilă în dezvoltarea locală, dar în producție este indispensabilă. Atunci când analizăm jurnalele pentru o problemă, este rar să auzim pe cineva spunând: „Avem prea multă logare în aplicația noastră.”, dar este obișnuit să auzim contrariul. Așadar, având în vedere acest lucru, să începem.

A Crash Course in Python Loggers

În partea de sus a fiecărui fișier, ar trebui să aveți ceva de genul acesta:

import logginglogger = logging.getLogger(__name__)

__name__ va evalua la calea Python punctată a modulului, de exemplu, myproject/myapp/views.py va folosi myproject.myapp.views. Acum putem folosi acel logger în tot fișierul astfel:

# A simple string logged at the "warning" levellogger.warning("Your log message is here")# A string with a variable at the "info" levellogger.info("The value of var is %s", var)# Logging the traceback for a caught exceptiontry: function_that_might_raise_index_error()except IndexError: # equivalent to logger.error(msg, exc_info=True) logger.exception("Something bad happened")

Nota: loggerii Python se vor ocupa de inserarea variabilelor în mesajul de log dacă le treceți ca argumente în funcția de logare. Dacă jurnalul nu trebuie să fie emis, substituirea variabilelor nu va avea loc niciodată, ajutând la evitarea unei mici lovituri de performanță pentru un jurnal care nu va fi niciodată folosit.

Pregătiți generos codul dumneavoastră cu aceste instrucțiuni de logare. Iată regula de bază pe care o folosesc pentru nivelurile de jurnal:

  • debug: Informații care nu sunt necesare pentru funcționarea obișnuită, dar utile în dezvoltare.
  • info: Informații care sunt utile în timpul funcționării obișnuite.
  • warning: Informații care ar putea fi problematice, dar care nu sunt urgente.
  • error: Informații care sunt importante și care probabil necesită o atenție promptă.
  • critical: Nu mă trezesc folosind acest lucru în practică, dar dacă aveți nevoie de unul mai mare decât error, iată-l

Where to Log

Aplicația dvs. nu ar trebui să fie preocupată de locul unde se duc jurnalele sale. În schimb, ar trebui să înregistreze totul în consolă (stdout/stderr) și să lase serverul să decidă ce să facă cu el de acolo. De obicei, acest lucru este pus într-un fișier dedicat (și logrotat), capturat de jurnalul Systemd sau Docker, trimis la un serviciu separat, cum ar fi ElasticSearch, sau o combinație a acestora. Stocarea jurnalelor este o preocupare de implementare, nu o preocupare a aplicației.

Singurul lucru de care aplicația dumneavoastră trebuie să se preocupe este formatul jurnalelor. De obicei, acesta este doar un șir de caractere cu datele relevante, dar dacă serverul dvs. adaugă deja un timestamp la jurnal, probabil că doriți să îl excludeți din propriul formatter. De asemenea, dacă agregatorul dvs. de jurnale poate accepta JSON, un formator ca python-json-logger poate fi mai potrivit.

Configurarea loggerului

Scrierea în loggere în Python este ușoară. Configurarea lor pentru a merge la locul potrivit este mai dificilă decât v-ați aștepta. Voi începe prin a ocoli logarea implicită a lui Django, așa cum am descris în postarea mea anterioară. Acest lucru ne va oferi o tablă albă cu care să lucrăm.

Configurarea lui Sentry

Serviciile de raportare a erorilor sunt esențiale pentru orice site non-trivial. În mod implicit, acestea prind excepțiile neprinse, vă notifică problema (o singură dată pe incident) și oferă o interfață frumoasă pentru a vedea starea aplicației în momentul în care a apărut excepția. Serviciul meu preferat pentru acest lucru este Sentry.

Potem duce Sentry cu un pas mai departe, trimițând orice mesaj de jurnal care este warning sau mai mare la acest serviciu, de asemenea. Acestea ar fi altfel pierdute într-o mare de fișiere jurnal care, în practică, rareori sunt revizuite. Pentru a face acest lucru, vom adăuga un logger „rădăcină” care servește ca un catch-all pentru orice jurnal care este trimis de orice modul Python. Asta arată cam așa în setările Django,

import logging.configLOGGING_CONFIG = Nonelogging.config.dictConfig({ 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'console': { # exact format is not important, this is the minimum information 'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'console', }, # Add Handler for Sentry for `warning` and above 'sentry': { 'level': 'WARNING', 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', }, }, 'loggers': { # root logger '': { 'level': 'WARNING', 'handlers': , }, },})

Logging From Your Application

În timp ce s-ar putea să doriți să știți doar despre avertismente și erori de la dependențele de la terți, de obicei doriți o perspectivă mult mai profundă asupra codului aplicației dumneavoastră. În mod ideal, tot codul dvs. trăiește sub un singur spațiu de nume, astfel încât să poată fi capturat cu o singură adăugare la loggers. Să presupunem că proiectul dvs. utilizează spațiul de nume myproject, pornind de la codul de mai sus, ați adăuga următoarele:

logging.config.dictConfig({ # ... 'loggers': { '': { 'level': 'WARNING', 'handlers': , }, 'myproject': { 'level': 'INFO', 'handlers': , # required to avoid double logging with root logger 'propagate': False, }, },})

Dar dacă doriți să investigați ceva din aplicația dvs. mai în profunzime cu logarea debug? Faptul că trebuie să comiteți un cod nou și să îl implementați pare exagerat. Acesta este un caz de utilizare excelent pentru variabilele de mediu. Putem modifica strofa anterioară pentru a arăta astfel:

import osLOGLEVEL = os.environ.get('LOGLEVEL', 'info').upper()logging.config.dictConfig({ # ... 'loggers': { '': { 'level': 'WARNING', 'handlers': , }, 'myproject': { 'level': LOGLEVEL, 'handlers': , # required to avoid double logging with root logger 'propagate': False, }, },})

Acum jurnalizarea aplicației noastre va fi implicit la info, dar poate fi ușor de crescut temporar prin setarea variabilei de mediu LOGLEVEL=debug. Alternativ, dacă stocarea jurnalelor nu este o problemă, luați în considerare posibilitatea de a înregistra întotdeauna la nivelul debug. Acestea sunt destul de ușor de filtrat cu un simplu grep sau prin intermediul instrumentului dvs. de vizualizare a jurnalelor, de exemplu Kibana.

Cutting out the Noise

După ce ați configurat și ați pus în funcțiune jurnalizarea, s-ar putea să descoperiți că unele module înregistrează informații de care nu vă pasă cu adevărat și care servesc doar la crearea unui zgomot suplimentar (mă uit la dvs. newrelic). Pentru aceste module, putem adăuga un alt logger pentru a le elimina. Prima opțiune este să le înregistrăm în consolă, dar să evităm propagarea lor la loggerul rădăcină care le-ar trimite la Sentry:

logging.config.dictConfig({ # ... 'loggers': { '': { 'level': 'WARNING', 'handlers': , }, 'myproject': { 'level': LOGLEVEL, 'handlers': , # required to avoid double logging with root logger 'propagate': False, }, # Don't send this module's logs to Sentry 'noisy_module': { 'level':'ERROR', 'handlers': , 'propagate': False, }, },})

Dacă găsiți că sunt prea zgomotoase, chiar și pentru consolă, putem renunța la ele cu totul:

logging.config.dictConfig({ # ... 'loggers': { # ... # Don't log this module at all 'noisy_module': { 'level': 'NOTSET', 'propagate': False, }, },})

Local Request Logging

O bunătate în configurația implicită a lui Django este înregistrarea cererilor cu runserver. Suprascriind configurația lui Django, o pierdem, dar este destul de ușor să o adăugăm înapoi:

from django.utils.log import DEFAULT_LOGGINGlogging.config.dictConfig({ # ... 'formatters': { # ... 'django.server': DEFAULT_LOGGING, }, 'handlers': { # ... 'django.server': DEFAULT_LOGGING, }, 'loggers': { # ... 'django.server': DEFAULT_LOGGING, },})

.

Leave a Reply