Elegancka konfiguracja logowania Pythona w Django

101

Nie znalazłem jeszcze sposobu na skonfigurowanie logowania w Pythonie za pomocą Django, z którego jestem zadowolony. Moje wymagania są dość proste:

  • Różne programy obsługi dziennika dla różnych zdarzeń - to znaczy chcę mieć możliwość logowania się do różnych plików
  • Łatwy dostęp do rejestratorów w moich modułach. Moduł powinien być w stanie znaleźć swój rejestrator przy niewielkim wysiłku.
  • Powinien być łatwy do zastosowania w modułach wiersza poleceń. Części systemu to samodzielne procesy wiersza poleceń lub demonów. Rejestrowanie powinno być łatwe w użyciu z tymi modułami.

Moja obecna konfiguracja polega na używaniu logging.confpliku i rejestrowania ustawień w każdym module, z którego się loguję. To nie jest w porządku.

Czy masz konfigurację logowania, którą lubisz? Proszę szczegółowo: jak logging.confustawiasz konfigurację (używasz lub ustawiasz to w kodzie), gdzie / kiedy inicjujesz loggery i jak uzyskujesz do nich dostęp w swoich modułach itp.

Parand
źródło
1
Przydatny może być następujący screencast - ericholscher.com/blog/2008/aug/29/… . Również lepszą obsługę logowania w Django zaproponował Simon Willison (patrz simonwillison.net/2009/Sep/28/ponies ).
Dominic Rodger
@Dominic Rodger - Możesz już robić elastyczne logowanie aplikacji w Django, propozycja Simona głównie dla ułatwienia logowania do wewnętrznych elementów Django. W Pythonie trwają prace nad dodaniem konfiguracji opartej na słownikach do rejestrowania w Pythonie, z czego może skorzystać Django.
Vinay Sajip

Odpowiedzi:

58

Najlepszym sposobem, jaki znalazłem do tej pory, jest zainicjowanie konfiguracji logowania w settings.py - nigdzie indziej. Możesz użyć pliku konfiguracyjnego lub zrobić to programowo krok po kroku - zależy to tylko od Twoich wymagań. Najważniejsze jest to, że zazwyczaj dodaję programy obsługi, które chcę, do głównego loggera, używając poziomów, a czasem logowania. Filtry, aby pobrać zdarzenia, które chcę, do odpowiednich plików, konsoli, syslogów itp. Możesz oczywiście dodać programy obsługi do innych rejestratorów też, ale z mojego doświadczenia wynika, że ​​nie ma takiej potrzeby.

W każdym module definiuję rejestrator za pomocą

logger = logging.getLogger(__name__)

i używaj tego do rejestrowania zdarzeń w module (a jeśli chcę dalej różnicować) użyj loggera, który jest dzieckiem loggera utworzonego powyżej.

Jeśli moja aplikacja ma być potencjalnie używana w witrynie, która nie konfiguruje logowania settings.py, definiuję NullHandler gdzieś w następujący sposób:

#someutils.py

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

null_handler = NullHandler()

i upewnij się, że jego wystąpienie jest dodawane do wszystkich rejestratorów utworzonych w modułach moich aplikacji, które używają rejestrowania. (Uwaga: NullHandler jest już w pakiecie rejestrowania dla Pythona 3.1 i będzie w Pythonie 2.7.) Więc:

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

Ma to na celu zapewnienie, że twoje moduły będą dobrze działać w witrynie, która nie konfiguruje logowania settings.py, i że nie otrzymasz żadnych irytujących komunikatów "Nie znaleziono programów obsługi dla rejestratora XYZ" (które są ostrzeżeniami o potencjalnie źle skonfigurowane logowanie).

Zrobienie tego w ten sposób spełnia określone wymagania:

  • Możesz skonfigurować różne programy obsługi dziennika dla różnych zdarzeń, tak jak obecnie.
  • Łatwy dostęp do rejestratorów w Twoich modułach - użyj getLogger(__name__).
  • Łatwe do zastosowania w modułach wiersza poleceń - również importują settings.py.

Aktualizacja: Zauważ, że od wersji 1.3, Django zawiera teraz obsługę logowania .

Vinay Sajip
źródło
Czy nie będzie to wymagało, aby każdy moduł miał zdefiniowaną procedurę obsługi w konfiguracji (nie można użyć funkcji obsługi dla foo do obsługi foo.bar)? Zobacz rozmowę, którą odbyliśmy lata temu na groups.google.com/group/comp.lang.python/browse_thread/thread/ ...
andrew cooke
1
@andrew Cooke: Użytkownik może użyć do obsługi foodo zdarzenia uchwyt rejestrowane foo.bar. Re. ten wątek - zarówno fileConfig, jak i dictConfig mają teraz opcje zapobiegające wyłączaniu starych programów rejestrujących. Zobacz ten problem: bugs.python.org/issue3136 , który pojawił się kilka miesięcy po twoim problemie bugs.python.org/issue2697 - w każdym razie został rozwiązany od czerwca 2008 r.
Vinay Sajip
czy nie byłoby lepiej zrobić, logger = someutils.getLogger(__name__)skąd someutils.getLoggerzwraca rejestrator z logging.getLoggerjuż dodanym null_handler?
7yl4r
1
@ 7yl4r Nie potrzebujesz każdego rejestratora, który ma NullHandlerdodany - zwykle jest to tylko rejestrator najwyższego poziomu dla twojej hierarchii pakietów. Więc to byłaby przesada, IMO.
Vinay Sajip,
123

Wiem, że to już rozwiązana odpowiedź, ale zgodnie z django> = 1.3 jest nowe ustawienie logowania.

Przejście ze starego do nowego nie jest automatyczne, więc pomyślałem, że zapiszę to tutaj.

I oczywiście sprawdź dokumentację django, aby uzyskać więcej informacji.

To jest podstawowa konfiguracja, utworzona domyślnie za pomocą django-admin createproject v1.3 - przebieg może się zmienić w najnowszych wersjach 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,
        }
    }
}

Ta struktura jest oparta na standardowym dzienniku Python dictConfig , który dyktuje następujące bloki:

  • formatters - odpowiednią wartością będzie dict, w którym każdy klucz jest identyfikatorem programu formatującego, a każda wartość to dykt opisujący, jak skonfigurować odpowiednią instancję programu Formatter.
  • filters - odpowiednią wartością będzie dict, w którym każdy klucz jest identyfikatorem filtru, a każda wartość to dykt opisujący, jak skonfigurować odpowiednią instancję filtru.
  • handlers- odpowiednią wartością będzie dict, w którym każdy klucz jest identyfikatorem programu obsługi, a każda wartość jest dictem opisującym, jak skonfigurować odpowiednią instancję programu Handler. Każdy program obsługi ma następujące klucze:

    • class(obowiązkowy). To jest w pełni kwalifikowana nazwa klasy obsługi.
    • level(opcjonalny). Poziom przewodnika.
    • formatter(opcjonalny). Identyfikator programu formatującego dla tego programu obsługi.
    • filters(opcjonalny). Lista identyfikatorów filtrów dla tego modułu obsługi.

Zwykle robię przynajmniej to:

  • dodaj plik .log
  • skonfigurować moje aplikacje do zapisywania w tym dzienniku

Co przekłada się na:

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

edytować

Zobacz wyjątki żądań są teraz zawsze rejestrowane, a numer biletu 16288 :

Zaktualizowałem powyższą przykładową konfigurację, aby wyraźnie uwzględnić poprawny filtr dla administratorów poczty, aby domyślnie wiadomości e-mail nie były wysyłane, gdy debugowanie ma wartość True.

Powinieneś dodać filtr:

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

i zastosuj go do programu obsługi mail_admins:

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

W przeciwnym razie django.core.handers.base.handle_uncaught_exceptionnie przekazuje błędów do programu rejestrującego „django.request”, jeśli settings.DEBUG ma wartość True.

Jeśli nie zrobisz tego w Django 1.5, otrzymasz plik

DeprecationWarning: nie masz żadnych filtrów zdefiniowanych w module obsługi logowania „mail_admins”: dodanie niejawnego filtru debug-false-only

ale to nadal będzie działać poprawnie ZARÓWNO w django 1.4 i django 1.5.

** koniec edycji **

To conf jest silnie inspirowane przykładowym conf w dokumencie django, ale dodaje część pliku dziennika.

Często też wykonuję następujące czynności:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

Następnie w moim kodzie Pythona zawsze dodaję NullHandler na wypadek, gdyby w ogóle nie została zdefiniowana konfiguracja logowania. Pozwala to uniknąć ostrzeżeń dla nieokreślonego programu obsługi. Szczególnie przydatne dla bibliotek, które niekoniecznie są wywoływane tylko w 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.')

Mam nadzieję że to pomoże!

Stefano
źródło
Stefano, wielkie dzięki za szczegółową odpowiedź, bardzo pomocne. Może to sprawić, że warto zaktualizować wersję do wersji 1.3.
Parand
Parand, zdecydowanie warto (IMHO!) Przejść do django 1.3, chociaż jest kilka punktów, o które należy zadbać, aby zapewnić płynne przejście - otwórz nowe pytanie SO, jeśli masz kłopoty ;-)
Stefano
tak przy okazji: nadal używam tego rodzaju ustawień i dziennika plików, ale przeniosłem się do wartownika do produkcji!
Stefano
@clime dobrze, starałem się to wyjaśnić w samej odpowiedzi: w przypadku, gdy nie jest zdefiniowana żadna konfiguracja logowania. Pozwala to uniknąć ostrzeżeń dla nieokreślonego programu obsługi. Szczególnie przydatne dla bibliotek, które niekoniecznie są wywoływane tylko w Django (ref)
Stefano
Nie rozumiem, jak używasz tej definicji: 'null': {'level': 'DEBUG', 'class': 'django.utils.log.NullHandler',}
clime
9

Rejestrowanie na najwyższym poziomie inicjalizujemy urls.pyza pomocą logging.inipliku.

Lokalizacja logging.inijest podana w settings.py, ale to wszystko.

Wtedy każdy moduł to robi

logger = logging.getLogger(__name__)

Aby odróżnić instancje testowe, programistyczne i produkcyjne, mamy różne pliki logging.ini. W większości przypadków mamy „dziennik konsoli”, który trafia na stderr tylko z błędami. Mamy „dziennik aplikacji”, który używa zwykłego kroczącego pliku dziennika, który trafia do katalogu dzienników.

S.Lott
źródło
Skończyło się na tym, że użyłem tego, z wyjątkiem inicjalizacji w settings.py zamiast urls.py
Parand
Jak używasz ustawień z settings.py w pliku logging.ini? Na przykład potrzebuję ustawienia BASE_DIR, aby móc powiedzieć mu, gdzie przechowywać pliki dziennika.
slypete
@slypete: Nie używamy ustawień w logowaniu.ini. Ponieważ logowanie jest w dużej mierze niezależne, nie używamy żadnych ustawień Django. Tak, istnieje możliwość powtórzenia czegoś. Nie, to nie robi dużej różnicy w praktyce.
S.Lott
W takim przypadku chciałbym mieć osobny plik logging.ini w każdej instalacji mojej aplikacji.
podstępny
@slypete: Masz plik settings.py dla każdej instalacji. Masz również plik logging.ini dla każdej instalacji. Ponadto prawdopodobnie masz również plik konfiguracyjny Apache dla każdej instalacji. Plus plik interfejsu wsgi. Nie jestem pewien, o co ci chodzi.
S.Lott
6

Obecnie używam systemu logowania, który sam stworzyłem. Używa formatu CSV do logowania.

django-csvlog

Ten projekt nadal nie ma pełnej dokumentacji, ale pracuję nad tym.

Oduvan
źródło