From 59c7f6407b7f5d3ec028708b40810c93017e312c Mon Sep 17 00:00:00 2001 From: Michael Hammann Date: Fri, 25 Jun 2021 09:59:13 +0200 Subject: [PATCH 1/5] feature: add support for runningregex to set RUNNING state only when keyword is detected in output --- supervisor/options.py | 14 ++++++++++++-- supervisor/process.py | 21 ++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/supervisor/options.py b/supervisor/options.py index 7b53cc760..ad7abaa05 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -932,6 +932,14 @@ def get(section, opt, *args, **kwargs): serverurl = get(section, 'serverurl', None) if serverurl and serverurl.strip().upper() == 'AUTO': serverurl = None + runningregex = get(section, 'runningregex', None) + if runningregex: + try: + runningregex = re.compile(runningregex) + except Exception as e: + raise ValueError( + f"program section {section} has invalid runningregex value. Error {e}") + # find uid from "user" option user = get(section, 'user', None) @@ -1057,7 +1065,9 @@ def get(section, opt, *args, **kwargs): exitcodes=exitcodes, redirect_stderr=redirect_stderr, environment=environment, - serverurl=serverurl) + serverurl=serverurl, + runningregex=runningregex + ) programs.append(pconfig) @@ -1875,7 +1885,7 @@ class ProcessConfig(Config): 'stderr_events_enabled', 'stderr_syslog', 'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup', 'exitcodes', 'redirect_stderr' ] - optional_param_names = [ 'environment', 'serverurl' ] + optional_param_names = [ 'environment', 'serverurl', 'runningregex' ] def __init__(self, options, **params): self.options = options diff --git a/supervisor/process.py b/supervisor/process.py index d6f60f3e2..6b1ef1302 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -679,7 +679,7 @@ def transition(self): processname = as_string(self.config.name) if state == ProcessStates.STARTING: - if now - self.laststart > self.config.startsecs: + if now - self.laststart > self.config.startsecs and not self.config.runningregex: # STARTING -> RUNNING if the proc has started # successfully and it has stayed up for at least # proc.config.startsecs, @@ -692,6 +692,25 @@ def transition(self): '> than %s seconds (startsecs)' % self.config.startsecs) logger.info('success: %s %s' % (processname, msg)) + if self.config.runningregex: + logfile = getattr(self.config, 'stdout_logfile') + + + from supervisor.options import readFile + str = as_string(readFile(logfile, 0, 0)) + + if self.config.runningregex.match(str): + # STARTING -> RUNNING if the proc has started + # successfully and the runningregex is met + self.delay = 0 + self.backoff = 0 + self._assertInState(ProcessStates.STARTING) + self.change_state(ProcessStates.RUNNING) + msg = ( + 'entered RUNNING state, found runningregex in stdout') + logger.info('success: %s %s' % (processname, msg)) + + if state == ProcessStates.BACKOFF: if self.backoff > self.config.startretries: # BACKOFF -> FATAL if the proc has exceeded its number From 9bdac9f3e004e85b6bce7df3a87bf81f79774986 Mon Sep 17 00:00:00 2001 From: Michael Hammann Date: Wed, 14 Jul 2021 14:20:06 +0200 Subject: [PATCH 2/5] feature: added support for matching regex output even if asci escape sequences are used on the output --- supervisor/options.py | 2 +- supervisor/process.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/supervisor/options.py b/supervisor/options.py index ad7abaa05..3df01a614 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -935,7 +935,7 @@ def get(section, opt, *args, **kwargs): runningregex = get(section, 'runningregex', None) if runningregex: try: - runningregex = re.compile(runningregex) + runningregex = re.compile(r'.*'+ runningregex) except Exception as e: raise ValueError( f"program section {section} has invalid runningregex value. Error {e}") diff --git a/supervisor/process.py b/supervisor/process.py index 6b1ef1302..782d90035 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -5,6 +5,7 @@ import shlex import time import traceback +import re from supervisor.compat import maxint from supervisor.compat import as_bytes @@ -698,6 +699,10 @@ def transition(self): from supervisor.options import readFile str = as_string(readFile(logfile, 0, 0)) + + # delete ascii escape sequence and newlines with regular expression + ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]|\n') + str = ansi_escape.sub('', str) if self.config.runningregex.match(str): # STARTING -> RUNNING if the proc has started From 76626e6707efdbbea5cd13d2cd0ea0321890faea Mon Sep 17 00:00:00 2001 From: Michael Hammann Date: Fri, 16 Jul 2021 10:22:06 +0200 Subject: [PATCH 3/5] fix: added offset parameter when reading logfile to check for runningregex --- supervisor/process.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/supervisor/process.py b/supervisor/process.py index 782d90035..0dac7b2a4 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -22,6 +22,7 @@ from supervisor.options import decode_wait_status from supervisor.options import signame from supervisor.options import ProcessException, BadCommand +from supervisor.options import readFile from supervisor.dispatchers import EventListenerStates @@ -195,6 +196,16 @@ def spawn(self): Return the process id. If the fork() call fails, return None. """ + # if runningregex is used, set the log_offset correctly + if self.config.runningregex is not None: + try: + logfile = getattr(self.config, 'stdout_logfile') + str = as_string(readFile(logfile, self.log_offset, 0)) + self.log_offset = len(str) + except: # file does not exisst yet + self.log_offset = 0 + + options = self.config.options processname = as_string(self.config.name) @@ -695,10 +706,7 @@ def transition(self): if self.config.runningregex: logfile = getattr(self.config, 'stdout_logfile') - - - from supervisor.options import readFile - str = as_string(readFile(logfile, 0, 0)) + str = as_string(readFile(logfile, self.log_offset, 0)) # delete ascii escape sequence and newlines with regular expression ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]|\n') From fc02429e6897fe83415c8fcfea8752a06a01dd5d Mon Sep 17 00:00:00 2001 From: Michael Hammann Date: Wed, 8 Sep 2021 05:08:10 +0200 Subject: [PATCH 4/5] fix: logfiles are read correctly now and only the new lines are taken into account --- supervisor/process.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/supervisor/process.py b/supervisor/process.py index 0dac7b2a4..304b76ed0 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -200,9 +200,10 @@ def spawn(self): if self.config.runningregex is not None: try: logfile = getattr(self.config, 'stdout_logfile') - str = as_string(readFile(logfile, self.log_offset, 0)) - self.log_offset = len(str) - except: # file does not exisst yet + with open(logfile, 'rb') as f: + log_file_length = len(f.read()) + self.log_offset = log_file_length + except FileNotFoundError: self.log_offset = 0 @@ -706,21 +707,20 @@ def transition(self): if self.config.runningregex: logfile = getattr(self.config, 'stdout_logfile') - str = as_string(readFile(logfile, self.log_offset, 0)) + logfile_as_str = as_string(readFile(logfile, self.log_offset, 0)) # delete ascii escape sequence and newlines with regular expression ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]|\n') - str = ansi_escape.sub('', str) + logfile_as_str = ansi_escape.sub('', logfile_as_str) - if self.config.runningregex.match(str): - # STARTING -> RUNNING if the proc has started - # successfully and the runningregex is met + # STARTING -> RUNNING if the process has started + # successfully and the runningregex is met + if self.config.runningregex.match(logfile_as_str): self.delay = 0 self.backoff = 0 self._assertInState(ProcessStates.STARTING) self.change_state(ProcessStates.RUNNING) - msg = ( - 'entered RUNNING state, found runningregex in stdout') + msg = ('entered RUNNING state, found runningregex in stdout') logger.info('success: %s %s' % (processname, msg)) From f9b9e6a36c9427eb5463e032f6d4970f8e71cee9 Mon Sep 17 00:00:00 2001 From: Michael Hammann Date: Wed, 27 Oct 2021 13:17:27 +0200 Subject: [PATCH 5/5] fix: runningregex can only be used with logfiles, default to startsecs if no logfile is defined and handle the case when the logfile is deleted or moved while supervisor runs --- supervisor/options.py | 7 ++++++- supervisor/process.py | 38 +++++++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/supervisor/options.py b/supervisor/options.py index 3df01a614..3565243bf 100644 --- a/supervisor/options.py +++ b/supervisor/options.py @@ -939,7 +939,12 @@ def get(section, opt, *args, **kwargs): except Exception as e: raise ValueError( f"program section {section} has invalid runningregex value. Error {e}") - + if "/dev/" in get(section, 'stdout_logfile'): + self.warnings.warn( + '\033[93m runningregex is only supported for logfiles.' + 'It does not work with /dev/null or /dev/fd/1.' + 'Startsecs are used instead \033[0m') + runningregex = None # find uid from "user" option user = get(section, 'user', None) diff --git a/supervisor/process.py b/supervisor/process.py index 304b76ed0..96181d356 100644 --- a/supervisor/process.py +++ b/supervisor/process.py @@ -707,21 +707,29 @@ def transition(self): if self.config.runningregex: logfile = getattr(self.config, 'stdout_logfile') - logfile_as_str = as_string(readFile(logfile, self.log_offset, 0)) - - # delete ascii escape sequence and newlines with regular expression - ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]|\n') - logfile_as_str = ansi_escape.sub('', logfile_as_str) - - # STARTING -> RUNNING if the process has started - # successfully and the runningregex is met - if self.config.runningregex.match(logfile_as_str): - self.delay = 0 - self.backoff = 0 - self._assertInState(ProcessStates.STARTING) - self.change_state(ProcessStates.RUNNING) - msg = ('entered RUNNING state, found runningregex in stdout') - logger.info('success: %s %s' % (processname, msg)) + try: + logfile_as_str = as_string(readFile(logfile, self.log_offset, 0)) + + # delete ascii escape sequence and newlines with regular expression + ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]|\n') + logfile_as_str = ansi_escape.sub('', logfile_as_str) + + # STARTING -> RUNNING if the process has started + # successfully and the runningregex is met + if self.config.runningregex.match(logfile_as_str): + self.delay = 0 + self.backoff = 0 + self._assertInState(ProcessStates.STARTING) + self.change_state(ProcessStates.RUNNING) + msg = ('entered RUNNING state, found runningregex in stdout') + logger.info('success: %s %s' % (processname, msg)) + except ValueError: + with open(logfile, 'w') as file: + file.write("") + logger.warn( + 'logfile {} did not exists. Created new logfile, ' + 'runnngregex match will be checked again next ' + 'iteration.'.format(logfile)) if state == ProcessStates.BACKOFF: