diff --git a/lib/tfw/components/__init__.py b/lib/tfw/components/__init__.py index f707c24..12735f9 100644 --- a/lib/tfw/components/__init__.py +++ b/lib/tfw/components/__init__.py @@ -7,3 +7,4 @@ from .terminal_event_handler import TerminalEventHandler from .ide_event_handler import IdeEventHandler from .history_monitor import HistoryMonitor, BashMonitor, GDBMonitor from .terminal_commands import TerminalCommands +from .log_monitoring_event_handler import LogMonitoringEventHandler diff --git a/lib/tfw/components/log_monitor.py b/lib/tfw/components/log_monitor.py new file mode 100644 index 0000000..287618f --- /dev/null +++ b/lib/tfw/components/log_monitor.py @@ -0,0 +1,52 @@ +# Copyright (C) 2018 Avatao.com Innovative Learning Kft. +# All Rights Reserved. See LICENSE file for details. + +import logging +from os.path import dirname + +from watchdog.events import PatternMatchingEventHandler as PatternMatchingWatchdogEventHandler + +from tfw.networking.event_handlers import ServerUplinkConnector +from tfw.decorators import RateLimiter +from tfw.mixins import ObserverMixin, SupervisorLogMixin + + +class LogMonitor(ObserverMixin): + def __init__(self, process_name, log_tail=0): + self.prevent_log_recursion() + ObserverMixin.__init__(self) + event_handler = SendLogWatchdogEventHandler(process_name, log_tail=log_tail) + self.observer.schedule( + event_handler, + event_handler.path + ) + + @staticmethod + def prevent_log_recursion(): + # This is done to prevent inotify event logs triggering themselves (infinite log recursion) + logging.getLogger('watchdog.observers.inotify_buffer').propagate = False + + +class SendLogWatchdogEventHandler(PatternMatchingWatchdogEventHandler, SupervisorLogMixin): + def __init__(self, process_name, log_tail=0): + self.acquire_own_supervisor_instance() # This thread-localises the xmlrpc client + self.process_name = process_name + self.procinfo = self.supervisor.getProcessInfo(self.process_name) + super().__init__([self.procinfo['stdout_logfile'], self.procinfo['stderr_logfile']]) + self.uplink = ServerUplinkConnector() + self.log_tail = log_tail + + @property + def path(self): + return dirname(self.procinfo['stdout_logfile']) + + @RateLimiter(rate_per_second=5) + def on_modified(self, event): + self.uplink.send({ + 'key': 'processlog', + 'data': { + 'command': 'new_log', + 'stdout': self.read_stdout(self.process_name, tail=self.log_tail), + 'stderr': self.read_stderr(self.process_name, tail=self.log_tail) + } + }) diff --git a/lib/tfw/components/log_monitoring_event_handler.py b/lib/tfw/components/log_monitoring_event_handler.py new file mode 100644 index 0000000..8d354a8 --- /dev/null +++ b/lib/tfw/components/log_monitoring_event_handler.py @@ -0,0 +1,15 @@ +# Copyright (C) 2018 Avatao.com Innovative Learning Kft. +# All Rights Reserved. See LICENSE file for details. + + +from tfw import EventHandlerBase +from tfw.mixins import MonitorManagerMixin +from .log_monitor import LogMonitor + + +class LogMonitoringEventHandler(EventHandlerBase, MonitorManagerMixin): + def __init__(self, process_name, log_tail=0): + MonitorManagerMixin.__init__(self, LogMonitor, process_name, log_tail) + + def handle_event(self, message): + pass diff --git a/lib/tfw/components/process_managing_event_handler.py b/lib/tfw/components/process_managing_event_handler.py index a028334..a512c1e 100644 --- a/lib/tfw/components/process_managing_event_handler.py +++ b/lib/tfw/components/process_managing_event_handler.py @@ -4,14 +4,14 @@ from xmlrpc.client import Fault as SupervisorFault from tfw import EventHandlerBase -from tfw.mixins import SupervisorMixin +from tfw.mixins import SupervisorMixin, SupervisorLogMixin from tfw.config.logs import logging from .directory_monitor import with_monitor_paused LOG = logging.getLogger(__name__) -class ProcessManager(SupervisorMixin): +class ProcessManager(SupervisorMixin, SupervisorLogMixin): def __init__(self): self.commands = {'start': self.start_process, 'stop': self.stop_process, @@ -34,22 +34,24 @@ class ProcessManagingEventHandler(EventHandlerBase): Commands available: start, stop, restart, readlog (the names are as self-documenting as it gets) """ - def __init__(self, key, dirmonitor=None): + def __init__(self, key, dirmonitor=None, log_tail=0): super().__init__(key) self.key = key self.monitor = dirmonitor self.processmanager = ProcessManager() + self.log_tail = log_tail @with_monitor_paused def handle_event(self, message): try: data = message['data'] - self.processmanager(data['command'], data['process_name']) - message['data']['log'] = self.processmanager.read_log_stdout(message['data']['process_name']) + try: + self.processmanager(data['command'], data['process_name']) + except SupervisorFault as fault: + message['data']['error'] = fault.faultString + finally: + message['data']['stdout'] = self.processmanager.read_stdout(data['process_name'], self.log_tail) + message['data']['stderr'] = self.processmanager.read_stderr(data['process_name'], self.log_tail) return message except KeyError: LOG.error('IGNORING MESSAGE: Invalid message received: %s', message) - except SupervisorFault as fault: - message['data']['error'] = fault.faultString - message['data']['log'] = self.processmanager.read_log_stderr(message['data']['process_name']) - return message diff --git a/lib/tfw/mixins/__init__.py b/lib/tfw/mixins/__init__.py index 60e67fc..58915ca 100644 --- a/lib/tfw/mixins/__init__.py +++ b/lib/tfw/mixins/__init__.py @@ -1,7 +1,7 @@ # Copyright (C) 2018 Avatao.com Innovative Learning Kft. # All Rights Reserved. See LICENSE file for details. -from .supervisor_mixin import SupervisorMixin +from .supervisor_mixin import SupervisorMixin, SupervisorLogMixin from .callback_mixin import CallbackMixin from .observer_mixin import ObserverMixin from .monitor_manager_mixin import MonitorManagerMixin diff --git a/lib/tfw/mixins/monitor_manager_mixin.py b/lib/tfw/mixins/monitor_manager_mixin.py index 16060ce..1f98314 100644 --- a/lib/tfw/mixins/monitor_manager_mixin.py +++ b/lib/tfw/mixins/monitor_manager_mixin.py @@ -7,10 +7,10 @@ LOG = logging.getLogger(__name__) class MonitorManagerMixin: - def __init__(self, monitor_type, directories): + def __init__(self, monitor_type, *monitor_args): self._monitor_type = monitor_type self._monitor = None - self._monitored_directories = directories + self._monitor_args = monitor_args self.reload_monitor() @property @@ -23,5 +23,5 @@ class MonitorManagerMixin: self._monitor.stop() except KeyError: LOG.debug('Working directory was removed – ignoring...') - self._monitor = self._monitor_type(self._monitored_directories) + self._monitor = self._monitor_type(*self._monitor_args) self._monitor.watch() # This runs on a separate thread diff --git a/lib/tfw/mixins/supervisor_mixin.py b/lib/tfw/mixins/supervisor_mixin.py index a95b769..9360395 100644 --- a/lib/tfw/mixins/supervisor_mixin.py +++ b/lib/tfw/mixins/supervisor_mixin.py @@ -9,9 +9,21 @@ from os import remove from tfw.config import TFWENV -class SupervisorMixin: - supervisor = xmlrpc.client.ServerProxy(TFWENV.SUPERVISOR_HTTP_URI).supervisor +def get_supervisor_instance(): + return xmlrpc.client.ServerProxy(TFWENV.SUPERVISOR_HTTP_URI).supervisor + +class SupervisorBaseMixin: + supervisor = get_supervisor_instance() + + def acquire_own_supervisor_instance(self): + """ + Give this instance non-static, local xmlrpc client + """ + self.supervisor = get_supervisor_instance() + + +class SupervisorMixin(SupervisorBaseMixin): def stop_process(self, process_name): with suppress(SupervisorFault): self.supervisor.stopProcess(process_name) @@ -19,22 +31,20 @@ class SupervisorMixin: def start_process(self, process_name): self.supervisor.startProcess(process_name) - def read_log_stdout(self, process_name): - return self._read_log_internal(self.supervisor.readProcessStdoutLog, process_name) - - def read_log_stderr(self, process_name): - return self._read_log_internal(self.supervisor.readProcessStderrLog, process_name) - - def _read_log_internal(self, read_method, process_name): - log = read_method(process_name, 0, 0) - self.clear_logs(process_name) - return log - - def clear_logs(self, process_name): - for logfile in ('stdout_logfile', 'stderr_logfile'): - remove(self.supervisor.getProcessInfo(process_name)[logfile]) - self.supervisor.clearProcessLogs(process_name) - def restart_process(self, process_name): self.stop_process(process_name) self.start_process(process_name) + + +class SupervisorLogMixin(SupervisorBaseMixin): + def read_stdout(self, process_name, tail=0): + return self.supervisor.readProcessStdoutLog(process_name, -tail, 0) + + def read_stderr(self, process_name, tail=0): + return self.supervisor.readProcessStderrLog(process_name, -tail, 0) + + def clear_logs(self, process_name): + for logfile in ('stdout_logfile', 'stderr_logfile'): + with suppress(FileNotFoundError): + remove(self.supervisor.getProcessInfo(process_name)[logfile]) + self.supervisor.clearProcessLogs(process_name) diff --git a/supervisor/supervisord.conf b/supervisor/supervisord.conf index 6e9e04c..80d98f3 100644 --- a/supervisor/supervisord.conf +++ b/supervisor/supervisord.conf @@ -1,6 +1,5 @@ [supervisord] user=root -logfile = /tmp/supervisord.log loglevel = debug pidfile = /tmp/supervisord.pid