Manually trigger Django email error report
Asked Answered
M

6

36

Django error reporting handles uncaught exceptions by sending an email, and (optionally) shows user a nice 500 error page.

This works very well, but in a few instances I'd like to allow users to continue with their business uninterrupted, but still have Django send me the email error report about the exception.

So basically: can I manually send email error report even if I catch the exception?

Of course, I'd like to avoid manually generating the error report email.

Mook answered 1/4, 2015 at 13:37 Comment(0)
F
42

You can use the following code to send manually an email about a request and an exception e:

import sys
import traceback
from django.core import mail
from django.views.debug import ExceptionReporter

def send_manually_exception_email(request, e):
    exc_info = sys.exc_info()
    reporter = ExceptionReporter(request, is_email=True, *exc_info)
    subject = e.message.replace('\n', '\\n').replace('\r', '\\r')[:989]
    message = "%s\n\n%s" % (
        '\n'.join(traceback.format_exception(*exc_info)),
        reporter.filter.get_request_repr(request)
    )
    mail.mail_admins(
        subject, message, fail_silently=True,
        html_message=reporter.get_traceback_html()
    )

You can test it in a view like this:

def test_view(request):
    try:
        raise Exception
    except Exception as e:
        send_manually_exception_email(request, e)
Forgo answered 26/4, 2015 at 13:54 Comment(4)
Sweetest! And although no answer produced exactly the error report that is generated automatically, this approach gave most info. It is similar to the error page Django shows with DEBUG = TrueMook
I am getting an error in line no 9. It says: 'Exception' object has no attribute 'message'Liuka
This also filters sensitive data just like the e-mail report unless DEFAULT_EXCEPTION_REPORTER_FILTER is set to a filter that doesn't. (At least on Django 1.9.6)Addax
get_request_repr has been removed in Django 1.10. I'm using message = reporter.get_traceback_text for now as recommended by django-zh-cn.readthedocs.io/en/latest/releases/1.9.html, but that looks more like the debug=True error page than the automatic emails that Django sends.Slimy
J
6

Just setup a simple log handler in your settings.

LOGGING = {
    'version': 1, 
    'disable_existing_loggers': False,
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        },
        'app': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        },
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
    }
}

and then in your view, you can do anything

 import logging
 logger = logging.getLogger('app')

 def some_view(request):
     try:
         # something
         if something_wnet_wrong:
             logger.error('Something went wrong!')
         return some_http_response
     except:
         #something else
         logger.error(sys.exc_info(), request)        
         return some_other_response

If you want detailed error report, you can try something like this.

You also need to take care of sensitive information.

Jug answered 23/4, 2015 at 9:42 Comment(0)
A
5

Yes you can manually send email error report even if you catch the exception.

There are several ways you can go about this.

  1. You can use the existing default logger configuration (and its associated handler configuration, documented here) for django.request which sends all error messages to the mail_admins handler, which sends the anything logged with log.error from django.request when debug is false as email using AdminEmailHandler, whose existing call point is in handle_uncaught_exception.
  2. You can add additional logger configuration which uses the same handler, to catch your exception earlier than django.request and call log.error earlier.
  3. You can subclass django.request, specifically handle_uncaught_exception.
  4. You can use a custom middleware ( for example StandardExceptionMiddleware) or ExceptionMiddleware
  5. You can manually call the contents of emit in AdminEmailHandler or mail.mail_admins directly.

Of these options, Option 4 seems to be the most commonly done.

Based on the additional information in your comment a code example of 2 is below.

First the code that would be added to view

from django.http import HttpResponse
import logging
logger = logging.getLogger(__name__)

def my_view(request):

    try:
        result = do_something()
        return HttpResponse('<h1>Page was found' + result + '</h1>')
    except Exception: 
         # Can have whatever status_code and title you like, but I was just matching the existing call.
         logger.error('Internal Server Error: %s', request.path,
            exc_info=sys.exc_info(),
            extra={
            'status_code': 500,
            'request': request
            }
         )
         return HttpResponse('<h1>Page was found, and exception was mailed to admins.</h1>')

This is based of Django documentation for view writing and and introduction to Django logging, but hasn't been tested.

Then the additional logger configuration is add to the to the loggers entry (as per here)

'nameofdjangoapplicationgoeshere': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': False,
        },
Archimandrite answered 23/4, 2015 at 4:12 Comment(1)
I wish to trigger the error report from a View and then continue with things as usual (i.e. allow the View to return a HttpResponse), so I don't think a Middleware will do. Or am I missing smth, @Brandon 's comment also indicates middleware? Options 1 and 2 seem to be the answer. Care to add a code example?Mook
H
5

I mostly use this pattern with the standard error reporting.

import logging    
logger = logging.getLogger('django.request')

#code block in view
try:
    #code that can raise exception
except:
    logger.exception('Information')
#continue as nothing happend

It will trigger the built in error reporting and logger.exception will catch the stack frame. https://docs.djangoproject.com/en/1.8/topics/logging/#making-logging-calls

edit:

I noticed some information was missing in the email and to get an exact traceback as the built in the following can be used instead:

logger.exception('Internal Server Error: %s', request.path,
                 extra={'status_code': 500, 'request': request})

More info found here: How to send django exception log manually?

Hogle answered 29/4, 2015 at 22:14 Comment(0)
Y
0

Building on @JuniorCompressor's answer, this is the code that I use:

import sys
from django.core import mail
from django.views.debug import ExceptionReporter

def send_exception_email(request, exception, subject_prefix=''):

    exc_info = sys.exc_info()
    reporter = ExceptionReporter(request, *exc_info, is_email=True)

    def exception_name():
        if exc_info[0]:
            return exc_info[0].__name__
        return 'Exception'

    def subject_suffix():
        if request:
            return '{} at {}'.format(
                exception_name(),
                request.path_info
            )
        return exception_name()

    def subject():
        return '{}{}'.format(
            subject_prefix,
            subject_suffix()
        )

    mail.mail_admins(
        subject=subject(),
        message=reporter.get_traceback_text(),
        fail_silently=True,
        html_message=reporter.get_traceback_html()
    )
Yi answered 4/12, 2018 at 12:40 Comment(0)
C
0

Here is a trimmed down version of @gitaarik's solution, adapted to Python 3:

import sys

from django.core import mail
from django.views.debug import ExceptionReporter

def send_exception_email(request, exception, subject_prefix=''):
    exc_info = sys.exc_info()

    exception_name = exc_info[0].__name__ if exc_info[0] else 'Exception'
    request_path = f" at {request.path_info}" if request else ''

    reporter = ExceptionReporter(request, *exc_info, is_email=True)

    mail.mail_admins(
        subject=f"{subject_prefix}{exception_name}{request_path}",
        message=reporter.get_traceback_text(),
        fail_silently=True,
        html_message=reporter.get_traceback_html(),
    )
Carri answered 15/12, 2020 at 15:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.