# Copyright (C) 2018 Avatao.com Innovative Learning Kft. # All Rights Reserved. See LICENSE file for details. import logging from os.path import dirname from tfw.networking import Scope from tfw.event_handlers import FrontendEventHandlerBase, TFWServerUplinkConnector from tfw.components.inotify import InotifyObserver from tfw.mixins.supervisor_mixin import SupervisorLogMixin LOG = logging.getLogger(__name__) class LogInotifyObserver(InotifyObserver, SupervisorLogMixin): def __init__(self, process_name, log_tail=0): self.prevent_log_recursion() self.uplink = TFWServerUplinkConnector() self.process_name = process_name self.log_tail = log_tail self.procinfo = self.supervisor.getProcessInfo(self.process_name) super().__init__( [dirname(self.procinfo['stdout_logfile']), dirname(self.procinfo['stderr_logfile'])], [self.procinfo['stdout_logfile'], self.procinfo['stderr_logfile']] ) def reset(self, process_name, log_tail): self.process_name = process_name self.log_tail = log_tail self.procinfo = self.supervisor.getProcessInfo(self.process_name) self.paths = [ dirname(self.procinfo['stdout_logfile']), dirname(self.procinfo['stderr_logfile']) ] self.patterns = [ self.procinfo['stdout_logfile'], self.procinfo['stderr_logfile'] ] def on_modified(self, event): self.uplink.send_message({ '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) } }, Scope.BROADCAST) @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 LogMonitoringEventHandler(FrontendEventHandlerBase): """ Monitors the output of a supervisor process (stdout, stderr) and sends the results to the frontend. Accepts messages that have a data['command'] key specifying a command to be executed. The API of each command is documented in their respective handler. """ def __init__(self, key, process_name, log_tail=0): super().__init__(key) self.process_name = process_name self.log_tail = log_tail self.monitor = LogInotifyObserver(process_name, log_tail) self.monitor.start() 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) except KeyError: LOG.error('IGNORING MESSAGE: Invalid message received: %s', message) def handle_process_name(self, data): """ Changes the monitored process. :param data: TFW message data containing 'value' (name of the process to monitor) """ self.monitor.reset(data['value'], self.log_tail) def handle_log_tail(self, data): """ Sets tail length of the log the monitor will send to the frontend (the monitor will send back the last 'value' characters of the log). :param data: TFW message data containing 'value' (new tail length) """ self.monitor.reset(self.process_name, data['value']) def cleanup(self): self.monitor.stop()