Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9abd5b72f4 | ||
|
|
2e953ea098 | ||
|
|
211ae1e2d9 | ||
|
|
84ea46ae76 | ||
|
|
73f6c6635b | ||
|
|
babb14288c | ||
|
|
3bb97a6dcc | ||
|
|
9e6cd57532 | ||
|
|
28c16e1127 | ||
|
|
4815b97200 | ||
|
|
9f6418bf60 | ||
|
|
780d6a9b3c | ||
|
|
32e3c2860d | ||
|
|
029f4a9eb2 | ||
|
|
a6d9b50b93 | ||
|
|
9723f98950 | ||
|
|
1a93dad562 | ||
|
|
44df95a434 | ||
|
|
fd56f8ca63 | ||
|
|
77fabf28f6 |
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from re import match
|
from re import match
|
||||||
|
from shlex import split
|
||||||
|
|
||||||
from tfw.config.logs import logging
|
from tfw.config.logs import logging
|
||||||
|
|
||||||
@@ -57,10 +58,10 @@ class TerminalCommands(ABC):
|
|||||||
return match(self._command_method_regex, string)
|
return match(self._command_method_regex, string)
|
||||||
|
|
||||||
def callback(self, history):
|
def callback(self, history):
|
||||||
parts = history[-1].split()
|
parts = split(history[-1])
|
||||||
command = parts[0]
|
command = parts[0]
|
||||||
if command in self.command_implemetations.keys():
|
if command in self.command_implemetations.keys():
|
||||||
try:
|
try:
|
||||||
self.command_implemetations[command](*parts[1:])
|
self.command_implemetations[command](*parts[1:])
|
||||||
except IndexError:
|
except Exception: # pylint: disable=broad-except
|
||||||
LOG.debug('Command "%s" failed!', command)
|
LOG.exception('Command "%s" failed:', command)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user