From 2339e855bbbaa217cd24a87b53996811fab899e0 Mon Sep 17 00:00:00 2001
From: Jens Langhammer <jens.langhammer@beryju.org>
Date: Fri, 16 Oct 2020 16:00:24 +0200
Subject: [PATCH] *: Improve MonitoredTasks' error capture

---
 passbook/admin/tasks.py        | 2 +-
 passbook/lib/tasks.py          | 9 +++++++--
 passbook/outposts/tasks.py     | 2 +-
 passbook/sources/ldap/tasks.py | 2 +-
 passbook/stages/email/tasks.py | 2 +-
 5 files changed, 11 insertions(+), 6 deletions(-)

diff --git a/passbook/admin/tasks.py b/passbook/admin/tasks.py
index a0551eb3a..4c9532a4c 100644
--- a/passbook/admin/tasks.py
+++ b/passbook/admin/tasks.py
@@ -27,4 +27,4 @@ def update_latest_version(self: MonitoredTask):
         )
     except (RequestException, IndexError) as exc:
         cache.set(VERSION_CACHE_KEY, "0.0.0", VERSION_CACHE_TIMEOUT)
-        self.set_status(TaskResult(TaskResultStatus.ERROR, [str(exc)]))
+        self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
diff --git a/passbook/lib/tasks.py b/passbook/lib/tasks.py
index 0e837c559..55d1aed6f 100644
--- a/passbook/lib/tasks.py
+++ b/passbook/lib/tasks.py
@@ -2,6 +2,7 @@
 from dataclasses import dataclass, field
 from datetime import datetime
 from enum import Enum
+from traceback import format_tb
 from typing import Any, Dict, List, Optional
 
 from celery import Task
@@ -25,11 +26,15 @@ class TaskResult:
 
     messages: List[str] = field(default_factory=list)
 
-    error: Optional[Exception] = field(default=None)
-
     # Optional UID used in cache for tasks that run in different instances
     uid: Optional[str] = field(default=None)
 
+    def with_error(self, exc: Exception) -> "TaskResult":
+        """Since errors might not always be pickle-able, set the traceback"""
+        self.messages.extend(format_tb(exc.__traceback__))
+        self.messages.append(str(exc))
+        return self
+
 
 @dataclass
 class TaskInfo:
diff --git a/passbook/outposts/tasks.py b/passbook/outposts/tasks.py
index 7b81f8e52..b4a3cd5df 100644
--- a/passbook/outposts/tasks.py
+++ b/passbook/outposts/tasks.py
@@ -45,7 +45,7 @@ def outpost_controller(self: MonitoredTask, outpost_pk: str):
                 logs = ProxyDockerController(outpost).run_with_logs()
     except ControllerException as exc:
         self.set_status(
-            TaskResult(TaskResultStatus.ERROR, [str(exc)], exc, uid=outpost.name)
+            TaskResult(TaskResultStatus.ERROR, uid=outpost.name).with_error(exc)
         )
     else:
         self.set_status(TaskResult(TaskResultStatus.SUCCESSFUL, logs, uid=outpost.name))
diff --git a/passbook/sources/ldap/tasks.py b/passbook/sources/ldap/tasks.py
index 3652f901d..eb5ddfd2e 100644
--- a/passbook/sources/ldap/tasks.py
+++ b/passbook/sources/ldap/tasks.py
@@ -35,4 +35,4 @@ def ldap_sync(self: MonitoredTask, source_pk: int):
             )
         )
     except LDAPException as exc:
-        self.set_status(TaskResult(TaskResultStatus.ERROR, [str(exc)], exc))
+        self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
diff --git a/passbook/stages/email/tasks.py b/passbook/stages/email/tasks.py
index 89a2ce813..87c8a2f69 100644
--- a/passbook/stages/email/tasks.py
+++ b/passbook/stages/email/tasks.py
@@ -62,5 +62,5 @@ def send_mail(self: MonitoredTask, email_stage_pk: int, message: Dict[Any, Any])
             )
         )
     except (SMTPException, ConnectionError) as exc:
-        self.set_status(TaskResult(TaskResultStatus.ERROR, [str(exc)], exc))
+        self.set_status(TaskResult(TaskResultStatus.ERROR).with_error(exc))
         raise exc