[enh] Allow to display all operation logs with one command

This commit is contained in:
ljf 2018-04-03 20:38:56 +02:00
parent 60727e5fab
commit ba01168684
4 changed files with 69 additions and 59 deletions

View file

@ -1603,26 +1603,27 @@ hook:
help: The directory from where the script will be executed help: The directory from where the script will be executed
############################# #############################
# Journals # # Log #
############################# #############################
journals: log:
category_help: Manage debug journals category_help: Manage debug logs
actions: actions:
### journals_list() ### log_list()
list: list:
action_help: List journals action_help: List logs
api: GET /journals api: GET /logs
arguments: arguments:
-l: -l:
full: --limit full: --limit
help: Maximum number of journals per categories help: Maximum number of logs per categories
type: int type: int
### journals_display() ### log_display()
display: display:
action_help: Display a journal content action_help: Display a log content
api: GET /journals/<file_name> api: GET /logs/<file_name_list>
arguments: arguments:
file_name: file_name_list:
help: Journal file name help: Log filenames for which to display the content
nargs: "*"

View file

@ -205,7 +205,7 @@
"invalid_url_format": "Invalid URL format", "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", "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", "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_init_failed_to_create_admin": "LDAP initialization failed to create admin user",
"ldap_initialized": "LDAP has been initialized", "ldap_initialized": "LDAP has been initialized",
"license_undefined": "undefined", "license_undefined": "undefined",

View file

@ -433,7 +433,7 @@ def app_change_url(auth, app, domain, path):
""" """
from yunohost.hook import hook_exec, hook_callback from yunohost.hook import hook_exec, hook_callback
from yunohost.journals import Journal from yunohost.log import Journal
installed = _is_installed(app) installed = _is_installed(app)
if not installed: 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.hook import hook_add, hook_remove, hook_exec, hook_callback
from yunohost.journals import Journal from yunohost.log import Journal
# Retrieve interface # Retrieve interface
is_api = msettings.get('interface') == 'api' 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.hook import hook_add, hook_remove, hook_exec, hook_callback
from yunohost.journals import Journal from yunohost.log import Journal
# Fetch or extract sources # Fetch or extract sources
try: try:
@ -837,7 +837,7 @@ def app_remove(auth, app):
""" """
from yunohost.hook import hook_exec, hook_remove, hook_callback 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): if not _is_installed(app):
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,

View file

@ -19,9 +19,9 @@
""" """
""" yunohost_journals.py """ yunohost_log.py
Manage debug journals Manage debug logs
""" """
import os import os
@ -34,90 +34,98 @@ from moulinette import m18n
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger 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: Keyword argument:
limit -- Maximum number of journals per categories limit -- Maximum number of logs per categories
""" """
result = {"categories": []} result = {"categories": []}
if not os.path.exists(JOURNALS_PATH): if not os.path.exists(OPERATIONS_PATH):
return result return result
for category in sorted(os.listdir(JOURNALS_PATH)): for category in sorted(os.listdir(OPERATIONS_PATH)):
result["categories"].append({"name": category, "journals": []}) result["categories"].append({"name": category, "logs": []})
for journal in filter(lambda x: x.endswith(".journal"), os.listdir(os.path.join(JOURNALS_PATH, category))): 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")] operation = operation[:-len(OPERATION_FILE_EXT)]
journal = journal.split("_") 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({ result["categories"][-1]["logs"].append({
"started_at": journal_datetime, "started_at": operation_datetime,
"name": " ".join(journal[:-2]), "name": " ".join(operation[:-2]),
"file_name": file_name, "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: 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 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: Argument:
file_name file_name_list
""" """
if not os.path.exists(JOURNALS_PATH): if not os.path.exists(OPERATIONS_PATH):
raise MoulinetteError(errno.EINVAL, 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): result = {"logs": []}
for journal in filter(lambda x: x.endswith(".journal"), os.listdir(os.path.join(JOURNALS_PATH, category))):
if journal != file_name: 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 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() content = content.read()
journal = journal[:-len(".journal")] operation = operation[:-len(OPERATION_FILE_EXT)]
journal = journal.split("_") 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")
infos, logs = content.split("\n---\n", 1) infos, logs = content.split("\n---\n", 1)
infos = yaml.safe_load(infos) 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] logs = [{"datetime": x.split(": ", 1)[0].replace("_", " "), "line": x.split(": ", 1)[1]} for x in logs.split("\n") if x]
return { result['logs'].append({
"started_at": journal_datetime, "started_at": operation_datetime,
"name": " ".join(journal[:-2]), "name": " ".join(operation[:-2]),
"file_name": file_name, "file_name": file_name,
"path": os.path.join(JOURNALS_PATH, category, file_name), "path": os.path.join(OPERATIONS_PATH, category, file_name),
"metadata": infos, "metadata": infos,
"logs": logs, "logs": logs,
} })
raise MoulinetteError(errno.EINVAL, logger.debug("====> %s", len(file_name_list), exc_info=1)
m18n.n('journal_does_exists', journal=file_name)) 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): class Journal(object):
def __init__(self, name, category, on_stdout=None, on_stderr=None, on_write=None, **kwargs): 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 # this help uniformise file name and avoir threads concurrency errors
self.started_at = datetime.now() self.started_at = datetime.now()
self.path = os.path.join(JOURNALS_PATH, category) self.path = os.path.join(OPERATIONS_PATH, category)
self.fd = None self.fd = None
@ -157,7 +165,8 @@ class Journal(object):
if not os.path.exists(self.path): if not os.path.exists(self.path):
os.makedirs(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) serialized_additional_information = yaml.safe_dump(self.additional_information, default_flow_style=False)