Repository: OpenMined/PySyft
Pull request: Add support for Hagrid logs & set --tail=false to default
## Description
fixes #6971 

This PR adds a new command to hagrid, which is `hagrid logs`, and generates an output like so. 

Also, this PR sets --tail=false to the default behavior in hagrid when launching domain nodes.

<img width="501" alt="Screenshot 2022-11-10 at 02 18 33" src="https://user-images.githubusercontent.com/11381249/200977432-13c6a10a-b0a5-4faf-be07-898170aec45c.png">


Problem statement:
"hagrid launch --tail=False" should be default + a table that lists containers and the commands to see their live logs
The new user experience when --tail=False is incredible and by far the best experience for most users (everyone except devs?), especially since there's a definitive "you're fully launched' moment, as opposed to just watching logs until it stops. --tail=false should be the default experience except when you're in dev mode.

That said - finding logs is hard and scary to docker noobs and they're going to experience bugs. I think it would be great if we added a table to the final output of hagrid launch --tail-False which listed every container with a single command for how to see the logs. (and a highlight for where "if you get errors they'll be here or here" pointing somehow towards the streaming and backend containers. 

STRETCH: link to Rasswanth's video for finding logs in case people need it.

Hints from issue discussion:
I agree we should default to `tail=false`. To provide more access to information we should build `hagrid status` which will show all the known nodes and allow a user to explore them further, either by checking logs or running commands like, land, update, backup, etc. A small step in that direction would be a `hagrid logs` command which could provide a quick way to access the logs of different containers.

In the meantime some helpful print statements is likely the best short term solution.
@madhavajay @iamtrask for this improvement, I have a PR open for this and have built the first working version of `hagrid logs`. 
 See PR [Here](https://github.com/OpenMined/PySyft/pull/7011) 

Please take a look here to leave a comment, if there's how we can make more improvements especially related to the wording of the output etc. 

I also assumed it will be best to default `--tail=false` even in dev mode, except explicitly set to `true.`

@madhavajay I am also thinking for the hagrid logs command we should have an option for log file dump to a directory.. something like `hagrid logs {{DOMAIN_NAME}} --dump_file=true` or something. I'd assume, this can be helpful, maybe? 
@bitsofsteve great work but please stick to the scope of the ticket for now. This ticket only needs `--tail=fase` and a list of the docker commands.
@bitsofsteve great work.
I tested it and it works. I think there are some small changes that we want to do before merge.

It seems like it shows the health check twice?
<img width="611" alt="Screen Shot 2022-11-18 at 3 52 15 pm" src="https://user-images.githubusercontent.com/2882739/202630865-bd43d41f-759e-40d2-954e-43bc9a4c4b10.png">

The description on logging doesnt wrap properly.
<img width="667" alt="Screen Shot 2022-11-18 at 3 53 06 pm" src="https://user-images.githubusercontent.com/2882739/202630905-a6cf8871-291e-4d5b-aa6b-a0a925b1eeb9.png">
I think what you want to do here is remove the line breaks from the actual quoted text and instead break up the string like this to format it:

```python
long_string = (
+ "a really long line here"
+ "more long line continued here\n" # \n for explicit line breaks
)
```

That might fix it.
<img width="764" alt="Screen Shot 2022-11-18 at 3 52 59 pm" src="https://user-images.githubusercontent.com/2882739/202630888-b48ac71a-73e2-4258-8584-a6fb3536e05d.png">

Commit message: Merge branch 'dev' into issue_6971

Patch to review:
diff --git a/packages/hagrid/hagrid/cli.py b/packages/hagrid/hagrid/cli.py
index f811a2184d8..7a996e4775c 100644
--- a/packages/hagrid/hagrid/cli.py
+++ b/packages/hagrid/hagrid/cli.py
@@ -1,4 +1,5 @@
 # stdlib
+from collections import namedtuple
 import json
 import os
 from pathlib import Path
@@ -183,7 +184,7 @@ def clean(location: str) -> None:
 )
 @click.option(
     "--tail",
-    default="true",
+    default="false",
     required=False,
     type=str,
     help="Optional: don't tail logs on launch",
@@ -285,7 +286,7 @@ def clean(location: str) -> None:
 )
 @click.option(
     "--tag",
-    default=None,
+    default="latest",
     required=False,
     type=str,
     help="Optional: container image tag to use",
@@ -388,6 +389,13 @@ def launch(args: TypeTuple[str], **kwargs: Any) -> None:
                 port = match_port.group().replace("HTTP_PORT=", "")
                 check_status("localhost" + ":" + port)
 
+            node_name = verb.get_named_term_type(name="node_name").raw_input
+            rich.get_console().print(
+                rich.panel.Panel.fit(
+                    f"🚨🚨🚨 To view container logs run [bold red] hagrid logs {node_name} [/bold red]\t"
+                )
+            )
+
     except Exception as e:
         print(f"Error: {e}\n\n")
         return
@@ -3511,3 +3519,81 @@ def ssh(ip_address: str, cmd: str) -> None:
 
 
 cli.add_command(ssh)
+
+
+# Add hagrid logs command to the CLI
+@click.command(help="Get the logs of the HAGrid service")
+@click.argument("domain_name", type=str)
+def logs(domain_name: str) -> None:  # nosec
+
+    """
+    host_name = get_host_name(domain_name)
+
+    """
+    container_ids = (
+        subprocess.check_output(  # nosec
+            f"docker ps -qf name=^{domain_name}-*", shell=True
+        )
+        .decode("utf-8")
+        .split()
+    )
+    Container = namedtuple("Container", "id name logs")
+    container_names = []
+    for container in container_ids:
+        container_name = (
+            subprocess.check_output(  # nosec
+                "docker inspect --format '{{.Name}}' " + container, shell=True
+            )
+            .decode("utf-8")
+            .strip()
+            .replace("/", "")
+        )
+        log_command = "docker logs -f " + container_name
+        container_names.append(
+            Container(id=container, name=container_name, logs=log_command)
+        )
+    # Generate a table of the containers and their logs with Rich
+    table = rich.table.Table(title="Container Logs")
+    table.add_column("Container ID", justify="center", style="cyan", no_wrap=True)
+    table.add_column("Container Name", justify="right", style="cyan", no_wrap=True)
+    table.add_column("Log Command", justify="right", style="cyan", no_wrap=True)
+    for container in container_names:  # type: ignore
+        table.add_row(container.id, container.name, container.logs)  # type: ignore
+    console = rich.console.Console()
+    console.print(table)
+    # Print instructions on how to view the logs
+    console.print(
+        rich.panel.Panel(
+            long_string,
+            title="How to view logs",
+            border_style="white",
+            expand=False,
+            padding=1,
+            highlight=True,
+        )
+    )
+
+
+long_string = (
+    "ℹ [bold green]To view the live logs of a container,copy the log command and paste it into your terminal.[/bold green]\n"  # noqa: E501
+    + "\n"
+    + "ℹ [bold green]The logs will be streamed to your terminal until you exit the command.[/bold green]\n"
+    + "\n"
+    + "ℹ [bold green]To exit the logs, press CTRL+C.[/bold green]\n"
+    + "\n"
+    + "🚨 The [bold white]backend & backend_stream[/bold white] [bold green]containers are the most important to monitor for debugging.[/bold green]\n"  # noqa: E501
+    + "\n"
+    + "               [bold white]--------------- Ctop 🦾 -------------------------[/bold white]\n"
+    + "\n"
+    + "🧠 To learn about using [bold white]ctop[/bold white] to monitor your containers,visit https://www.youtube.com/watch?v=BJhlCxerQP4n \n"  # noqa: E501
+    + "\n"
+    + "               [bold white]----------------- How to view this. 🙂 ---------------[/bold white]\n"
+    + "\n"
+    + """ℹ [bold green]To view this panel again, run the command [bold white]hagrid logs {{DOMAIN_NAME}}[/bold white] [/bold green]\n"""  # noqa: E501
+    + "\n"
+    + """🚨 DOMAIN_NAME above is the name of your Hagrid deployment,without the curly braces. E.g hagrid logs canada [bold green]\n"""  # noqa: E501
+    + "\n"
+    + "               [bold green]HAPPY DEBUGGING! 🐛🐞🦗🦟🦠🦠🦠[/bold green]\n                      "
+)
+
+cli.add_command(logs)


Code review comments:
diff_hunk: @@ -3511,3 +3519,81 @@ def ssh(ip_address: str, cmd: str) -> None:
 
 
 cli.add_command(ssh)
+
+
+# Add hagrid logs command to the CLI
+@click.command(help="Get the logs of the HAGrid service")
+@click.argument("domain_name", type=str)
+def logs(domain_name: str) -> None:  # nosec
+
+    """
+    host_name = get_host_name(domain_name)
path: packages/hagrid/hagrid/cli.py
text: @user1:
Q: should this comment be deleted?

diff_hunk: @@ -3511,3 +3519,81 @@ def ssh(ip_address: str, cmd: str) -> None:
 
 
 cli.add_command(ssh)
+
+
+# Add hagrid logs command to the CLI
+@click.command(help="Get the logs of the HAGrid service")
+@click.argument("domain_name", type=str)
+def logs(domain_name: str) -> None:  # nosec
+
+    """
+    host_name = get_host_name(domain_name)
+
+    """
+    container_ids = (
+        subprocess.check_output(  # nosec
+            f"docker ps -qf name=^{domain_name}-*", shell=True
+        )
+        .decode("utf-8")
+        .split()
+    )
+    Container = namedtuple("Container", "id name logs")
+    container_names = []
+    for container in container_ids:
+        container_name = (
+            subprocess.check_output(  # nosec
+                "docker inspect --format '{{.Name}}' " + container, shell=True
+            )
+            .decode("utf-8")
+            .strip()
+            .replace("/", "")
+        )
+        log_command = "docker logs -f " + container_name
+        container_names.append(
+            Container(id=container, name=container_name, logs=log_command)
+        )
+    # Generate a table of the containers and their logs with Rich
+    table = rich.table.Table(title="Container Logs")
+    table.add_column("Container ID", justify="center", style="cyan", no_wrap=True)
+    table.add_column("Container Name", justify="right", style="cyan", no_wrap=True)
+    table.add_column("Log Command", justify="right", style="cyan", no_wrap=True)
+    for container in container_names:  # type: ignore
+        table.add_row(container.id, container.name, container.logs)  # type: ignore
+    console = rich.console.Console()
+    console.print(table)
+    # Print instructions on how to view the logs
+    console.print(
+        rich.panel.Panel(
+            long_string,
+            title="How to view logs",
+            border_style="white",
+            expand=False,
+            padding=1,
+            highlight=True,
+        )
+    )
+
+
+long_string = (
+    "ℹ [bold green]To view the live logs of a container,copy the log command and paste it into your terminal.[/bold green]\n"  # noqa: E501
+    + "\n"
+    + "ℹ [bold green]The logs will be streamed to your terminal until you exit the command.[/bold green]\n"
+    + "\n"
+    + "ℹ [bold green]To exit the logs, press CTRL+C.[/bold green]\n"
+    + "\n"
+    + "🚨 The [bold white]backend & backend_stream[/bold white] [bold green]containers are the most important to monitor for debugging.[/bold green]\n"  # noqa: E501
path: packages/hagrid/hagrid/cli.py
text: @user1:
MAYBE: we could specifiy celery worker also.