Repository: fractal-analytics-platform/fractal-client (language: python)

Issue:
`fractal workflow job-status` error log formatting
When there is an error in a task, the output of `fractal workflow job-status` is formatted in a way that makes it fairly hard to read the log. Also, I'm not fully sure whether the log is always displayed completely & in the correct order (to be verified).

Would be good to find a way to make the log display more readable.


Discussion / hints:
![Screenshot 2022-12-15 at 16 31 32](https://user-images.githubusercontent.com/18033446/207901372-0773cfea-b7da-4f06-892c-b61f72dcb97c.png)

Here's an example of how those logs can look like

Fix patch:
diff --git a/fractal/cmd/_workflow.py b/fractal/cmd/_workflow.py
index 08c3cf4d2..4e5768f87 100644
--- a/fractal/cmd/_workflow.py
+++ b/fractal/cmd/_workflow.py
@@ -24,6 +24,7 @@ async def workflow_query_job_status(
     client: AuthClient,
     job_id: int,
     batch: bool = False,
+    do_not_separate_logs: bool = False,
     **kwargs,
 ) -> BaseInterface:
     """
@@ -37,7 +38,15 @@ async def workflow_query_job_status(
     if batch:
         return PrintInterface(retcode=0, data=job.status)
     else:
-        return RichJsonInterface(retcode=0, data=job.sanitised_dict())
+        data = job.sanitised_dict()
+        if do_not_separate_logs or (job.log is None):
+            return RichJsonInterface(retcode=0, data=data)
+        else:
+            log = data.pop("log")
+            extra_lines = ["\nThis is the job log:\n", log]
+            return RichJsonInterface(
+                retcode=0, data=data, extra_lines=extra_lines
+            )
 
 
 async def workflow_new(
diff --git a/fractal/interface.py b/fractal/interface.py
index 5223ee26d..909b56a67 100644
--- a/fractal/interface.py
+++ b/fractal/interface.py
@@ -1,5 +1,7 @@
 from typing import Any
 from typing import Dict
+from typing import Optional
+from typing import Sequence
 
 from rich import print_json
 from rich.console import Console
@@ -34,11 +36,22 @@ class RichJsonInterface(BaseInterface):
     Output json using rich.print_json
     """
 
-    def __init__(self, retcode: int, data: Dict[str, Any]):
+    def __init__(
+        self,
+        retcode: int,
+        data: Dict[str, Any],
+        extra_lines: Optional[Sequence[str]] = None,
+    ):
         super().__init__(retcode, data)
+        if extra_lines is None:
+            self.extra_lines = None
+        else:
+            self.extra_lines = "\n".join(extra_lines)
 
     def show(self, *args, **kwargs):
         print_json(data=self.data)
+        if self.extra_lines:
+            print(self.extra_lines)
 
 
 class RichConsoleInterface(BaseInterface):
diff --git a/fractal/parser.py b/fractal/parser.py
index 4b43a85f4..8e8ccb7dd 100644
--- a/fractal/parser.py
+++ b/fractal/parser.py
@@ -452,6 +452,12 @@
     "--job-id",
     help="Id of the job",
 )
+workflow_job_status_parser.add_argument(
+    "--do-not-separate-logs",
+    dest="do_not_separate_logs",
+    help="Show the job logs in the main output, instead of a separate field",
+    action="store_true",
+)
 
 
 # VERSION GROUP


Test patch:
diff --git a/tests/test_workflow.py b/tests/test_workflow.py
index 92fd061c1..1baf96dd0 100644
--- a/tests/test_workflow.py
+++ b/tests/test_workflow.py
@@ -353,16 +353,28 @@ async def test_workflow_apply(
     assert res.retcode == 0
     job_id = res.data["id"]
 
-    # Note: we may want to use a while loop to wait for the failure of the
-    # workflow execution
-
     # Verify that status is failed, and that there is a log
-    cmd = f"workflow job-status --job-id {job_id}"
-    res = await invoke(cmd)
-    assert res.retcode == 0
+    cmd = f"workflow job-status --job-id {job_id} --do-not-separate-logs"
+    starting_time = time.perf_counter()
+    while True:
+        res = await invoke(cmd)
+        debug(res.data)
+        assert res.retcode == 0
+        if res.data["status"] == "failed":
+            break
+        await asyncio.sleep(1)
+        assert time.perf_counter() - starting_time < TIMEOUT
     debug(res.data)
-    assert res.data["status"] == "failed"
     assert res.data["log"] is not None
     # Note: the failing task is not added to the history
     assert len(res.data["history"]) > 0
-    print(res.data["log"])
+
+    # Test job-status default (without --do-not-separate-logs)
+    cmd = f"workflow job-status --job-id {job_id}"
+    res = await invoke(cmd)
+    assert res.retcode == 0
+    debug(res.data)
+    debug(res.extra_lines)
+    assert "log" not in res.data
+    assert res.extra_lines
+    res.show()