We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Celery: Unhanded exception in `task_prerun_callback`
Using celery-batches 0.2.0, celery 4.4.7, python 3.10, redis backend

What happens: Request with missing `delivery_info` raises error. This bubbles up to sentry, another tool.

Expected behavior: Drop the trace is unless error reporting is enabled on scout-apm.

```
AttributeError: 'NoneType' object has no attribute 'get'
  File "celery/utils/dispatch/signal.py", line 288, in send
    response = receiver(signal=self, sender=sender, **named)
  File "scout_apm/celery.py", line 60, in task_prerun_callback
    tracked_request.tag("is_eager", delivery_info.get("is_eager", False))
```

```
{
args: [
[
<celery_batches.SimpleRequest object at 0x7fbd1f2f9720>, 
<celery_batches.SimpleRequest object at 0x7fcd15dd65e0>, 
<celery_batches.SimpleRequest object at 0x7fad18d6fa30>, 
<celery_batches.SimpleRequest object at 0x7fed14d6e1a0>, 
<celery_batches.SimpleRequest object at 0x7fad13d6e230>
]
], 
kwargs: {}, 
sender: <@task: project.app.jobs.track_happening of project at 0x2faa2c2713f0 (v2 compatible)>, 
signal: <Signal: task_prerun providing_args={'args', 'kwargs', 'task', 'task_id'}>, 
task_id: '684c6b14-4349-42e7-bc8d-4d410bceb1c9'
}
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/scout_apm/celery.py`
Content:
```
1 # coding=utf-8
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 
4 import datetime as dt
5 import logging
6 
7 from celery.signals import before_task_publish, task_failure, task_postrun, task_prerun
8 
9 try:
10     import django
11 
12     if django.VERSION < (3, 1):
13         from django.views.debug import get_safe_settings
14     else:
15         from django.views.debug import SafeExceptionReporterFilter
16 
17         def get_safe_settings():
18             return SafeExceptionReporterFilter().get_safe_settings()
19 
20 except ImportError:
21     # Django not installed
22     get_safe_settings = None
23 
24 import scout_apm.core
25 from scout_apm.compat import datetime_to_timestamp, string_type
26 from scout_apm.core.config import scout_config
27 from scout_apm.core.error import ErrorMonitor
28 from scout_apm.core.tracked_request import TrackedRequest
29 
30 logger = logging.getLogger(__name__)
31 
32 
33 def before_task_publish_callback(headers=None, properties=None, **kwargs):
34     if "scout_task_start" not in headers:
35         headers["scout_task_start"] = datetime_to_timestamp(dt.datetime.utcnow())
36 
37 
38 def task_prerun_callback(task=None, **kwargs):
39     tracked_request = TrackedRequest.instance()
40     tracked_request.is_real_request = True
41 
42     start = getattr(task.request, "scout_task_start", None)
43     if start is not None:
44         now = datetime_to_timestamp(dt.datetime.utcnow())
45         try:
46             queue_time = now - start
47         except TypeError:
48             pass
49         else:
50             tracked_request.tag("queue_time", queue_time)
51 
52     task_id = getattr(task.request, "id", None)
53     if task_id:
54         tracked_request.tag("task_id", task_id)
55     parent_task_id = getattr(task.request, "parent_id", None)
56     if parent_task_id:
57         tracked_request.tag("parent_task_id", parent_task_id)
58 
59     delivery_info = task.request.delivery_info
60     tracked_request.tag("is_eager", delivery_info.get("is_eager", False))
61     tracked_request.tag("exchange", delivery_info.get("exchange", "unknown"))
62     tracked_request.tag("priority", delivery_info.get("priority", "unknown"))
63     tracked_request.tag("routing_key", delivery_info.get("routing_key", "unknown"))
64     tracked_request.tag("queue", delivery_info.get("queue", "unknown"))
65 
66     tracked_request.start_span(operation=("Job/" + task.name))
67 
68 
69 def task_postrun_callback(task=None, **kwargs):
70     tracked_request = TrackedRequest.instance()
71     tracked_request.stop_span()
72 
73 
74 def task_failure_callback(
75     sender,
76     task_id=None,
77     exception=None,
78     args=None,
79     kwargs=None,
80     traceback=None,
81     einfo=None,
82     **remaining
83 ):
84     tracked_request = TrackedRequest.instance()
85     tracked_request.tag("error", "true")
86 
87     custom_controller = sender.name
88     custom_params = {
89         "celery": {
90             "task_id": task_id,
91             "args": args,
92             "kwargs": kwargs,
93         }
94     }
95 
96     # Look up the django settings if populated.
97     environment = None
98     if get_safe_settings:
99         try:
100             environment = get_safe_settings()
101         except django.core.exceptions.ImproperlyConfigured as exc:
102             # Django not setup correctly
103             logger.debug(
104                 "Celery integration does not have django configured properly: %r", exc
105             )
106             pass
107         except Exception as exc:
108             logger.debug(
109                 "Celery task_failure callback exception: %r", exc, exc_info=exc
110             )
111             pass
112 
113     # Celery occassionally will send the traceback as a string rather
114     # than a Stack trace object as the docs indicate. In that case,
115     # fall back to the billiard ExceptionInfo instance
116     traceback = (
117         traceback if traceback and not isinstance(traceback, string_type) else einfo.tb
118     )
119     exc_info = (exception.__class__, exception, traceback)
120     ErrorMonitor.send(
121         exc_info,
122         environment=environment,
123         custom_params=custom_params,
124         custom_controller=custom_controller,
125     )
126 
127 
128 def install(app=None):
129     if app is not None:
130         copy_configuration(app)
131 
132     installed = scout_apm.core.install()
133     if not installed:
134         return
135 
136     before_task_publish.connect(before_task_publish_callback)
137     task_prerun.connect(task_prerun_callback)
138     task_failure.connect(task_failure_callback)
139     task_postrun.connect(task_postrun_callback)
140 
141 
142 def copy_configuration(app):
143     prefix = "scout_"
144     prefix_len = len(prefix)
145 
146     to_set = {}
147     for key, value in app.conf.items():
148         key_lower = key.lower()
149         if key_lower.startswith(prefix) and len(key_lower) > prefix_len:
150             scout_key = key_lower[prefix_len:]
151             to_set[scout_key] = value
152 
153     scout_config.set(**to_set)
154 
155 
156 def uninstall():
157     before_task_publish.disconnect(before_task_publish_callback)
158     task_prerun.disconnect(task_prerun_callback)
159     task_postrun.disconnect(task_postrun_callback)
160     task_failure.disconnect(task_failure_callback)
161
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/scout_apm/celery.py b/src/scout_apm/celery.py
--- a/src/scout_apm/celery.py
+++ b/src/scout_apm/celery.py
@@ -56,12 +56,13 @@
     if parent_task_id:
         tracked_request.tag("parent_task_id", parent_task_id)
 
-    delivery_info = task.request.delivery_info
-    tracked_request.tag("is_eager", delivery_info.get("is_eager", False))
-    tracked_request.tag("exchange", delivery_info.get("exchange", "unknown"))
-    tracked_request.tag("priority", delivery_info.get("priority", "unknown"))
-    tracked_request.tag("routing_key", delivery_info.get("routing_key", "unknown"))
-    tracked_request.tag("queue", delivery_info.get("queue", "unknown"))
+    delivery_info = getattr(task.request, "delivery_info", None)
+    if delivery_info:
+        tracked_request.tag("is_eager", delivery_info.get("is_eager", False))
+        tracked_request.tag("exchange", delivery_info.get("exchange", "unknown"))
+        tracked_request.tag("priority", delivery_info.get("priority", "unknown"))
+        tracked_request.tag("routing_key", delivery_info.get("routing_key", "unknown"))
+        tracked_request.tag("queue", delivery_info.get("queue", "unknown"))
 
     tracked_request.start_span(operation=("Job/" + task.name))

{"golden_diff": "diff --git a/src/scout_apm/celery.py b/src/scout_apm/celery.py\n--- a/src/scout_apm/celery.py\n+++ b/src/scout_apm/celery.py\n@@ -56,12 +56,13 @@\n     if parent_task_id:\n         tracked_request.tag(\"parent_task_id\", parent_task_id)\n \n-    delivery_info = task.request.delivery_info\n-    tracked_request.tag(\"is_eager\", delivery_info.get(\"is_eager\", False))\n-    tracked_request.tag(\"exchange\", delivery_info.get(\"exchange\", \"unknown\"))\n-    tracked_request.tag(\"priority\", delivery_info.get(\"priority\", \"unknown\"))\n-    tracked_request.tag(\"routing_key\", delivery_info.get(\"routing_key\", \"unknown\"))\n-    tracked_request.tag(\"queue\", delivery_info.get(\"queue\", \"unknown\"))\n+    delivery_info = getattr(task.request, \"delivery_info\", None)\n+    if delivery_info:\n+        tracked_request.tag(\"is_eager\", delivery_info.get(\"is_eager\", False))\n+        tracked_request.tag(\"exchange\", delivery_info.get(\"exchange\", \"unknown\"))\n+        tracked_request.tag(\"priority\", delivery_info.get(\"priority\", \"unknown\"))\n+        tracked_request.tag(\"routing_key\", delivery_info.get(\"routing_key\", \"unknown\"))\n+        tracked_request.tag(\"queue\", delivery_info.get(\"queue\", \"unknown\"))\n \n     tracked_request.start_span(operation=(\"Job/\" + task.name))\n", "issue": "Celery: Unhanded exception in `task_prerun_callback`\nUsing celery-batches 0.2.0, celery 4.4.7, python 3.10, redis backend\r\n\r\nWhat happens: Request with missing `delivery_info` raises error. This bubbles up to sentry, another tool.\r\n\r\nExpected behavior: Drop the trace is unless error reporting is enabled on scout-apm.\r\n\r\n```\r\nAttributeError: 'NoneType' object has no attribute 'get'\r\n  File \"celery/utils/dispatch/signal.py\", line 288, in send\r\n    response = receiver(signal=self, sender=sender, **named)\r\n  File \"scout_apm/celery.py\", line 60, in task_prerun_callback\r\n    tracked_request.tag(\"is_eager\", delivery_info.get(\"is_eager\", False))\r\n```\r\n\r\n```\r\n{\r\nargs: [\r\n[\r\n<celery_batches.SimpleRequest object at 0x7fbd1f2f9720>, \r\n<celery_batches.SimpleRequest object at 0x7fcd15dd65e0>, \r\n<celery_batches.SimpleRequest object at 0x7fad18d6fa30>, \r\n<celery_batches.SimpleRequest object at 0x7fed14d6e1a0>, \r\n<celery_batches.SimpleRequest object at 0x7fad13d6e230>\r\n]\r\n], \r\nkwargs: {}, \r\nsender: <@task: project.app.jobs.track_happening of project at 0x2faa2c2713f0 (v2 compatible)>, \r\nsignal: <Signal: task_prerun providing_args={'args', 'kwargs', 'task', 'task_id'}>, \r\ntask_id: '684c6b14-4349-42e7-bc8d-4d410bceb1c9'\r\n}\r\n```\r\n\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport datetime as dt\nimport logging\n\nfrom celery.signals import before_task_publish, task_failure, task_postrun, task_prerun\n\ntry:\n    import django\n\n    if django.VERSION < (3, 1):\n        from django.views.debug import get_safe_settings\n    else:\n        from django.views.debug import SafeExceptionReporterFilter\n\n        def get_safe_settings():\n            return SafeExceptionReporterFilter().get_safe_settings()\n\nexcept ImportError:\n    # Django not installed\n    get_safe_settings = None\n\nimport scout_apm.core\nfrom scout_apm.compat import datetime_to_timestamp, string_type\nfrom scout_apm.core.config import scout_config\nfrom scout_apm.core.error import ErrorMonitor\nfrom scout_apm.core.tracked_request import TrackedRequest\n\nlogger = logging.getLogger(__name__)\n\n\ndef before_task_publish_callback(headers=None, properties=None, **kwargs):\n    if \"scout_task_start\" not in headers:\n        headers[\"scout_task_start\"] = datetime_to_timestamp(dt.datetime.utcnow())\n\n\ndef task_prerun_callback(task=None, **kwargs):\n    tracked_request = TrackedRequest.instance()\n    tracked_request.is_real_request = True\n\n    start = getattr(task.request, \"scout_task_start\", None)\n    if start is not None:\n        now = datetime_to_timestamp(dt.datetime.utcnow())\n        try:\n            queue_time = now - start\n        except TypeError:\n            pass\n        else:\n            tracked_request.tag(\"queue_time\", queue_time)\n\n    task_id = getattr(task.request, \"id\", None)\n    if task_id:\n        tracked_request.tag(\"task_id\", task_id)\n    parent_task_id = getattr(task.request, \"parent_id\", None)\n    if parent_task_id:\n        tracked_request.tag(\"parent_task_id\", parent_task_id)\n\n    delivery_info = task.request.delivery_info\n    tracked_request.tag(\"is_eager\", delivery_info.get(\"is_eager\", False))\n    tracked_request.tag(\"exchange\", delivery_info.get(\"exchange\", \"unknown\"))\n    tracked_request.tag(\"priority\", delivery_info.get(\"priority\", \"unknown\"))\n    tracked_request.tag(\"routing_key\", delivery_info.get(\"routing_key\", \"unknown\"))\n    tracked_request.tag(\"queue\", delivery_info.get(\"queue\", \"unknown\"))\n\n    tracked_request.start_span(operation=(\"Job/\" + task.name))\n\n\ndef task_postrun_callback(task=None, **kwargs):\n    tracked_request = TrackedRequest.instance()\n    tracked_request.stop_span()\n\n\ndef task_failure_callback(\n    sender,\n    task_id=None,\n    exception=None,\n    args=None,\n    kwargs=None,\n    traceback=None,\n    einfo=None,\n    **remaining\n):\n    tracked_request = TrackedRequest.instance()\n    tracked_request.tag(\"error\", \"true\")\n\n    custom_controller = sender.name\n    custom_params = {\n        \"celery\": {\n            \"task_id\": task_id,\n            \"args\": args,\n            \"kwargs\": kwargs,\n        }\n    }\n\n    # Look up the django settings if populated.\n    environment = None\n    if get_safe_settings:\n        try:\n            environment = get_safe_settings()\n        except django.core.exceptions.ImproperlyConfigured as exc:\n            # Django not setup correctly\n            logger.debug(\n                \"Celery integration does not have django configured properly: %r\", exc\n            )\n            pass\n        except Exception as exc:\n            logger.debug(\n                \"Celery task_failure callback exception: %r\", exc, exc_info=exc\n            )\n            pass\n\n    # Celery occassionally will send the traceback as a string rather\n    # than a Stack trace object as the docs indicate. In that case,\n    # fall back to the billiard ExceptionInfo instance\n    traceback = (\n        traceback if traceback and not isinstance(traceback, string_type) else einfo.tb\n    )\n    exc_info = (exception.__class__, exception, traceback)\n    ErrorMonitor.send(\n        exc_info,\n        environment=environment,\n        custom_params=custom_params,\n        custom_controller=custom_controller,\n    )\n\n\ndef install(app=None):\n    if app is not None:\n        copy_configuration(app)\n\n    installed = scout_apm.core.install()\n    if not installed:\n        return\n\n    before_task_publish.connect(before_task_publish_callback)\n    task_prerun.connect(task_prerun_callback)\n    task_failure.connect(task_failure_callback)\n    task_postrun.connect(task_postrun_callback)\n\n\ndef copy_configuration(app):\n    prefix = \"scout_\"\n    prefix_len = len(prefix)\n\n    to_set = {}\n    for key, value in app.conf.items():\n        key_lower = key.lower()\n        if key_lower.startswith(prefix) and len(key_lower) > prefix_len:\n            scout_key = key_lower[prefix_len:]\n            to_set[scout_key] = value\n\n    scout_config.set(**to_set)\n\n\ndef uninstall():\n    before_task_publish.disconnect(before_task_publish_callback)\n    task_prerun.disconnect(task_prerun_callback)\n    task_postrun.disconnect(task_postrun_callback)\n    task_failure.disconnect(task_failure_callback)\n", "path": "src/scout_apm/celery.py"}], "after_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport datetime as dt\nimport logging\n\nfrom celery.signals import before_task_publish, task_failure, task_postrun, task_prerun\n\ntry:\n    import django\n\n    if django.VERSION < (3, 1):\n        from django.views.debug import get_safe_settings\n    else:\n        from django.views.debug import SafeExceptionReporterFilter\n\n        def get_safe_settings():\n            return SafeExceptionReporterFilter().get_safe_settings()\n\nexcept ImportError:\n    # Django not installed\n    get_safe_settings = None\n\nimport scout_apm.core\nfrom scout_apm.compat import datetime_to_timestamp, string_type\nfrom scout_apm.core.config import scout_config\nfrom scout_apm.core.error import ErrorMonitor\nfrom scout_apm.core.tracked_request import TrackedRequest\n\nlogger = logging.getLogger(__name__)\n\n\ndef before_task_publish_callback(headers=None, properties=None, **kwargs):\n    if \"scout_task_start\" not in headers:\n        headers[\"scout_task_start\"] = datetime_to_timestamp(dt.datetime.utcnow())\n\n\ndef task_prerun_callback(task=None, **kwargs):\n    tracked_request = TrackedRequest.instance()\n    tracked_request.is_real_request = True\n\n    start = getattr(task.request, \"scout_task_start\", None)\n    if start is not None:\n        now = datetime_to_timestamp(dt.datetime.utcnow())\n        try:\n            queue_time = now - start\n        except TypeError:\n            pass\n        else:\n            tracked_request.tag(\"queue_time\", queue_time)\n\n    task_id = getattr(task.request, \"id\", None)\n    if task_id:\n        tracked_request.tag(\"task_id\", task_id)\n    parent_task_id = getattr(task.request, \"parent_id\", None)\n    if parent_task_id:\n        tracked_request.tag(\"parent_task_id\", parent_task_id)\n\n    delivery_info = getattr(task.request, \"delivery_info\", None)\n    if delivery_info:\n        tracked_request.tag(\"is_eager\", delivery_info.get(\"is_eager\", False))\n        tracked_request.tag(\"exchange\", delivery_info.get(\"exchange\", \"unknown\"))\n        tracked_request.tag(\"priority\", delivery_info.get(\"priority\", \"unknown\"))\n        tracked_request.tag(\"routing_key\", delivery_info.get(\"routing_key\", \"unknown\"))\n        tracked_request.tag(\"queue\", delivery_info.get(\"queue\", \"unknown\"))\n\n    tracked_request.start_span(operation=(\"Job/\" + task.name))\n\n\ndef task_postrun_callback(task=None, **kwargs):\n    tracked_request = TrackedRequest.instance()\n    tracked_request.stop_span()\n\n\ndef task_failure_callback(\n    sender,\n    task_id=None,\n    exception=None,\n    args=None,\n    kwargs=None,\n    traceback=None,\n    einfo=None,\n    **remaining\n):\n    tracked_request = TrackedRequest.instance()\n    tracked_request.tag(\"error\", \"true\")\n\n    custom_controller = sender.name\n    custom_params = {\n        \"celery\": {\n            \"task_id\": task_id,\n            \"args\": args,\n            \"kwargs\": kwargs,\n        }\n    }\n\n    # Look up the django settings if populated.\n    environment = None\n    if get_safe_settings:\n        try:\n            environment = get_safe_settings()\n        except django.core.exceptions.ImproperlyConfigured as exc:\n            # Django not setup correctly\n            logger.debug(\n                \"Celery integration does not have django configured properly: %r\", exc\n            )\n            pass\n        except Exception as exc:\n            logger.debug(\n                \"Celery task_failure callback exception: %r\", exc, exc_info=exc\n            )\n            pass\n\n    # Celery occassionally will send the traceback as a string rather\n    # than a Stack trace object as the docs indicate. In that case,\n    # fall back to the billiard ExceptionInfo instance\n    traceback = (\n        traceback if traceback and not isinstance(traceback, string_type) else einfo.tb\n    )\n    exc_info = (exception.__class__, exception, traceback)\n    ErrorMonitor.send(\n        exc_info,\n        environment=environment,\n        custom_params=custom_params,\n        custom_controller=custom_controller,\n    )\n\n\ndef install(app=None):\n    if app is not None:\n        copy_configuration(app)\n\n    installed = scout_apm.core.install()\n    if not installed:\n        return\n\n    before_task_publish.connect(before_task_publish_callback)\n    task_prerun.connect(task_prerun_callback)\n    task_failure.connect(task_failure_callback)\n    task_postrun.connect(task_postrun_callback)\n\n\ndef copy_configuration(app):\n    prefix = \"scout_\"\n    prefix_len = len(prefix)\n\n    to_set = {}\n    for key, value in app.conf.items():\n        key_lower = key.lower()\n        if key_lower.startswith(prefix) and len(key_lower) > prefix_len:\n            scout_key = key_lower[prefix_len:]\n            to_set[scout_key] = value\n\n    scout_config.set(**to_set)\n\n\ndef uninstall():\n    before_task_publish.disconnect(before_task_publish_callback)\n    task_prerun.disconnect(task_prerun_callback)\n    task_postrun.disconnect(task_postrun_callback)\n    task_failure.disconnect(task_failure_callback)\n", "path": "src/scout_apm/celery.py"}]}