Manually trigger Django email error report

Question:

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.

Asked By: frnhr

||

Answers:

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,
        },
Answered By: Appleman1234

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.

Answered By: Chillar Anand

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 = "%snn%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)
Answered By: JuniorCompressor

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?

Answered By: Daniel Backman

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()
    )
Answered By: gitaarik

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(),
    )
Answered By: caram