django-cron icon indicating copy to clipboard operation
django-cron copied to clipboard

Logging on Exceptions

Open colinhowe opened this issue 7 years ago • 5 comments

We use Sentry to capture exceptions. At the moment django_cron captures all unhandled exceptions and logs them to its own error model. Would you accept a PR that also records the unhandled exception using the standard logging framework? This would allow django_cron to integrate with anything that listens to logs.

colinhowe avatar Jul 24 '17 07:07 colinhowe

I'd be interested in something similar to get my cronjob failuers to also log into Sentry

MaZZly avatar Oct 15 '17 10:10 MaZZly

+1 on this. I understand why it captures all exceptions though (as this avoids Cron jobs crashing the django_cron internals and preventing jobs running).

What is probably needed is to find a way to allow Raven (or a.n.other monitoring framework) to hook a job failure.

cjsoftuk avatar Feb 05 '18 17:02 cjsoftuk

I've accomplished this by adding a post_save hook on the job object which pushes the cron exception message to Sentry:

from django_cron.models import CronJobLog
from django.db.models.signals import post_save
from django.dispatch import receiver
from raven.contrib.django.raven_compat.models import client


@receiver(post_save, sender=CronJobLog, dispatch_uid="propagate_cron_exception")
def propagate_cron_exception(sender, instance, **kwargs):
    if not instance.is_success:
        client.captureMessage("Exception in cron\n\n{}".format(instance.message))

lauritzen avatar Aug 14 '18 08:08 lauritzen

The post_save hook above is clever but unfortunately returns exception as text which doesn't look great in Sentry. I ended up catching the exception, logging it and re-raising it, which definitely is not clever:


from sentry_sdk import capture_exception

class GetPriceImbalance(CronJobBase):
    RUN_EVERY_MINS = 60 * 3

    schedule = Schedule(run_every_mins=RUN_EVERY_MINS)
    code = 'price_imbalance'

    def do(self):
        try:
            get_price_imbalance_data()
        except Exception as e:
            capture_exception(e)
            raise

will-emmerson avatar Feb 04 '19 16:02 will-emmerson

True, which is actually a big shortcoming. You could create a decorator to streamline the code a bit:

from functools import wraps
def sentry_exceptions(func):
    @wraps(func)
    def inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            capture_exception(e)
            raise

    return inner

Which lets you:

class GetPriceImbalance(CronJobBase):
    RUN_EVERY_MINS = 60 * 3

    schedule = Schedule(run_every_mins=RUN_EVERY_MINS)
    code = 'price_imbalance'

    @sentry_exceptions
    def do(self):
        get_price_imbalance_data()

lauritzen avatar Feb 04 '19 22:02 lauritzen