[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
#############################
# 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/<file_name>
action_help: Display a log content
api: GET /logs/<file_name_list>
arguments:
file_name:
help: Journal file name
file_name_list:
help: Log filenames for which to display the content
nargs: "*"

View file

@ -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",

View file

@ -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,

View file

@ -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)