Logowanie wyjątków i zdarzeń z projektów Django do Sentry

sentry to coś w rodzaju agregatora logów. W przypadku projektów Django używamy go do logowania wyjątków jak i innych informacji pisanych przez nasz kod do logów. Zamiast wysyłać maila z wyjątkiem do administratora wszystko zapisywane jest w sentry.

Sentry to aplikacja napisana w Django. Obsługuje logowanie z różnych frameworków, czy języków (nawet JavaScript). Aplikację można szybko odpalić lokalnie, odpalić na własnym serwerze, albo wykupić instancję na getsentry.

W tym artykule przedstawię jak odpalić Sentry lokalnie (np. do testów) i jak skonfigurować Django by logowało wszystko do Sentry.

Instalacja i konfiguracja Sentry

Instalacja jest prosta:
pip install sentry

Pod Ubuntu-pochodną nie miałem plików nagłówkowych Pythona (pakiet *-dev) co wyłożyło kompilację setproctitle. Zainstalowanie libpython-all-dev z repozytorium załatwiło sprawę. Dodatkowo obecne Sentry (0.5.5) nie jest kompatybilne z Django 1.5, więc jeżeli w systemie masz tą lub nowszą wersję to Sentry trzeba zainstalować w virtualenvie.

Po instalacji musimy stworzyć gdzieś plik konfiguracyjny:
sentry init conf.py
Wygenerowany zostanie plik konfiguracyjny, który możemy edytować. Jak na konfigurację testową wystarczy. Można zmienić EMAIL_BACKEND na konsolę, by próby wysłania emaila nie kończyły się wyjątkiem:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Następnie tworzymy tabele i wszystko co Sentry chce (tworzymy konto superużytkownika):
sentry --config=conf.py upgrade
Możemy już uruchomić aplikację poleceniem:
sentry --config=conf.py start
Wejdź na http://localhost:9000 by dokończyć rejestrację i założyć pierwszy logowany projekt:
Tworzenie zespołu w Sentry

Tworzenie zespołu w Sentry

Zakładanie pierwszego projektu

Zakładanie pierwszego projektu

Jako platformę wybieramy ten język/framework, którego używamy i chcemy zintegrować z Sentry. W tym przypadku będzie to Django. Po stworzeniu pierwszego projektu Sentry wyświetli instrukcje podpięcia logowania wyjątków z Django. Nie powie jednak wszystkiego...

Konfiguracja projektów Django

Instalujemy ravena:
pip install raven
Do INSTALLED_APPS dodajemy: 'raven.contrib.django.raven_compat',. Dodajemy także podany przez Sentry RAVEN_CONFIG:
RAVEN_CONFIG = {
    'dsn': 'TUTAJ_DSN_PODANY_PO_STWORZENIU_PROJEKTU_W_SENTRY',
}
To by było na tyle jeżeli chodzi o logowanie wyjątków. Ta konfiguracja nie obejmuje logowania wyjątków Celery (jeżeli używamy), jak i nie uwzględnia przekazywania przydanych danych do logów gdy używamy Pythonowego modułu logging. O tym za chwilę. Teraz czas na test logowania wyjątków.

Logowanie wyjątków

Załóżmy że mamy widok, któremu zdarzy się rzucić wyjątkiem:
from django.views import generic

class ExceptionView(generic.View):
    def get(self, request, **kwargs):
        raise ValueError('I don\'t like this value')

exception_view = ExceptionView.as_view()
Wyjątek rzucony przez widok
Co powinno zostać zapisane do Sentry:
Wyjątek z widoku zapisany w Sentry

Wyjątek z widoku zapisany w Sentry

Można kliknąć na wpis by zobaczyć szczegóły, w tym wyjątek, stacktrace, informacje o użytkowniku Django i parę innych danych.

Logowanie zdarzeń modułem logging

Moduł logging często jest wykorzystywany by logować jakieś zdarzenia, które np. nie powinny wystąpić (ale jak wystąpią to rzucenie wyjątkiem nie jest potrzebne). Oto prosty przykład:

import logging

from django import http
from django.views import generic

logger = logging.getLogger(__name__)


class LoggingView(generic.View):
    def get(self, request, **kwargs):
        logger.warning("An error")
        return http.HttpResponse("ok")

logging_view = LoggingView.as_view()


class InheritingView(LoggingView):
    pass

inheriting_view = InheritingView.as_view()
Próba użycia modułu logging skończy się informacją że brak handlera do jego obsłużenia:
No handlers could be found for logger "myapp.views"
Trzeba podpiąć Sentry jako handler logów w Django (wersj 1.3 lub nowsza). W settingsach znajdź LOGGING i zastąp na:
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'root': {
        'level': 'DEBUG',
        'handlers': ['sentry'],
    },
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
    },
    'handlers': {
        'sentry': {
            'level': 'DEBUG',
            'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler',
        },
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose'
        }
    },
    'loggers': {
        'django.db.backends': {
            'level': 'ERROR',
            'handlers': ['console'],
            'propagate': False,
        },
        'raven': {
            'level': 'DEBUG',
            'handlers': ['console'],
            'propagate': False,
        },
        'sentry.errors': {
            'level': 'DEBUG',
            'handlers': ['console'],
            'propagate': False,
        },
    },
}
Co opisano także w dokumentacji Sentry. W powyższym przykładzie "root" jak i "sentry" mają poziom ustawiony na "DEBUG" więc będą logować wszystkie typy zdarzeń. Po takim skonfigurowaniu LOGGING zdarzenia z naszych testowych widoków zapiszą się do Sentry, ale będą dość skąpe w informacje:
Error zapisany przez Sentry
Mamy komunikat, miejsce w pliku i tyle. Nie wiemy która klasa (widok) rzucił tym wpisem, nie mamy requestu. Możemy wykorzystać dodatkowy argument przyjmowany przez metody loggera - extra będący słownikiem z dodatkowymi danymi do zapisu. Sentry używa extra do zapisu requestu jak i staktrace.
logger.warning("an error", extra={'request': request, 'stack': True})
Request pod kluczem request ładnie zaloguje cały obiekt request. Dodanie stack ustawionego na True spowoduje zapisanie stacktrace (co powie, np. który z widoków tak naprawdę wygenerował wpis). Zamiast wszędzie dodawać stack można w settingsach dodać:
SENTRY_AUTO_LOG_STACKS = True
Error zapisany przez Sentry wraz z requestem

Error zapisany przez Sentry wraz z requestem

Error, warning, info i debug w Sentry

Logowanie z zadań Celery

Żeby wyjątki z tasków Celery się logowany to musimy do LOGGING w settingsach do sekcji loggers dodać (level wedle uznania):
'celery': {
    'level': 'WARNING',
    'handlers': ['sentry'],
    'propagate': False,
},
Co obsłuży wyjątki, ale nie użycie loggera. Celery porywa logger i żeby działał z Sentry trzeba go pobrać tak:
from celery import task

@task()
def celery_task():
    logger = celery_task.get_logger()
    logger.warning("celerrrrrrrrrry log")
Gdzie "celery_task" to nazwa funkcji-taska Celery.
Warning z zapisanym stacktrace z zadania Celery

Warning z zapisanym stacktrace z zadania Celery

Żeby używać loggera normalnie można w settingsach dodać:

CELERYD_HIJACK_ROOT_LOGGER = False

Gdy Celery porywa loggera to wiadomości wysyłane przez ten moduł zobaczymy w konsoli Celery (django-admin.py celeryd -l info). Gdy wyłączymy porywanie to zobaczymy je w Sentry.

RkBlog

Django, 12 June 2013

Comment article
Comment article RkBlog main page Search RSS Contact