From ba01168684c8459701e7ec26d1ec37084062e850 Mon Sep 17 00:00:00 2001 From: ljf Date: Tue, 3 Apr 2018 20:38:56 +0200 Subject: [PATCH] [enh] Allow to display all operation logs with one command --- data/actionsmap/yunohost.yml | 25 ++++---- locales/en.json | 2 +- src/yunohost/app.py | 8 +-- src/yunohost/{journals.py => log.py} | 93 +++++++++++++++------------- 4 files changed, 69 insertions(+), 59 deletions(-) rename src/yunohost/{journals.py => log.py} (54%) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 3a16bf3bd..b30167b18 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -1603,26 +1603,27 @@ hook: help: The directory from where the script will be executed ############################# -# Journals # +# Log # ############################# -journals: - category_help: Manage debug journals +log: + category_help: Manage debug logs actions: - ### journals_list() + ### log_list() list: - action_help: List journals - api: GET /journals + action_help: List logs + api: GET /logs arguments: -l: full: --limit - help: Maximum number of journals per categories + help: Maximum number of logs per categories type: int - ### journals_display() + ### log_display() display: - action_help: Display a journal content - api: GET /journals/ + action_help: Display a log content + api: GET /logs/ arguments: - file_name: - help: Journal file name + file_name_list: + help: Log filenames for which to display the content + nargs: "*" diff --git a/locales/en.json b/locales/en.json index 2859bc888..27946e18d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -205,7 +205,7 @@ "invalid_url_format": "Invalid URL format", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", - "journal_does_exists": "There is not journal with the name '{journal}', use 'yunohost journal list to see all available journals'", + "log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list to see all available operation logs'", "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user", "ldap_initialized": "LDAP has been initialized", "license_undefined": "undefined", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 27dee21e7..2ee96afd1 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -433,7 +433,7 @@ def app_change_url(auth, app, domain, path): """ from yunohost.hook import hook_exec, hook_callback - from yunohost.journals import Journal + from yunohost.log import Journal installed = _is_installed(app) if not installed: @@ -542,7 +542,7 @@ def app_upgrade(auth, app=[], url=None, file=None): """ from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback - from yunohost.journals import Journal + from yunohost.log import Journal # Retrieve interface is_api = msettings.get('interface') == 'api' @@ -673,7 +673,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False): """ from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback - from yunohost.journals import Journal + from yunohost.log import Journal # Fetch or extract sources try: @@ -837,7 +837,7 @@ def app_remove(auth, app): """ from yunohost.hook import hook_exec, hook_remove, hook_callback - from yunohost.journals import Journal + from yunohost.log import Journal if not _is_installed(app): raise MoulinetteError(errno.EINVAL, diff --git a/src/yunohost/journals.py b/src/yunohost/log.py similarity index 54% rename from src/yunohost/journals.py rename to src/yunohost/log.py index 0f1785697..7c1f3b2a8 100644 --- a/src/yunohost/journals.py +++ b/src/yunohost/log.py @@ -19,9 +19,9 @@ """ -""" yunohost_journals.py +""" yunohost_log.py - Manage debug journals + Manage debug logs """ import os @@ -34,90 +34,98 @@ from moulinette import m18n from moulinette.core import MoulinetteError from moulinette.utils.log import getActionLogger -JOURNALS_PATH = '/var/log/yunohost/journals/' +OPERATIONS_PATH = '/var/log/yunohost/operation/' +OPERATION_FILE_EXT = '.yml' -logger = getActionLogger('yunohost.journals') +logger = getActionLogger('yunohost.log') -def journals_list(limit=None): +def log_list(limit=None): """ - List available journals + List available logs Keyword argument: - limit -- Maximum number of journals per categories + limit -- Maximum number of logs per categories """ result = {"categories": []} - if not os.path.exists(JOURNALS_PATH): + if not os.path.exists(OPERATIONS_PATH): return result - for category in sorted(os.listdir(JOURNALS_PATH)): - result["categories"].append({"name": category, "journals": []}) - for journal in filter(lambda x: x.endswith(".journal"), os.listdir(os.path.join(JOURNALS_PATH, category))): + for category in sorted(os.listdir(OPERATIONS_PATH)): + result["categories"].append({"name": category, "logs": []}) + for operation in filter(lambda x: x.endswith(OPERATION_FILE_EXT), os.listdir(os.path.join(OPERATIONS_PATH, category))): - file_name = journal + file_name = operation - journal = journal[:-len(".journal")] - journal = journal.split("_") + operation = operation[:-len(OPERATION_FILE_EXT)] + operation = operation.split("_") - journal_datetime = datetime.strptime(" ".join(journal[-2:]), "%Y-%m-%d %H-%M-%S") + operation_datetime = datetime.strptime(" ".join(operation[-2:]), "%Y-%m-%d %H-%M-%S") - result["categories"][-1]["journals"].append({ - "started_at": journal_datetime, - "name": " ".join(journal[:-2]), + result["categories"][-1]["logs"].append({ + "started_at": operation_datetime, + "name": " ".join(operation[:-2]), "file_name": file_name, - "path": os.path.join(JOURNALS_PATH, category, file_name), + "path": os.path.join(OPERATIONS_PATH, category, file_name), }) - result["categories"][-1]["journals"] = list(reversed(sorted(result["categories"][-1]["journals"], key=lambda x: x["started_at"]))) + result["categories"][-1]["logs"] = list(reversed(sorted(result["categories"][-1]["logs"], key=lambda x: x["started_at"]))) if limit is not None: - result["categories"][-1]["journals"] = result["categories"][-1]["journals"][:limit] + result["categories"][-1]["logs"] = result["categories"][-1]["logs"][:limit] return result -def journals_display(file_name): +def log_display(file_name_list): """ - Display a journal content + Display full log or specific logs listed Argument: - file_name + file_name_list """ - if not os.path.exists(JOURNALS_PATH): + if not os.path.exists(OPERATIONS_PATH): raise MoulinetteError(errno.EINVAL, - m18n.n('journal_does_exists', journal=file_name)) + m18n.n('log_does_exists', log=" ".join(file_name_list))) - for category in os.listdir(JOURNALS_PATH): - for journal in filter(lambda x: x.endswith(".journal"), os.listdir(os.path.join(JOURNALS_PATH, category))): - if journal != file_name: + result = {"logs": []} + + for category in os.listdir(OPERATIONS_PATH): + for operation in filter(lambda x: x.endswith(OPERATION_FILE_EXT), os.listdir(os.path.join(OPERATIONS_PATH, category))): + if operation not in file_name_list and file_name_list: continue - with open(os.path.join(JOURNALS_PATH, category, file_name), "r") as content: + file_name = operation + + with open(os.path.join(OPERATIONS_PATH, category, file_name), "r") as content: content = content.read() - journal = journal[:-len(".journal")] - journal = journal.split("_") - journal_datetime = datetime.strptime(" ".join(journal[-2:]), "%Y-%m-%d %H-%M-%S") + operation = operation[:-len(OPERATION_FILE_EXT)] + operation = operation.split("_") + operation_datetime = datetime.strptime(" ".join(operation[-2:]), "%Y-%m-%d %H-%M-%S") infos, logs = content.split("\n---\n", 1) infos = yaml.safe_load(infos) logs = [{"datetime": x.split(": ", 1)[0].replace("_", " "), "line": x.split(": ", 1)[1]} for x in logs.split("\n") if x] - return { - "started_at": journal_datetime, - "name": " ".join(journal[:-2]), + result['logs'].append({ + "started_at": operation_datetime, + "name": " ".join(operation[:-2]), "file_name": file_name, - "path": os.path.join(JOURNALS_PATH, category, file_name), + "path": os.path.join(OPERATIONS_PATH, category, file_name), "metadata": infos, "logs": logs, - } + }) - raise MoulinetteError(errno.EINVAL, - m18n.n('journal_does_exists', journal=file_name)) + logger.debug("====> %s", len(file_name_list), exc_info=1) + if len(file_name_list) > 0 and len(result['logs']) < len(file_name_list): + logger.error(m18n.n('log_does_exists', log="', '".join(file_name_list))) + if len(result['logs']) > 0: + return result class Journal(object): def __init__(self, name, category, on_stdout=None, on_stderr=None, on_write=None, **kwargs): @@ -129,7 +137,7 @@ class Journal(object): # this help uniformise file name and avoir threads concurrency errors self.started_at = datetime.now() - self.path = os.path.join(JOURNALS_PATH, category) + self.path = os.path.join(OPERATIONS_PATH, category) self.fd = None @@ -157,7 +165,8 @@ class Journal(object): if not os.path.exists(self.path): os.makedirs(self.path) - file_name = "%s_%s.journal" % (self.name if isinstance(self.name, basestring) else "_".join(self.name), self.started_at.strftime("%F_%X").replace(":", "-")) + file_name = "%s_%s" % (self.name if isinstance(self.name, basestring) else "_".join(self.name), self.started_at.strftime("%F_%X").replace(":", "-")) + file_name += OPERATION_FILE_EXT serialized_additional_information = yaml.safe_dump(self.additional_information, default_flow_style=False)