9

Are there any lightweight alternatives to django-sentry for error logging in a Django environment?

I used django-db-log earlier which now known as django-sentry. Some of the others I found were pretty much dead as they had no commits in the last two years almost.

Thanks.

Mridang Agarwalla
  • 43,201
  • 71
  • 221
  • 382
  • 2
    Is there a reason you don't want to use the built in logger? https://docs.djangoproject.com/en/dev/topics/logging/ – agf Aug 20 '11 at 09:49
  • what functionality do you need? – Spacedman Aug 20 '11 at 12:44
  • Initially I was using `django-db-log` which was good because everytime I had an error on the site, i could see the error in the admin panel. I could filter the errors by type, frequency, etc. The error page that it logged was the default 500 error page that Django displays when an exception is encountered with all the stacktraces, variables in the memory, request params, etc. If I could do the same without writing much code and using Django's internal mechanism, that would be great. Hope this is helpful explanation. Thanks. – Mridang Agarwalla Aug 20 '11 at 13:02
  • Hi Agf and Spacedman, I've added my implementation in an answer below if you're interested. – Mridang Agarwalla Sep 28 '11 at 07:06

2 Answers2

13

Sentry being overkill and Djangodblog being deprecated, I rolled my own, cannibalising the necessary parts from both.

How it works is by catching the error signal. Then it uses Django's inbuilt exception reporter to generate the fancy 500 error page that Django displays when debugging is enabled. We store this in the DB and render it in the admin console.

Here's my implementation:

Model:

class Error(Model):
    """
    Model for storing the individual errors.
    """
    kind = CharField( _('type'),
        null=True, blank=True, max_length=128, db_index=True
    )
    info = TextField(
        null=False,
    )
    data = TextField(
        blank=True, null=True
    )
    path = URLField(
        null=True, blank=True, verify_exists=False,
    )
    when = DateTimeField(
        null=False, auto_now_add=True, db_index=True,
    )
    html = TextField(
        null=True, blank=True,
    )

    class Meta:
        """
        Meta information for the model.
        """
        verbose_name = _('Error')
        verbose_name_plural = _('Errors')

    def __unicode__(self):
        """
        String representation of the object.
        """
        return "%s: %s" % (self.kind, self.info)

Admin:

class ErrorAdmin(admin.ModelAdmin):
    list_display    = ('path', 'kind', 'info', 'when')
    list_display_links = ('path',)
    ordering        = ('-id',)
    search_fields   = ('path', 'kind', 'info', 'data')
    readonly_fields = ('path', 'kind', 'info', 'data', 'when', 'html',)
    fieldsets       = (
        (None, {
            'fields': ('kind', 'data', 'info')
        }),
    )

    def has_delete_permission(self, request, obj=None):
        """
        Disabling the delete permissions
        """
        return False

    def has_add_permission(self, request):
        """
        Disabling the create permissions
        """
        return False

    def change_view(self, request, object_id, extra_context={}):
        """
        The detail view of the error record.
        """
        obj = self.get_object(request, unquote(object_id))

        extra_context.update({
            'instance': obj,
            'error_body': mark_safe(obj.html),
        })

        return super(ErrorAdmin, self).change_view(request, object_id, extra_context)

admin.site.register(Error, ErrorAdmin)

Helper:

class LoggingExceptionHandler(object):
    """
    The logging exception handler
    """
    @staticmethod
    def create_from_exception(sender, request=None, *args, **kwargs):
        """
        Handles the exception upon receiving the signal.
        """
        kind, info, data = sys.exc_info()

        if not issubclass(kind, Http404):

            error = Error.objects.create(
                kind = kind.__name__,
                html = ExceptionReporter(request, kind, info, data).get_traceback_html(),
                path = request.build_absolute_uri(),
                info = info,
                data = '\n'.join(traceback.format_exception(kind, info, data)),
            )
            error.save()

Init:

from django.core.signals import got_request_exception

from modules.error.signals import LoggingExceptionHandler

got_request_exception.connect(LoggingExceptionHandler.create_from_exception)
Mridang Agarwalla
  • 43,201
  • 71
  • 221
  • 382
  • 1
    This is now available as a package from PyPi. http://pypi.python.org/pypi/django-erroneous/ – Mridang Agarwalla Nov 20 '12 at 20:41
  • 1
    This question and answer motivated me to roll my own also. I've been using it in production for a while and have finally released it on pypi as well: https://pypi.python.org/pypi/django-shoogie/ – Aryeh Leib Taurog Nov 17 '13 at 21:13
  • 1
    For Django>=1.4, a "form_url" arg was added to the ModelAdmin's change_view() method. This means that the above code needs a slight modification to still work. In the ErrorAdmin class above, in the overridden change_view() method, the call to super() toward the end of the method should now specify extra_context as a keyword argument, like this: return super(ErrorAdmin, self).change_view(request, object_id, extra_context=extra_context) – Christian Abbott Jun 05 '14 at 12:06
2

There is also: http://pypi.python.org/pypi/django-logdb if you want something more lightweight but still production ready.

Remco Wendt
  • 673
  • 9
  • 9