From 1e90dabd8b68580ea697b727f51103502ab5affe Mon Sep 17 00:00:00 2001 From: Yann Diorcet Date: Thu, 29 Mar 2018 14:40:25 +0200 Subject: [PATCH] Add Windows service --- circus/circusd.py | 6 +-- circus/circusrv.py | 98 ++++++++++++++++++++++++++++++++++++++++++++ circus/sighandler.py | 5 ++- circus/util.py | 14 +++---- setup.py | 5 +++ 5 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 circus/circusrv.py diff --git a/circus/circusd.py b/circus/circusd.py index c0c22be74..b367a876f 100644 --- a/circus/circusd.py +++ b/circus/circusd.py @@ -85,7 +85,7 @@ def main(): sys.exit(0) parser = argparse.ArgumentParser(description='Run some watchers.') - parser.add_argument('config', help='configuration file', nargs='?') + parser.add_argument('config', help='configuration file') # XXX we should be able to add all these options in the config file as well parser.add_argument('--log-level', dest='loglevel', @@ -114,10 +114,6 @@ def main(): print(__version__) sys.exit(0) - if args.config is None: - parser.print_usage() - sys.exit(0) - if args.daemonize: daemonize() diff --git a/circus/circusrv.py b/circus/circusrv.py new file mode 100644 index 000000000..42a9e910e --- /dev/null +++ b/circus/circusrv.py @@ -0,0 +1,98 @@ +import win32serviceutil +import servicemanager +import os +import logging +import traceback + +from circus.arbiter import Arbiter +from circus.util import check_future_exception_and_log, LOG_LEVELS + + +class ServiceManagerHandler(logging.Handler): + _map_ = { + logging.CRITICAL: servicemanager.EVENTLOG_ERROR_TYPE, + logging.ERROR: servicemanager.EVENTLOG_ERROR_TYPE, + logging.WARNING: servicemanager.EVENTLOG_WARNING_TYPE, + logging.INFO: servicemanager.EVENTLOG_INFORMATION_TYPE, + logging.DEBUG: servicemanager.EVENTLOG_INFORMATION_TYPE + } + + def emit(self, record): + level = self._map_.get(record.levelno) + details = "" + if record.exc_info is not None: + formated_exc = traceback.format_exception(*record.exc_info) + details = os.linesep.join(formated_exc) + servicemanager.LogMsg(level, 0xF000, (record.getMessage(), details)) + + +class CircusSrv(win32serviceutil.ServiceFramework): + _svc_name_ = 'circus' + _svc_display_name_ = 'Circus' + _svc_description_ = 'Run some watchers.' + + _parameter_config = 'Config' + _parameter_loglevel = 'LogLevel' + + def __init__(self, args): + self._svc_name_ = args[0] + super(CircusSrv, self).__init__(args) + + config = win32serviceutil.GetServiceCustomOption(self._svc_name_, self._parameter_config) + loglevel = logging.INFO + try: + lls = win32serviceutil.GetServiceCustomOption(self._svc_name_, self._parameter_loglevel) + if lls is not None: + loglevel = LOG_LEVELS.get(lls.lower(), logging.INFO) + except: + pass + + root_logger = logging.getLogger() + root_logger.setLevel(loglevel) + root_logger.handlers = [ServiceManagerHandler()] + + # From here it can also come from the arbiter configuration + # load the arbiter from config + self.arbiter = Arbiter.load_from_config(config) + + def SvcStop(self): + self.arbiter.loop.run_sync(self.arbiter._emergency_stop) + + def SvcDoRun(self): + arbiter = self.arbiter + try: + future = arbiter.start() + check_future_exception_and_log(future) + except Exception as e: + # emergency stop + arbiter.loop.run_sync(arbiter._emergency_stop) + raise (e) + except KeyboardInterrupt: + pass + + @classmethod + def OptionsHandler(cls, opts): + for opt, val in opts: + if opt == '-c': + win32serviceutil.SetServiceCustomOption(cls._svc_name_, + cls._parameter_config, + val) + if opt == '-l': + win32serviceutil.SetServiceCustomOption(cls._svc_name_, + cls._parameter_loglevel, + val) + + # Register now the source (rights of service's user may be different) + servicemanager.SetEventSourceName(cls._svc_name_, True) + + +def main(): + kwargs = {} + kwargs['customInstallOptions'] = 'c:l:' + kwargs['customOptionHandler'] = CircusSrv.OptionsHandler + ret = win32serviceutil.HandleCommandLine(CircusSrv, **kwargs) + sys.exit(ret) + + +if __name__ == '__main__': + main() diff --git a/circus/sighandler.py b/circus/sighandler.py index 182ba4503..384992b3c 100644 --- a/circus/sighandler.py +++ b/circus/sighandler.py @@ -25,7 +25,10 @@ def __init__(self, controller): # init signals logger.info('Registering signals...') self._old = {} - self._register() + try: + self._register() + except ValueError as e: + logger.warning("Can't register signals: %s" % e) def stop(self): for sig, callback in self._old.items(): diff --git a/circus/util.py b/circus/util.py index 97bf0ea44..fb7345e0b 100644 --- a/circus/util.py +++ b/circus/util.py @@ -7,7 +7,6 @@ import socket import sys import time -import traceback import json import struct try: @@ -1084,10 +1083,9 @@ def exception(self, timeout=None): def check_future_exception_and_log(future): if isinstance(future, concurrent.Future): - exception = future.exception() - if exception is not None: - logger.error("exception %s caught" % exception) - if hasattr(future, "exc_info"): - exc_info = future.exc_info() - traceback.print_tb(exc_info[2]) - return exception + try: + future.result() + return None + except Exception as e: + logger.exception("exception %s caught" % e) + return e diff --git a/setup.py b/setup.py index 6a814f1ed..ac9816865 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ import sys +import os from setuptools import setup, find_packages from circus import __version__ @@ -13,6 +14,9 @@ except ImportError: install_requires.append('argparse') +if os.name == 'nt': + install_requires.append('pypiwin32') + with open("README.rst") as f: README = f.read() @@ -40,6 +44,7 @@ entry_points=""" [console_scripts] circusd = circus.circusd:main + circusrv = circus.circusrv:main circusd-stats = circus.stats:main circusctl = circus.circusctl:main circus-top = circus.stats.client:main