2 min read

Using Celery Signals

Celery provides a signaling system that allows you to hook into various stages of a task's lifecycle. This is useful for logging, monitoring, or triggering side effects (like sending an email notification) when a task starts, succeeds, or fails.

How Signals Work

Signals use the Observer pattern. You define a function (the receiver) and connect it to a specific signal. When that event occurs in the worker, your function is executed.

Important: Signal handlers run synchronously within the worker process. If your signal handler is slow (e.g., making a network request), it will block the worker from processing other tasks.

Common Signals

  • task_prerun: Sent before a task is executed.
  • task_postrun: Sent after a task is executed (regardless of success or failure).
  • task_success: Sent when a task succeeds.
  • task_failure: Sent when a task fails.
  • worker_ready: Sent when the worker has started.

Connecting to Signals

The easiest way to connect to a signal is using the @connect decorator provided by the signal object.

Example 1: Logging Task Lifecycle

from celery.signals import task_prerun, task_postrun
from celery.utils.log import get_task_logger

logger = get_task_logger(__name__)

@task_prerun.connect
def task_starting_handler(sender=None, headers=None, body=None, **kwargs):
    # sender is the task object
    info = headers if headers else {}
    logger.info(f"Task {sender.name} is starting. ID: {info.get('id')}")

@task_postrun.connect
def task_finished_handler(sender=None, task_id=None, state=None, **kwargs):
    logger.info(f"Task {task_id} finished with state: {state}")

Example 2: Handling Task Failure

This is a common use case for sending alerts to Slack, Email, or Sentry when a critical task fails.

from celery.signals import task_failure

@task_failure.connect
def handle_task_failure(sender=None, task_id=None, exception=None, traceback=None, **kwargs):
    print(f"CRITICAL: Task {task_id} failed!")
    print(f"Exception: {exception}")

    # Example: Send alert (ensure this is fast or async!)
    # send_slack_alert(f"Task {sender.name} failed: {exception}")

Filtering Signals

You can restrict a signal handler to specific tasks by checking the sender argument, which usually contains the task object or task name.

@task_success.connect(sender='tasks.add')
def add_success_handler(sender=None, result=None, **kwargs):
    print(f"The add task finished with result: {result}")

programming/python/celery