Skip to content

feature: add support for runningregex to set RUNNING state accordingly #1446

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions supervisor/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,19 @@ 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(r'.*'+ runningregex)
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)
Expand Down Expand Up @@ -1057,7 +1070,9 @@ def get(section, opt, *args, **kwargs):
exitcodes=exitcodes,
redirect_stderr=redirect_stderr,
environment=environment,
serverurl=serverurl)
serverurl=serverurl,
runningregex=runningregex
)

programs.append(pconfig)

Expand Down Expand Up @@ -1875,7 +1890,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
Expand Down
42 changes: 41 additions & 1 deletion supervisor/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import shlex
import time
import traceback
import re

from supervisor.compat import maxint
from supervisor.compat import as_bytes
Expand All @@ -21,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

Expand Down Expand Up @@ -194,6 +196,17 @@ 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')
with open(logfile, 'rb') as f:
log_file_length = len(f.read())
self.log_offset = log_file_length
except FileNotFoundError:
self.log_offset = 0


options = self.config.options
processname = as_string(self.config.name)

Expand Down Expand Up @@ -679,7 +692,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,
Expand All @@ -692,6 +705,33 @@ 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')
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:
if self.backoff > self.config.startretries:
# BACKOFF -> FATAL if the proc has exceeded its number
Expand Down