Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
isagalaev committed Jan 20, 2010
0 parents commit 089b53e
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 0 deletions.
31 changes: 31 additions & 0 deletions README
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## WHAT'S THIS

By default Django sends server errors by email which might be not convenient
(you can't setup monitoring) and not always secure (a single bug in a hot place
could overflow mail server). Django_errorlog enables logging of server errors
with standard Python logging.

## USAGE

To install in a Django project:

1. Include 'django_errorlog' into INSTALLED_APPS

2. Setup handlers for log channels 'exception' and 'traceback' with usual
Python [logging handlers][1]. It's a good idea to have a separate file (or
whatever) for 'traceback' logger because its messages don't fit on a single
line and hence not easily grep'able.

3. To disable default mailing of server errors you can leave ADMINS setting
empty or not setup SMTP_HOST.

The application works automatically: it listents for a signal that Django sends
on all uncaught server errors and then logs short exception values and full
tracebacks into their respective log channels.

There are two utility functions in django_errorlog.utils: log_error and
log_warning. They can be used to manually log exception that you do handle in
your code. They accept exc_info (a triple of (exceptions, value, traceback) as
an argument. If called without arguments they get it from sys.exc_info().

[1]: http://docs.python.org/library/logging.html#handlers
17 changes: 17 additions & 0 deletions django_errorlog/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding:utf-8 -*-
'''
Приложение вешается на сигнал, который Django посылает в случае фатальных
ошибок и логирует значения и полные traceback'и исключений в логгеры
'exception' и 'traceback' соответственно.
Хендлеры для логгеров можно настраивать в проекте, а можно попросить
django_errorlog создать дефолтные. Для этого надо импортировать к себе
настройки из django_errorlog.settings и прописать свои имена файлов в
EXCEPTION_LOG_FILE и TRACEBACK_LOG_FILE.
Есть две вспомогательные функции в модуле utils: log_error и log_warning,
которые нужны, если в общий лог ошибок надо слогировать exception вручную.
Обе функции принимают либо exc_info, либо, если вызваны без параметров, берут
его из sys.exc_info() - текущего обрабатываемого исключения.
'''
110 changes: 110 additions & 0 deletions django_errorlog/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# -*- coding:utf-8 -*-
import sys
import os
import re
import traceback
import logging
from logging import handlers

from django.core import signals
from django.utils.encoding import smart_str
from django.conf import settings

def _get_logger(name, setting_name):
'''
Returns a named logger.
Creates a default file handler for it if there's a setting for it
(deprecated).
'''
if name not in _get_logger.loggers:
logger = logging.getLogger(name)
if getattr(settings, setting_name, ''):
try:
handler = handlers.RotatingFileHandler(
getattr(settings, setting_name),
'a',
settings.LOGGING_MAX_FILE_SIZE,
settings.LOGGING_MAX_FILES_COUNT
)
except:
handler = logging.StreamHandler(None)
handler.setFormatter(logging.Formatter(settings.LOGGING_FORMAT))
logger.addHandler(handler)
_get_logger.loggers[name] = logger

return _get_logger.loggers[name]
_get_logger.loggers = {}

def exception_str(value):
'''
Formats Exception object to a string. Unlike default str():
- can handle unicode strings in exception arguments
- tries to format arguments as str(), not as repr()
'''
try:
return ', '.join([smart_str(b) for b in value])
except (TypeError, AttributeError): # happens for non-iterable values
try:
return smart_str(value)
except UnicodeEncodeError:
try:
return repr(value)
except Exception:
return '<Unprintable value>'

POST_TRUNCATE_SIZE = 1024

def format_post(request):
'''
Casts request post data to string value. Depending on content type it's
either a dict or raw post data.
'''
if request.method == 'POST' and request.META['CONTENT_TYPE'] not in ['application/x-www-form-urlencoded', 'multipart/form-data']:
value = request.raw_post_data[:POST_TRUNCATE_SIZE]
if len(request.raw_post_data) > len(value):
value += '... (post data truncated at %s bytes)' % POST_TRUNCATE_SIZE
return value
else:
return str(request.POST)

def _log_exc_info(exc_info=None, level=logging.ERROR, aditional_lines=None):
'''
Logs exception info into 'exception' and 'traceback' loggers calling
formatting as necessary.
'''
exception, value, tb = exc_info or sys.exc_info()
exception_logger = _get_logger('exception', 'EXCEPTION_LOG_FILE')
# find innermost call
inner = tb
while inner.tb_next:
inner = inner.tb_next
lineno = inner.tb_lineno
module_name = inner.tb_frame.f_globals['__name__']
exception_logger.log(level, '%-20s %s:%s %s' % (
exception.__name__,
module_name,
lineno,
exception_str(value),
))

lines = traceback.format_exception(exception, value, tb)
if aditional_lines:
lines = aditional_lines + lines
traceback_logger = _get_logger('traceback', 'TRACEBACK_LOG_FILE')
traceback_logger.log(level, '\n'.join([smart_str(l) for l in lines]))

def _log_request_error(sender, request, **kwargs):
'''
Handles unhandled request exceptions.
'''
lines = [
'Path: %s' % request.path,
'GET: %s' % request.GET,
'POST: %s' % format_post(request),
]
_log_exc_info(aditional_lines=lines)

signals.got_request_exception.connect(_log_request_error)

17 changes: 17 additions & 0 deletions django_errorlog/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding:utf-8 -*-

## Settings for creating default handlers for loggers. This is deprecated
## in favor of setting up handlers in a project manually.

# Filename for logging one-line exception values
EXCEPTION_LOG_FILE = ''

# Filename for logging full tracebacks
TRACEBACK_LOG_FILE = ''

# Log format
LOGGING_FORMAT = '%(asctime)s %(name)-15s %(levelname)s %(message)s'

# Log file rotation settings
LOGGING_MAX_FILE_SIZE = 1024*1024
LOGGING_MAX_FILES_COUNT = 10
19 changes: 19 additions & 0 deletions django_errorlog/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding:utf-8 -*-
import logging

from django_errorlog.models import _log_exc_info, exception_str

def log_error(exc_info=None):
'''
Logs exc_info into 'exception' and 'traceback' logs with ERROR level.
If exc_info is None get it from sys.exc_info().
'''
_log_exc_info(exc_info, level=logging.ERROR)

def log_warning(exc_info=None):
'''
Logs exc_info into 'exception' and 'traceback' logs with WARNING level.
If exc_info is None get it from sys.exc_info().
'''
_log_exc_info(exc_info, level=logging.WARNING)

9 changes: 9 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from distutils.core import setup

setup(
name='django_errorlog',
description='Django application for logging server (aka "500") errors',
packages=[
'django_errorlog',
],
)

0 comments on commit 089b53e

Please sign in to comment.