[enh] Use Handler to write log

This commit is contained in:
ljf 2018-04-11 22:49:26 +02:00
parent a22672ede8
commit 219f1c6262
4 changed files with 105 additions and 80 deletions

View file

@ -422,6 +422,7 @@ def app_map(app=None, raw=False, user=None):
return result
@is_unit_operation()
def app_change_url(auth, app, domain, path):
"""
Modify the URL at which an application is installed.
@ -433,7 +434,6 @@ def app_change_url(auth, app, domain, path):
"""
from yunohost.hook import hook_exec, hook_callback
from yunohost.log import Journal
installed = _is_installed(app)
if not installed:
@ -497,8 +497,7 @@ def app_change_url(auth, app, domain, path):
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts")))
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url")))
journal = Journal(["change_url", app], "app", args=args_list, env=env_dict)
if hook_exec(os.path.join(APP_TMP_FOLDER, 'scripts/change_url'), args=args_list, env=env_dict, user="root", journal=journal) != 0:
if hook_exec(os.path.join(APP_TMP_FOLDER, 'scripts/change_url'), args=args_list, env=env_dict, user="root") != 0:
logger.error("Failed to change '%s' url." % app)
# restore values modified by app_checkurl
@ -531,6 +530,7 @@ def app_change_url(auth, app, domain, path):
hook_callback('post_app_change_url', args=args_list, env=env_dict)
@is_unit_operation()
def app_upgrade(auth, app=[], url=None, file=None):
"""
Upgrade app
@ -616,8 +616,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
# Execute App upgrade script
os.system('chown -hR admin: %s' % INSTALL_TMP)
journal = Journal(["upgrade", app_instance_name], "app", args=args_list, env=env_dict)
if hook_exec(extracted_app_folder + '/scripts/upgrade', args=args_list, env=env_dict, user="root", journal=journal) != 0:
if hook_exec(extracted_app_folder + '/scripts/upgrade', args=args_list, env=env_dict, user="root") != 0:
logger.error(m18n.n('app_upgrade_failed', app=app_instance_name))
else:
now = int(time.time())
@ -660,8 +659,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
if is_api:
return {"log": service_log('yunohost-api', number="100").values()[0]}
def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
def app_install(auth, app, label=None, args=None, no_remove_on_failure=False, **kwargs):
"""
Install apps
@ -673,7 +671,9 @@ 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.log import Journal
from yunohost.log import UnitOperationHandler
uo_install = UnitOperationHandler('app_install', 'app', args=kwargs)
# Fetch or extract sources
try:
@ -762,11 +762,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
try:
install_retcode = hook_exec(
os.path.join(extracted_app_folder, 'scripts/install'),
args=args_list, env=env_dict, user="root",
journal = Journal(
["install", app_instance_name],
"app", args=args_list, env=env_dict
),
args=args_list, env=env_dict, user="root"
)
except (KeyboardInterrupt, EOFError):
install_retcode = -1
@ -774,6 +770,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
logger.exception(m18n.n('unexpected_error'))
finally:
if install_retcode != 0:
uo_install.close()
if not no_remove_on_failure:
# Setup environment for remove script
env_dict_remove = {}
@ -782,18 +779,19 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(instance_number)
# Execute remove script
uo_remove = UnitOperationHandler('remove_on_failed_install',
'app', args=env_dict_remove)
remove_retcode = hook_exec(
os.path.join(extracted_app_folder, 'scripts/remove'),
args=[app_instance_name], env=env_dict_remove, user="root",
journal = Journal(
["remove", app_instance_name, "failed_install"],
"app", args=[app_instance_name], env=env_dict_remove,
),
args=[app_instance_name], env=env_dict_remove, user="root"
)
if remove_retcode != 0:
logger.warning(m18n.n('app_not_properly_removed',
app=app_instance_name))
uo_remove.close()
# Clean tmp folders
shutil.rmtree(app_setting_path)
shutil.rmtree(extracted_app_folder)
@ -827,7 +825,10 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
hook_callback('post_app_install', args=args_list, env=env_dict)
uo_install.close()
@is_unit_operation()
def app_remove(auth, app):
"""
Remove app
@ -863,8 +864,7 @@ def app_remove(auth, app):
env_dict["YNH_APP_INSTANCE_NAME"] = app
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
journal = Journal(["remove", app], "app", args=args_list, env=env_dict)
if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list, env=env_dict, user="root", journal=journal) == 0:
if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list, env=env_dict, user="root") == 0:
logger.success(m18n.n('app_removed', app=app))
hook_callback('post_app_remove', args=args_list, env=env_dict)

View file

@ -297,7 +297,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
chdir=None, env=None, journal=None, user="admin"):
chdir=None, env=None, user="admin"):
"""
Execute hook from a file with arguments
@ -359,18 +359,6 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
else:
logger.info(m18n.n('executing_script', script=path))
if journal is None:
# Define output callbacks and call command
callbacks = (
lambda l: logger.info(l.rstrip()),
lambda l: logger.warning(l.rstrip()),
)
else:
callbacks = journal.as_callbacks_tuple(
stdout=lambda l: logger.info(l.rstrip()),
stderr=lambda l: logger.warning(l.rstrip()),
)
returncode = call_async_output(
command, callbacks, shell=False, cwd=chdir
)

View file

@ -27,8 +27,11 @@
import os
import yaml
import errno
import logging
from datetime import datetime
from logging import StreamHandler, getLogger, Formatter
from sys import exc_info
from moulinette import m18n
from moulinette.core import MoulinetteError
@ -39,7 +42,6 @@ OPERATION_FILE_EXT = '.yml'
logger = getActionLogger('yunohost.log')
def log_list(limit=None):
"""
List available logs
@ -127,71 +129,105 @@ def log_display(file_name_list):
result['operations'] = sorted(result['operations'], key=lambda operation: operation['started_at'])
return result
class Journal(object):
def __init__(self, name, category, on_stdout=None, on_stderr=None, on_write=None, **kwargs):
def is_unit_operation(categorie=None, description_key=None):
def decorate(func):
def func_wrapper(*args, **kwargs):
cat = categorie
desc_key = description_key
if cat is None:
cat = func.__module__.split('.')[1]
if desc_key is None:
desc_key = func.__name__
uo = UnitOperationHandler(desc_key, cat, args=kwargs)
try:
result = func(*args, **kwargs)
finally:
uo.close(exc_info()[0])
return result
return func_wrapper
return decorate
class UnitOperationHandler(StreamHandler):
def __init__(self, name, category, **kwargs):
# TODO add a way to not save password on app installation
self.name = name
self._name = name
self.category = category
self.first_write = True
self.closed = False
# this help uniformise file name and avoir threads concurrency errors
self.started_at = datetime.now()
self.path = os.path.join(OPERATIONS_PATH, category)
self.fd = None
if not os.path.exists(self.path):
os.makedirs(self.path)
self.on_stdout = [] if on_stdout is None else on_stdout
self.on_stderr = [] if on_stderr is None else on_stderr
self.on_write = [] if on_write is None else on_write
self.filename = "%s_%s" % (self.started_at.strftime("%F_%X").replace(":", "-"), self._name if isinstance(self._name, basestring) else "_".join(self._name))
self.filename += OPERATION_FILE_EXT
self.additional_information = kwargs
def __del__(self):
if self.fd:
self.fd.close()
logging.StreamHandler.__init__(self, self._open())
def write(self, line):
self.formatter = Formatter('%(asctime)s: %(levelname)s - %(message)s')
if self.stream is None:
self.stream = self._open()
# Listen to the root logger
self.logger = getLogger('yunohost')
self.logger.addHandler(self)
def _open(self):
stream = open(os.path.join(self.path, self.filename), "w")
return stream
def close(self, error=None):
"""
Closes the stream.
"""
if self.closed:
return
self.acquire()
#self.ended_at = datetime.now()
#self.error = error
#self.stream.seek(0)
#context = {
# 'ended_at': datetime.now()
#}
#if error is not None:
# context['error'] = error
#self.stream.write(yaml.safe_dump(context))
self.logger.removeHandler(self)
try:
if self.stream:
try:
self.flush()
finally:
stream = self.stream
self.stream = None
if hasattr(stream, "close"):
stream.close()
finally:
self.release()
self.closed = True
def __del__(self):
self.close()
def emit(self, record):
if self.first_write:
self._do_first_write()
self.first_write = False
self.fd.write("%s: " % datetime.now().strftime("%F %X"))
self.fd.write(line.rstrip())
self.fd.write("\n")
self.fd.flush()
StreamHandler.emit(self, record)
def _do_first_write(self):
if not os.path.exists(self.path):
os.makedirs(self.path)
file_name = "%s_%s" % (self.started_at.strftime("%F_%X").replace(":", "-"), self.name if isinstance(self.name, basestring) else "_".join(self.name))
file_name += OPERATION_FILE_EXT
serialized_additional_information = yaml.safe_dump(self.additional_information, default_flow_style=False)
self.fd = open(os.path.join(self.path, file_name), "w")
self.fd.write(serialized_additional_information)
self.fd.write("\n---\n")
def stdout(self, line):
for i in self.on_stdout:
i(line)
self.write(line)
def stderr(self, line):
for i in self.on_stderr:
i(line)
self.write(line)
def as_callbacks_tuple(self, stdout=None, stderr=None):
if stdout:
self.on_stdout.append(stdout)
if stderr:
self.on_stderr.append(stderr)
return (self.stdout, self.stderr)
self.stream.write(serialized_additional_information)
self.stream.write("\n---\n")

View file

@ -38,6 +38,7 @@ from moulinette.core import MoulinetteError
from moulinette.utils import log, filesystem
from yunohost.hook import hook_callback
from yunohost.log import is_unit_operation
BASE_CONF_PATH = '/home/yunohost.conf'
BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup')
@ -141,7 +142,7 @@ def service_stop(names):
m18n.n('service_stop_failed', service=name))
logger.info(m18n.n('service_already_stopped', service=name))
@is_unit_operation()
def service_enable(names):
"""
Enable one or more services