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.


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 .


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


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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.