We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Celery exceptions cause error in capture_stacktrace
The latest version of celery, 5.2.1, raises some exceptions in an unexpected way.

For this particular case, `tb` was a string of value:

```
'Traceback (most recent call last):\n  File "/app/.heroku/python/lib/python3.9/site-packages/billiard/pool.py", line 366, in workloop\n    put((READY, (job, i, result, inqW_fd)))\n  File "/app/.heroku/python/lib/python3.9/site-packages/billiard/queues.py", line 366, in put\n    self.send_payload(ForkingPickler.dumps(obj))\n  File "/app/.heroku/python/lib/python3.9/site-packages/billiard/reduction.py", line 56, in dumps\n    cls(buf, protocol).dump(obj)\nbilliard.pool.MaybeEncodingError: Error sending res...'
```

Stacktrace of error
```
AttributeError: 'str' object has no attribute 'tb_frame'
  File "celery/utils/dispatch/signal.py", line 276, in send
    response = receiver(signal=self, sender=sender, **named)
  File "scout_apm/celery.py", line 114, in task_failure_callback
    ErrorMonitor.send(
  File "scout_apm/core/error.py", line 69, in send
    for frame in capture_stacktrace(traceback)
  File "scout_apm/core/backtrace.py", line 132, in capture_stacktrace
    return list(reversed(list(itertools.islice(walker, LIMIT))))
  File "scout_apm/core/backtrace.py", line 75, in stacktrace_walker
    for frame, lineno in traceback.walk_tb(tb):
  File "traceback.py", line 312, in walk_tb
    yield tb.tb_frame, tb.tb_lineno
```

--- 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 
21 except ImportError:
22     # Django not installed
23     get_safe_settings = None
24 
25 import scout_apm.core
26 from scout_apm.compat import datetime_to_timestamp
27 from scout_apm.core.config import scout_config
28 from scout_apm.core.error import ErrorMonitor
29 from scout_apm.core.tracked_request import TrackedRequest
30 
31 logger = logging.getLogger(__name__)
32 
33 
34 def before_task_publish_callback(headers=None, properties=None, **kwargs):
35     if "scout_task_start" not in headers:
36         headers["scout_task_start"] = datetime_to_timestamp(dt.datetime.utcnow())
37 
38 
39 def task_prerun_callback(task=None, **kwargs):
40     tracked_request = TrackedRequest.instance()
41     tracked_request.is_real_request = True
42 
43     start = getattr(task.request, "scout_task_start", None)
44     if start is not None:
45         now = datetime_to_timestamp(dt.datetime.utcnow())
46         try:
47             queue_time = now - start
48         except TypeError:
49             pass
50         else:
51             tracked_request.tag("queue_time", queue_time)
52 
53     task_id = getattr(task.request, "id", None)
54     if task_id:
55         tracked_request.tag("task_id", task_id)
56     parent_task_id = getattr(task.request, "parent_id", None)
57     if parent_task_id:
58         tracked_request.tag("parent_task_id", parent_task_id)
59 
60     delivery_info = task.request.delivery_info
61     tracked_request.tag("is_eager", delivery_info.get("is_eager", False))
62     tracked_request.tag("exchange", delivery_info.get("exchange", "unknown"))
63     tracked_request.tag("priority", delivery_info.get("priority", "unknown"))
64     tracked_request.tag("routing_key", delivery_info.get("routing_key", "unknown"))
65     tracked_request.tag("queue", delivery_info.get("queue", "unknown"))
66 
67     tracked_request.start_span(operation=("Job/" + task.name))
68 
69 
70 def task_postrun_callback(task=None, **kwargs):
71     tracked_request = TrackedRequest.instance()
72     tracked_request.stop_span()
73 
74 
75 def task_failure_callback(
76     sender,
77     task_id=None,
78     exception=None,
79     args=None,
80     kwargs=None,
81     traceback=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     exc_info = (exception.__class__, exception, traceback)
114     ErrorMonitor.send(
115         exc_info,
116         environment=environment,
117         custom_params=custom_params,
118         custom_controller=custom_controller,
119     )
120 
121 
122 def install(app=None):
123     if app is not None:
124         copy_configuration(app)
125 
126     installed = scout_apm.core.install()
127     if not installed:
128         return
129 
130     before_task_publish.connect(before_task_publish_callback)
131     task_prerun.connect(task_prerun_callback)
132     task_failure.connect(task_failure_callback)
133     task_postrun.connect(task_postrun_callback)
134 
135 
136 def copy_configuration(app):
137     prefix = "scout_"
138     prefix_len = len(prefix)
139 
140     to_set = {}
141     for key, value in app.conf.items():
142         key_lower = key.lower()
143         if key_lower.startswith(prefix) and len(key_lower) > prefix_len:
144             scout_key = key_lower[prefix_len:]
145             to_set[scout_key] = value
146 
147     scout_config.set(**to_set)
148 
149 
150 def uninstall():
151     before_task_publish.disconnect(before_task_publish_callback)
152     task_prerun.disconnect(task_prerun_callback)
153     task_postrun.disconnect(task_postrun_callback)
154     task_failure.disconnect(task_failure_callback)
155
```
--- 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
@@ -23,7 +23,7 @@
     get_safe_settings = None
 
 import scout_apm.core
-from scout_apm.compat import datetime_to_timestamp
+from scout_apm.compat import datetime_to_timestamp, string_type
 from scout_apm.core.config import scout_config
 from scout_apm.core.error import ErrorMonitor
 from scout_apm.core.tracked_request import TrackedRequest
@@ -79,6 +79,7 @@
     args=None,
     kwargs=None,
     traceback=None,
+    einfo=None,
     **remaining
 ):
     tracked_request = TrackedRequest.instance()
@@ -110,6 +111,12 @@
             )
             pass
 
+    # Celery occassionally will send the traceback as a string rather
+    # than a Stack trace object as the docs indicate. In that case,
+    # fall back to the billiard ExceptionInfo instance
+    traceback = (
+        traceback if traceback and not isinstance(traceback, string_type) else einfo.tb
+    )
     exc_info = (exception.__class__, exception, traceback)
     ErrorMonitor.send(
         exc_info,

{"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@@ -23,7 +23,7 @@\n     get_safe_settings = None\n \n import scout_apm.core\n-from scout_apm.compat import datetime_to_timestamp\n+from scout_apm.compat import datetime_to_timestamp, string_type\n from scout_apm.core.config import scout_config\n from scout_apm.core.error import ErrorMonitor\n from scout_apm.core.tracked_request import TrackedRequest\n@@ -79,6 +79,7 @@\n     args=None,\n     kwargs=None,\n     traceback=None,\n+    einfo=None,\n     **remaining\n ):\n     tracked_request = TrackedRequest.instance()\n@@ -110,6 +111,12 @@\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", "issue": "Celery exceptions cause error in capture_stacktrace\nThe latest version of celery, 5.2.1, raises some exceptions in an unexpected way.\r\n\r\nFor this particular case, `tb` was a string of value:\r\n\r\n```\r\n'Traceback (most recent call last):\\n  File \"/app/.heroku/python/lib/python3.9/site-packages/billiard/pool.py\", line 366, in workloop\\n    put((READY, (job, i, result, inqW_fd)))\\n  File \"/app/.heroku/python/lib/python3.9/site-packages/billiard/queues.py\", line 366, in put\\n    self.send_payload(ForkingPickler.dumps(obj))\\n  File \"/app/.heroku/python/lib/python3.9/site-packages/billiard/reduction.py\", line 56, in dumps\\n    cls(buf, protocol).dump(obj)\\nbilliard.pool.MaybeEncodingError: Error sending res...'\r\n```\r\n\r\nStacktrace of error\r\n```\r\nAttributeError: 'str' object has no attribute 'tb_frame'\r\n  File \"celery/utils/dispatch/signal.py\", line 276, in send\r\n    response = receiver(signal=self, sender=sender, **named)\r\n  File \"scout_apm/celery.py\", line 114, in task_failure_callback\r\n    ErrorMonitor.send(\r\n  File \"scout_apm/core/error.py\", line 69, in send\r\n    for frame in capture_stacktrace(traceback)\r\n  File \"scout_apm/core/backtrace.py\", line 132, in capture_stacktrace\r\n    return list(reversed(list(itertools.islice(walker, LIMIT))))\r\n  File \"scout_apm/core/backtrace.py\", line 75, in stacktrace_walker\r\n    for frame, lineno in traceback.walk_tb(tb):\r\n  File \"traceback.py\", line 312, in walk_tb\r\n    yield tb.tb_frame, tb.tb_lineno\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\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\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    **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    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\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"}]}