Repository: jrobichaud/django-structlog

Issue:
Does django-structlog has compatibility with Celery Beat?
I've configured django-structlog to work with Celery by following the documentation, but I noticed that my Celery Beat worker doesn't produce any logs whenever it sends a new task or if the schedule was changed in the admin panel.

Can django-structlog also log these events from Celery Beat?

Discussion:

Patch:
diff --git a/django_structlog/__init__.py b/django_structlog/__init__.py
index e7fa852..84b7404 100644
--- a/django_structlog/__init__.py
+++ b/django_structlog/__init__.py
@@ -4,6 +4,6 @@
 
 name = "django_structlog"
 
-VERSION = (5, 1, 0)
+VERSION = (5, 2, 0)
 
 __version__ = ".".join(str(v) for v in VERSION)
diff --git a/django_structlog/celery/receivers.py b/django_structlog/celery/receivers.py
index b44d3e6..19bc4f5 100644
--- a/django_structlog/celery/receivers.py
+++ b/django_structlog/celery/receivers.py
@@ -39,6 +39,7 @@ def receiver_task_pre_run(task_id, task, *args, **kwargs):
     signals.bind_extra_task_metadata.send(
         sender=receiver_task_pre_run, task=task, logger=logger
     )
+    logger.info("task_started", task=task.name)
 
 
 def receiver_task_retry(request=None, reason=None, einfo=None, **kwargs):
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 8a08055..b42b2d2 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -2,6 +2,13 @@ Change Log
 ==========
 
 
+5.2.0 (June 29, 2023)
+---------------------
+
+*New:*
+    - Add new event ``task_started``. See `#260 <https://github.com/jrobichaud/django-structlog/issues/260>`_. Special thanks to `@adrenaline681 <https://github.com/adrenaline681>`_.
+
+
 5.1.0 (April 22, 2023)
 ----------------------
 
diff --git a/docs/events.rst b/docs/events.rst
index e51026c..1fcafcc 100644
--- a/docs/events.rst
+++ b/docs/events.rst
@@ -78,6 +78,8 @@ Task Events
 +--------------------+-------------+------------------------------------------------+
 | task_retrying      | WARNING     | Worker retry task                              |
 +--------------------+-------------+------------------------------------------------+
+| task_started       | INFO        | task just started executing                    |
++--------------------+-------------+------------------------------------------------+
 | task_succeeded     | INFO        | Task completed successfully                    |
 +--------------------+-------------+------------------------------------------------+
 | task_failed        | ERROR/INFO* | Task failed                                    |
@@ -122,6 +124,8 @@ These metadata appear once along with their associated event
 +------------------+------------------+----------------------------------------+
 | task_retrying    | reason           | reason for retry                       |
 +------------------+------------------+----------------------------------------+
+| task_started     | task             | name of the task                       |
++------------------+------------------+----------------------------------------+
 | task_failed      | error            | exception as string                    |
 +------------------+------------------+----------------------------------------+
 | task_failed      | exception*       | exception's traceback                  |


Test patch:
diff --git a/test_app/tests/celery/test_receivers.py b/test_app/tests/celery/test_receivers.py
index 1170537..85e95d6 100644
--- a/test_app/tests/celery/test_receivers.py
+++ b/test_app/tests/celery/test_receivers.py
@@ -220,12 +220,16 @@ class TestReceivers(TestCase):
             "request_id": expected_request_uuid,
             "user_id": expected_user_id,
         }
+        task.name = "task_name"
         structlog.contextvars.bind_contextvars(foo="bar")
 
         context = structlog.contextvars.get_merged_contextvars(self.logger)
         self.assertDictEqual({"foo": "bar"}, context)
 
-        receivers.receiver_task_pre_run(task_id, task)
+        with self.assertLogs(
+            logging.getLogger("django_structlog.celery.receivers"), logging.INFO
+        ) as log_results:
+            receivers.receiver_task_pre_run(task_id, task)
         context = structlog.contextvars.get_merged_contextvars(self.logger)
 
         self.assertDictEqual(
@@ -237,6 +241,13 @@ class TestReceivers(TestCase):
             context,
         )
 
+        self.assertEqual(1, len(log_results.records))
+        record = log_results.records[0]
+        self.assertEqual("task_started", record.msg["event"])
+        self.assertEqual("INFO", record.levelname)
+        self.assertIn("task", record.msg)
+        self.assertEqual("task_name", record.msg["task"])
+
     def test_signal_bind_extra_task_metadata(self):
         @receiver(signals.bind_extra_task_metadata)
         def receiver_bind_extra_request_metadata(