Compare commits

...
Author SHA1 Message Date
Kristóf Tóth 9abd5b72f4 Hotfix supervisor infinite log recursion if no LogMonitor 2018-05-30 16:48:55 +02:00
Kristóf Tóth 2e953ea098 Implement LogMonitorEH API 2018-05-30 15:21:36 +02:00
Kristóf Tóth 211ae1e2d9 Allow changing MonitorManagerMixin.monitor_args dynamically 2018-05-30 15:19:59 +02:00
therealkrispet 84ea46ae76 Merge pull request #32 from avatao-content/console
Console
2018-05-30 14:30:17 +02:00
Kristóf Tóth 73f6c6635b Rename my bootiful threadlocalise.. SupervisorBaseMixin method 2018-05-30 14:07:40 +02:00
Kristóf Tóth babb14288c Use contextlib.suppress() to suppress stuff in SupervisorLogMixin 2018-05-30 14:05:13 +02:00
Kristóf Tóth 3bb97a6dcc Implement LogMonitoringEventHandler to manage a LogMonitor via API 2018-05-30 13:14:25 +02:00
Kristóf Tóth 9e6cd57532 Refactor MonitorManagerMixin to allow arbitrary Monitor arguments 2018-05-30 13:06:09 +02:00
Kristóf Tóth 28c16e1127 Rework log deletion – use log_tail arguments instead 2018-05-29 17:59:16 +02:00
Kristóf Tóth 4815b97200 Fix shared xmlrpc client raising HTTP errors in LogMonitor 2018-05-29 16:40:58 +02:00
Kristóf Tóth 9f6418bf60 Support creating thread-local xmlrpc clients in SupervisorBaseMixin 2018-05-29 16:40:58 +02:00
Kristóf Tóth 780d6a9b3c Avoid pylint stupidity ¯\_(ツ)_/¯ 2018-05-29 16:40:58 +02:00
Kristóf Tóth 32e3c2860d Fix inotify event logs triggering themselves (infinite log recursion) 2018-05-29 16:40:58 +02:00
Kristóf Tóth 029f4a9eb2 Implement watchdog observer to watch the logs of a supervisor process 2018-05-29 16:40:58 +02:00
Kristóf Tóth a6d9b50b93 Log everything from supervisor to stdout to avoid log recursion 2018-05-29 16:40:53 +02:00
Kristóf Tóth 9723f98950 Rework ProcessManagingEH log API 2018-05-28 16:36:59 +02:00
Kristóf Tóth 1a93dad562 Separate log clearing from reading 2018-05-28 15:02:53 +02:00
Kristóf Tóth 44df95a434 Refactor supervisor logging logic to separate mixin 2018-05-28 14:32:11 +02:00
8 changed files with 138 additions and 32 deletions
+1
View File
@@ -7,3 +7,4 @@ from .terminal_event_handler import TerminalEventHandler
from .ide_event_handler import IdeEventHandler from .ide_event_handler import IdeEventHandler
from .history_monitor import HistoryMonitor, BashMonitor, GDBMonitor from .history_monitor import HistoryMonitor, BashMonitor, GDBMonitor
from .terminal_commands import TerminalCommands from .terminal_commands import TerminalCommands
from .log_monitoring_event_handler import LogMonitoringEventHandler
+52
View File
@@ -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)
}
})
@@ -0,0 +1,37 @@
# 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 tfw.config.logs import logging
from .log_monitor import LogMonitor
LOG = logging.getLogger(__name__)
class LogMonitoringEventHandler(EventHandlerBase, MonitorManagerMixin):
def __init__(self, key, process_name, log_tail=0):
super().__init__(key)
self.process_name = process_name
self.log_tail = log_tail
MonitorManagerMixin.__init__(self, LogMonitor, self.process_name, self.log_tail)
self.command_handlers = {
'process_name': self.handle_process_name,
'log_tail': self.handle_log_tail
}
def handle_event(self, message):
try:
data = message['data']
self.command_handlers[data['command']](data)
self.reload_monitor()
except KeyError:
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message)
def handle_process_name(self, data):
self.set_monitor_args(data['process_name'], self.log_tail)
def handle_log_tail(self, data):
self.set_monitor_args(self.process_name, data['log_tail'])
@@ -4,14 +4,14 @@
from xmlrpc.client import Fault as SupervisorFault from xmlrpc.client import Fault as SupervisorFault
from tfw import EventHandlerBase from tfw import EventHandlerBase
from tfw.mixins import SupervisorMixin from tfw.mixins import SupervisorMixin, SupervisorLogMixin
from tfw.config.logs import logging from tfw.config.logs import logging
from .directory_monitor import with_monitor_paused from .directory_monitor import with_monitor_paused
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class ProcessManager(SupervisorMixin): class ProcessManager(SupervisorMixin, SupervisorLogMixin):
def __init__(self): def __init__(self):
self.commands = {'start': self.start_process, self.commands = {'start': self.start_process,
'stop': self.stop_process, 'stop': self.stop_process,
@@ -34,22 +34,24 @@ class ProcessManagingEventHandler(EventHandlerBase):
Commands available: start, stop, restart, readlog Commands available: start, stop, restart, readlog
(the names are as self-documenting as it gets) (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) super().__init__(key)
self.key = key self.key = key
self.monitor = dirmonitor self.monitor = dirmonitor
self.processmanager = ProcessManager() self.processmanager = ProcessManager()
self.log_tail = log_tail
@with_monitor_paused @with_monitor_paused
def handle_event(self, message): def handle_event(self, message):
try: try:
data = message['data'] data = message['data']
self.processmanager(data['command'], data['process_name']) try:
message['data']['log'] = self.processmanager.read_log_stdout(message['data']['process_name']) 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 return message
except KeyError: except KeyError:
LOG.error('IGNORING MESSAGE: Invalid message received: %s', message) 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
+1 -1
View File
@@ -1,7 +1,7 @@
# Copyright (C) 2018 Avatao.com Innovative Learning Kft. # Copyright (C) 2018 Avatao.com Innovative Learning Kft.
# All Rights Reserved. See LICENSE file for details. # 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 .callback_mixin import CallbackMixin
from .observer_mixin import ObserverMixin from .observer_mixin import ObserverMixin
from .monitor_manager_mixin import MonitorManagerMixin from .monitor_manager_mixin import MonitorManagerMixin
+6 -3
View File
@@ -7,21 +7,24 @@ LOG = logging.getLogger(__name__)
class MonitorManagerMixin: class MonitorManagerMixin:
def __init__(self, monitor_type, directories): def __init__(self, monitor_type, *monitor_args):
self._monitor_type = monitor_type self._monitor_type = monitor_type
self._monitor = None self._monitor = None
self._monitored_directories = directories self.monitor_args = monitor_args
self.reload_monitor() self.reload_monitor()
@property @property
def monitor(self): def monitor(self):
return self._monitor return self._monitor
def set_monitor_args(self, *monitor_args):
self.monitor_args = monitor_args
def reload_monitor(self): def reload_monitor(self):
if self._monitor: if self._monitor:
try: try:
self._monitor.stop() self._monitor.stop()
except KeyError: except KeyError:
LOG.debug('Working directory was removed ignoring...') 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 self._monitor.watch() # This runs on a separate thread
+28 -18
View File
@@ -9,9 +9,21 @@ from os import remove
from tfw.config import TFWENV from tfw.config import TFWENV
class SupervisorMixin: def get_supervisor_instance():
supervisor = xmlrpc.client.ServerProxy(TFWENV.SUPERVISOR_HTTP_URI).supervisor 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): def stop_process(self, process_name):
with suppress(SupervisorFault): with suppress(SupervisorFault):
self.supervisor.stopProcess(process_name) self.supervisor.stopProcess(process_name)
@@ -19,22 +31,20 @@ class SupervisorMixin:
def start_process(self, process_name): def start_process(self, process_name):
self.supervisor.startProcess(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): def restart_process(self, process_name):
self.stop_process(process_name) self.stop_process(process_name)
self.start_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)
+2 -1
View File
@@ -1,6 +1,7 @@
[supervisord] [supervisord]
user=root user=root
logfile = /tmp/supervisord.log logfile=/dev/null
logfile_maxbytes=0
loglevel = debug loglevel = debug
pidfile = /tmp/supervisord.pid pidfile = /tmp/supervisord.pid