Sentry - na straży aplikacji Django

Ile razy zapomniałeś o jakimś drobiazgu, lub zrobiłeś jakiś błąd, przez który Twoja aplikacja Django wysypała się dopiero na serwerze produkcyjnym? Użytkownicy strony zobaczyli błąd 500 (bo utworzyłeś szablon 500.html, prawda?) i tyle. Pół biedy, jeśli dostałeś powiadomienie o błędzie na maila, Django wysyła na tyle dużo informacji do ADMINS-ów że lepsze to niż nic. Ale rodzi się pytanie - nie można by raportować błędów jakoś bardziej elegancko? Można - po to jest Sentry.

Aktualizacja

Ten wpis dotyczy bardzo starej wersji Sentry, instalowanej razem z projektem. Od tamtego czasu projekt poszedł ostro do przodu, stał się SaaS-em, którego jednak można za darmo postawić u siebie. O konfiguracji i działaniu współczesnych wersji Sentry możesz przeczytać w Bibliotece Riklaunima.

Jeżeli jednak chcesz zobaczyć, jak to drzewiej bywało, czytaj dalej.

Wprowadzenie

Sentry (ang. strażnik, wartownik), a właściwie django-sentry, bo pod taką nazwą jest zarejestrowana paczka na PyPI, to aplikacja Django rozwijana przez Davida Cramera i ekipę z Disqus. Stanowi kontynuację django-db-log i umożliwia rejestrowanie wyjątków oraz logowanie komunikatów błędów do bazy danych. Na dzień dzisiejszy ponad 400 osób obserwuje projekt na Githubie, a kilkadziesiąt utworzyło własne forki.

Instalacja

Wszystko przebiega zupełnie typowo, pip install django-sentry (easy_install też zadziała, ale pip jest bardziej sexy ;) ). Następnie dodajemy zgodnie z dokumentacją Sentry i jego zależności do INSTALLED_APPS, a potem aktualizujemy strukturę bazy danych (twórca Sentry zaleca South, ja zresztą też).

# dla używających South
python manage.py migrate

# lub bez South - tradycyjnie
python manage.py syncdb

Pozostaje jeszcze podpiąć Sentry do pliku URLconf, najprościej tak:

urlpatterns = patterns('',
    # ...,
    (r'^sentry/', include('sentry.urls')),
)

Po uruchomieniu serwera deweloperskiego Django i wejściu pod adres skonfigurowany w pliku urls.py, zobaczymy "kokpit" Sentry, prawdopodobnie pusty. Podobnie jak aplikacja panelu administracyjnego, kokpit Sentry stanowi kompletną witrynę internetową z własnymi szablonami i plikami statycznymi.

Wyjątki

Sentry przechwytuje błędy aplikacji zgłaszane jako wyjątki i loguje wystąpienie błędu do bazy danych. W kokpicie wyjątki są grupowane na podstawie źródła błędu - nazwy funkcji widoku, w której wystąpił problem, lub nazwy pliku szablonu, jeśli błąd powstał w trakcie renderowania. W czytelny sposób jest prezentowana liczba wystąpień danego błędu, jego źródło, oraz data ostatniego wystąpienia. Spośród pozostałych informacji najbardziej istotny jest typ wyjątku oraz właściwy komunikat błędu.

Zgłoszenie błędu w kokpicie

Kokpit prezentuje informacje niemalże w czasie rzeczywistym - za pomocą AJAX-a regularnie odpytuje Sentry, czy nie zarejestrowano nowych błędów i aktualizuje stronę bez potrzeby odświeżania. Dodatkowo, najeżdżając kursorem na informacje o błędzie i klikając pole z prawej strony, możemy go "odfajkować" - oznaczyć, że problem jest znany i prawdopodobnie sobie z nim poradziliśmy. Spowoduje to ukrycie danego błędu z listy. Oczywiście, gdy jednak (odpukać) problem się powtórzy, zgłoszenie pojawi się ponownie w kokpicie.

Możemy sortować informacje o błędach według priorytetu, bądź też czasu pierwszego lub ostatniego wystąpienia. Kokpit umożliwia ponadto filtrowanie zgłoszeń na podstawie kilku kryteriów, między innymi stanu zgłoszenia, loggera - źródła, kategorii błędu, lub serwera na którym wystąpił błąd.

Kliknięcie zgłoszenia otwiera widok jego szczegółów. Zobaczymy tu częstość występowania danego błędu w formie eleganckiego wykresu. Reszta widoku szczegółów zgłoszenia przypomina stronę informacji o błędzie, wyświetlaną przez Django gdy DEBUG = True.

Widok szczegółów zgłoszenia błędu

Logowanie własnych zdarzeń

Druga istotna funkcjonalność Sentry poza przechwytywaniem wyjątków, to możliwość rejestrowania dowolnych zdarzeń w aplikacji, jeśli używamy do tego modułu logging ze standardowej biblioteki Pythona. Musimy jedynie skonfigurować logging tak, by wykorzystywał klasę obsługi (handler - kto potrafi to sensowniej przetłumaczyć?) dostarczaną przez Sentry. Posłużę się przykładową konfiguracją z dokumentacji Sentry.

import logging
from sentry.client.handlers import SentryHandler

logger = logging.getLogger()
# ensure we havent already registered the handler
if SentryHandler not in map(lambda x: x.__class__, logger.handlers):
    logger.addHandler(SentryHandler())

    # Add StreamHandler to sentry's default so you can catch missed exceptions
    logger = logging.getLogger('sentry.errors')
    logger.propagate = False
    logger.addHandler(logging.StreamHandler())

Jako przykład wykorzystania logowania zdarzeń wykorzystam minimalnie przerobiony fragment kodu z jednego z moich projektów. Chciałem logować rejestrować nieudane próby logowania użytkowników na stronie - w dość naiwny, ale działający sposób.

# globalny logger dla widoków aplikacji
logger = logging.getLogger('mojprojekt.accounts.views')

# nakładka na widok django.contrib.auth.views.login
def login(request, template_name='accounts/login.html'):
    response = auth_views.login(request, template_name=template_name)
    if request.method == 'POST' and not request.user.is_authenticated():
        logger.warning(u"Failed login attempt (username: %s)" % request.POST.get('username'))
    return response

Po podpięciu Sentry, mogę śledzić w kokpicie również tego typu zdarzenia.

Ostrzeżenie o nieudanym logowaniu

Tak na marginesie: Django 1.3 dodaje kilka ułatwień w obsłudze logowania, między innymi standaryzuje konfigurację loggerów oraz udostępnia kilka własnych, wbudowanych we framework (między innymi logowanie żądań HTTP kończących się błędami, czy też debugowanie zapytań SQL).

Podsumowując, Sentry to godna uwagi aplikacja i zdecydowanie warto się przyjrzeć jej bliżej. Jako ciekawostkę dodam, że autor rozważa w wersji 2.0 uniezależnienie jej od Django i przepisanie klienta do postaci middleware WSGI.

Zobacz też: