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 .history_monitor import HistoryMonitor, BashMonitor, GDBMonitor
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 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']
try:
self.processmanager(data['command'], data['process_name'])
message['data']['log'] = self.processmanager.read_log_stdout(message['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
+1 -1
View File
@@ -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
+6 -3
View File
@@ -7,21 +7,24 @@ 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
def monitor(self):
return self._monitor
def set_monitor_args(self, *monitor_args):
self.monitor_args = monitor_args
def reload_monitor(self):
if self._monitor:
try:
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
+28 -18
View File
@@ -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)
+2 -1
View File
@@ -1,6 +1,7 @@
[supervisord]
user=root
logfile = /tmp/supervisord.log
logfile=/dev/null
logfile_maxbytes=0
loglevel = debug
pidfile = /tmp/supervisord.pid