diff --git a/renku_notebooks/api/classes/server.py b/renku_notebooks/api/classes/server.py index 3f7e5e2ff..bd9ccd66e 100644 --- a/renku_notebooks/api/classes/server.py +++ b/renku_notebooks/api/classes/server.py @@ -2,7 +2,7 @@ import gitlab from itertools import chain from kubernetes import client -from kubernetes.client.rest import ApiException +from kubernetes.client.exceptions import ApiException from kubernetes.client.models import V1DeleteOptions import base64 import json @@ -475,24 +475,38 @@ def stop(self, forced=False): else: return status - def get_logs(self, max_log_lines=0, container_name="jupyter-server"): - """Get the logs of the k8s pod that runs the user server.""" + def get_logs(self, max_log_lines=0): + """Get the logs of all containers in the server pod.""" js = self.js if js is None: return None + output = {} pod_name = js["status"]["mainPod"]["name"] - if max_log_lines == 0: - logs = self._k8s_client.read_namespaced_pod_log( - pod_name, self._k8s_namespace, container=container_name - ) + all_containers = js["status"]["mainPod"]["status"].get( + "containerStatuses", [] + ) + js["status"]["mainPod"]["status"].get("initContainerStatuses", []) + for container in all_containers: + container_name = container["name"] + try: + logs = self._k8s_client.read_namespaced_pod_log( + pod_name, + self._k8s_namespace, + container=container_name, + tail_lines=max_log_lines if max_log_lines > 0 else None, + timestamps=True, + ) + except ApiException as err: + if err.status in [400, 404]: + continue # container does not exist or is not ready yet + else: + raise + else: + output[container_name] = logs + + if len(output.keys()) == 0: + return None else: - logs = self._k8s_client.read_namespaced_pod_log( - pod_name, - self._k8s_namespace, - tail_lines=max_log_lines, - container=container_name, - ) - return logs + return output @property def server_url(self): diff --git a/renku_notebooks/api/notebooks.py b/renku_notebooks/api/notebooks.py index 05c38ed27..a05d04ab8 100644 --- a/renku_notebooks/api/notebooks.py +++ b/renku_notebooks/api/notebooks.py @@ -350,9 +350,6 @@ def server_logs(user, server_name): content: application/json: schema: ServerLogs - example: - - Line 1 of logs - - Line 2 of logs 404: description: The specified server does not exist. tags: @@ -363,10 +360,7 @@ def server_logs(user, server_name): max_lines = request.args.get("max_lines", default=250, type=int) logs = server.get_logs(max_lines) if logs is not None: - return ( - ServerLogs().dumps({"items": str.splitlines(logs)}), - 200, - ) + return ServerLogs().dump(logs) return make_response(jsonify({"messages": {"error": "Cannot find server"}}), 404) diff --git a/renku_notebooks/api/schemas.py b/renku_notebooks/api/schemas.py index cc7da777e..d578d8bce 100644 --- a/renku_notebooks/api/schemas.py +++ b/renku_notebooks/api/schemas.py @@ -647,18 +647,17 @@ class ServerOptionsUI(Schema): # cloudstorage = fields.Nested(CloudStorageServerOption(), required=True) -class ServerLogs(Schema): - """ - The list of k8s logs (one log line per list element) - for the pod that runs the jupyter server. - """ +_ServerLogs = Schema.from_dict({"jupyter-server": fields.String(required=False)}) - items = fields.List(fields.Str()) - @post_dump - @post_load - def remove_item_key(self, data, **kwargs): - return data.get("items", []) +class ServerLogs(_ServerLogs): + class Meta: + unknown = INCLUDE # only affects loading, not dumping + + @post_dump(pass_original=True) + def keep_unknowns(self, output, orig, **kwargs): + output = {**orig, **output} + return output def _in_range(value, value_range):