prompt: 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()
  
  ```
  
golden_diff: 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,
  
verification_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"}]}