Improvements.
Change-Id: I773b69e72b7167292dbe0ec751039f15e76523a5
This commit is contained in:
parent
cac08110c7
commit
e0bf201b48
139
bin/server.py
139
bin/server.py
@ -18,11 +18,13 @@
|
|||||||
#
|
#
|
||||||
# Module authors:
|
# Module authors:
|
||||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
|
# Alexander Lenz <alexander.lenz@frm2.tum.de>
|
||||||
#
|
#
|
||||||
# *****************************************************************************
|
# *****************************************************************************
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import argparse
|
||||||
from os import path
|
from os import path
|
||||||
|
|
||||||
# Pathes magic to make python find out stuff.
|
# Pathes magic to make python find out stuff.
|
||||||
@ -33,75 +35,84 @@ pid_path = path.join(basepath, 'pid')
|
|||||||
log_path = path.join(basepath, 'log')
|
log_path = path.join(basepath, 'log')
|
||||||
sys.path[0] = path.join(basepath, 'src')
|
sys.path[0] = path.join(basepath, 'src')
|
||||||
|
|
||||||
import logger
|
import loggers
|
||||||
import argparse
|
from server import Server
|
||||||
from lib import check_pidfile, start_server, kill_server
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Manage a SECoP server")
|
|
||||||
loggroup = parser.add_mutually_exclusive_group()
|
|
||||||
loggroup.add_argument("-v", "--verbose",
|
|
||||||
help="Output lots of diagnostic information",
|
|
||||||
action='store_true', default=False)
|
|
||||||
loggroup.add_argument("-q", "--quiet", help="suppress non-error messages",
|
|
||||||
action='store_true', default=False)
|
|
||||||
parser.add_argument("action",
|
|
||||||
help="What to do: (re)start, status or stop",
|
|
||||||
choices=['start', 'status', 'stop', 'restart'],
|
|
||||||
default="status")
|
|
||||||
parser.add_argument("name",
|
|
||||||
help="Name of the instance.\n"
|
|
||||||
" Uses etc/name.cfg for configuration\n"
|
|
||||||
"may be omitted to mean ALL (which are configured)",
|
|
||||||
nargs='?', default='')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
|
|
||||||
logger = logger.get_logger('startup', loglevel)
|
|
||||||
|
|
||||||
logger.debug("action specified %r" % args.action)
|
def parseArgv(argv):
|
||||||
|
parser = argparse.ArgumentParser(description="Manage a SECoP server")
|
||||||
|
loggroup = parser.add_mutually_exclusive_group()
|
||||||
|
loggroup.add_argument("-v", "--verbose",
|
||||||
|
help="Output lots of diagnostic information",
|
||||||
|
action='store_true', default=False)
|
||||||
|
loggroup.add_argument("-q", "--quiet", help="suppress non-error messages",
|
||||||
|
action='store_true', default=False)
|
||||||
|
parser.add_argument("action",
|
||||||
|
help="What to do: (re)start, status or stop",
|
||||||
|
choices=['start', 'status', 'stop', 'restart'],
|
||||||
|
default="status")
|
||||||
|
parser.add_argument("name",
|
||||||
|
help="Name of the instance.\n"
|
||||||
|
" Uses etc/name.cfg for configuration\n"
|
||||||
|
"may be omitted to mean ALL (which are configured)",
|
||||||
|
nargs='?', default='')
|
||||||
|
parser.add_argument('-d',
|
||||||
|
'--daemonize',
|
||||||
|
action='store_true',
|
||||||
|
help='Run as daemon',
|
||||||
|
default=False)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def handle_servername(name, action):
|
def main(argv=None):
|
||||||
pidfile = path.join(pid_path, name + '.pid')
|
if argv is None:
|
||||||
cfgfile = path.join(etc_path, name + '.cfg')
|
argv = sys.argv
|
||||||
if action == "restart":
|
|
||||||
handle_servername(name, 'stop')
|
args = parseArgv(argv[1:])
|
||||||
handle_servername(name, 'start')
|
|
||||||
return
|
loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info')
|
||||||
elif action == "start":
|
loggers.initLogging('secop', loglevel, path.join(log_path))
|
||||||
logger.info("Starting server %s" % name)
|
logger = loggers.log
|
||||||
# XXX also do it !
|
|
||||||
start_server(name, basepath, loglevel)
|
srvNames = []
|
||||||
elif action == "stop":
|
|
||||||
pid = check_pidfile(pidfile)
|
if not args.name:
|
||||||
if pid:
|
print('No name given, iterating over all specified servers')
|
||||||
logger.info("Stopping server %s" % name)
|
for dirpath, dirs, files in os.walk(etc_path):
|
||||||
# XXX also do it!
|
for fn in files:
|
||||||
stop_server(pidfile)
|
if fn.endswith('.cfg'):
|
||||||
else:
|
srvNames.append(fn[:-4])
|
||||||
logger.info("Server %s already dead" % name)
|
else:
|
||||||
elif action == "status":
|
logger.debug('configfile with strange extension found: %r'
|
||||||
if check_pidfile(pidfile):
|
% path.basename(fn))
|
||||||
logger.info("Server %s is running." % name)
|
# ignore subdirs!
|
||||||
else:
|
while (dirs):
|
||||||
logger.info("Server %s is DEAD!" % name)
|
dirs.pop()
|
||||||
else:
|
else:
|
||||||
logger.error("invalid action specified: How can this ever happen???")
|
srvNames = [args.name]
|
||||||
|
|
||||||
print "================================"
|
srvs = []
|
||||||
if not args.name:
|
for entry in srvNames:
|
||||||
logger.debug("No name given, iterating over all specified servers")
|
srv = Server(entry, basepath)
|
||||||
for dirpath, dirs, files in os.walk(etc_path):
|
srvs.append(srv)
|
||||||
for fn in files:
|
|
||||||
if fn.endswith('.cfg'):
|
if args.action == "restart":
|
||||||
handle_servername(fn[:-4], args.action)
|
srv.stop()
|
||||||
|
srv.start()
|
||||||
|
elif args.action == "start":
|
||||||
|
if len(srvNames) > 1 or args.daemonize:
|
||||||
|
srv.start()
|
||||||
else:
|
else:
|
||||||
logger.debug('configfile with strange extension found: %r'
|
srv.run()
|
||||||
% path.basename(fn))
|
elif args.action == "stop":
|
||||||
# ignore subdirs!
|
srv.stop()
|
||||||
while(dirs):
|
elif args.action == "status":
|
||||||
dirs.pop()
|
if srv.isAlive():
|
||||||
else:
|
logger.info("Server %s is running." % entry)
|
||||||
handle_servername(args.name, args.action)
|
else:
|
||||||
print "================================"
|
logger.info("Server %s is DEAD!" % entry)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
|
@ -161,7 +161,7 @@ class Device(object):
|
|||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
# may be overriden in other classes
|
# may be overriden in other classes
|
||||||
pass
|
self.log.debug('init()')
|
||||||
|
|
||||||
|
|
||||||
class Readable(Device):
|
class Readable(Device):
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# *****************************************************************************
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify it under
|
|
||||||
# the terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation; either version 2 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
# Module authors:
|
|
||||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
|
||||||
#
|
|
||||||
# *****************************************************************************
|
|
||||||
|
|
||||||
"""Loggers"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from os import path
|
|
||||||
|
|
||||||
from paths import log_path
|
|
||||||
|
|
||||||
|
|
||||||
def get_logger(inst='', loglevel=logging.INFO):
|
|
||||||
loglevelmap = {'debug': logging.DEBUG,
|
|
||||||
'info': logging.INFO,
|
|
||||||
'warning': logging.WARNING,
|
|
||||||
'error': logging.ERROR,
|
|
||||||
}
|
|
||||||
loglevel = loglevelmap.get(loglevel, loglevel)
|
|
||||||
logging.basicConfig(level=loglevel,
|
|
||||||
format='#[%(asctime)-15s][%(levelname)s]: %(message)s')
|
|
||||||
|
|
||||||
logger = logging.getLogger(inst)
|
|
||||||
logger.setLevel(loglevel)
|
|
||||||
fh = logging.FileHandler(path.join(log_path, inst + '.log'))
|
|
||||||
fh.setLevel(loglevel)
|
|
||||||
logger.addHandler(fh)
|
|
||||||
logging.root.addHandler(fh) # ???
|
|
||||||
return logger
|
|
343
src/loggers/__init__.py
Normal file
343
src/loggers/__init__.py
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Alexander Lenz <alexander.lenz@posteo.de>
|
||||||
|
# Georg Brandl <georg@python.org>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import linecache
|
||||||
|
import traceback
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from os import path
|
||||||
|
from logging import Logger, Formatter, Handler, DEBUG, INFO, WARNING, ERROR, \
|
||||||
|
setLoggerClass
|
||||||
|
|
||||||
|
from . import colors
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
LOGFMT = '%(asctime)s : %(levelname)-7s : %(name)-15s: %(message)s'
|
||||||
|
DATEFMT = '%H:%M:%S'
|
||||||
|
DATESTAMP_FMT = '%Y-%m-%d'
|
||||||
|
SECONDS_PER_DAY = 60 * 60 * 24
|
||||||
|
|
||||||
|
LOGLEVELS = {'debug': DEBUG, 'info': INFO, 'warning': WARNING, 'error': ERROR}
|
||||||
|
INVLOGLEVELS = {value : key for key, value in LOGLEVELS.items()}
|
||||||
|
|
||||||
|
|
||||||
|
log = None
|
||||||
|
|
||||||
|
|
||||||
|
def initLogging(rootname='secop', rootlevel='info', logdir='/tmp/log'):
|
||||||
|
global log
|
||||||
|
setLoggerClass(SecopLogger)
|
||||||
|
log = SecopLogger(rootname)
|
||||||
|
log.setLevel(LOGLEVELS[rootlevel])
|
||||||
|
|
||||||
|
# console logging for fg process
|
||||||
|
log.addHandler(ColoredConsoleHandler())
|
||||||
|
|
||||||
|
# logfile for fg and bg process
|
||||||
|
log.addHandler(LogfileHandler(logdir, rootname))
|
||||||
|
|
||||||
|
|
||||||
|
def getLogger(name, subdir=False):
|
||||||
|
global log
|
||||||
|
return log.getChild(name, subdir)
|
||||||
|
|
||||||
|
|
||||||
|
class SecopLogger(Logger):
|
||||||
|
maxLogNameLength = 0
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
Logger.__init__(self, *args, **kwargs)
|
||||||
|
SecopLogger._storeLoggerNameLength(self)
|
||||||
|
|
||||||
|
|
||||||
|
def getChild(self, suffix, ownDir=False):
|
||||||
|
child = Logger.getChild(self, suffix)
|
||||||
|
child.setLevel(self.getEffectiveLevel())
|
||||||
|
|
||||||
|
for handler in self._collectHandlers():
|
||||||
|
if ownDir and isinstance(handler, LogfileHandler):
|
||||||
|
handler = handler.getChild(suffix)
|
||||||
|
child.addHandler(handler)
|
||||||
|
|
||||||
|
child.propagate = False
|
||||||
|
|
||||||
|
return child
|
||||||
|
|
||||||
|
def _collectHandlers(self):
|
||||||
|
result = []
|
||||||
|
|
||||||
|
log = self
|
||||||
|
while log is not None:
|
||||||
|
result += log.handlers
|
||||||
|
log = log.parent
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _storeLoggerNameLength(logObj):
|
||||||
|
# store max logger name length for formatting
|
||||||
|
if len(logObj.name) > SecopLogger.maxLogNameLength:
|
||||||
|
SecopLogger.maxLogNameLength = len(logObj.name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleFormatter(Formatter):
|
||||||
|
"""
|
||||||
|
A lightweight formatter for the interactive console, with optional
|
||||||
|
colored output.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, fmt=None, datefmt=None, colorize=None):
|
||||||
|
Formatter.__init__(self, fmt, datefmt)
|
||||||
|
if colorize:
|
||||||
|
self.colorize = colorize
|
||||||
|
else:
|
||||||
|
self.colorize = lambda c, s: s
|
||||||
|
|
||||||
|
def formatException(self, exc_info):
|
||||||
|
return traceback.format_exception_only(*exc_info[0:2])[-1]
|
||||||
|
|
||||||
|
def formatTime(self, record, datefmt=None):
|
||||||
|
return time.strftime(datefmt or DATEFMT,
|
||||||
|
self.converter(record.created))
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
record.message = record.getMessage()
|
||||||
|
levelno = record.levelno
|
||||||
|
datefmt = self.colorize('lightgray', '[%(asctime)s] ')
|
||||||
|
namefmt = '%(name)-' + str(SecopLogger.maxLogNameLength) + 's: '
|
||||||
|
if levelno <= DEBUG:
|
||||||
|
fmtstr = self.colorize('darkgray', '%s%%(message)s' % namefmt)
|
||||||
|
elif levelno <= INFO:
|
||||||
|
fmtstr = '%s%%(message)s' % namefmt
|
||||||
|
elif levelno <= WARNING:
|
||||||
|
fmtstr = self.colorize('fuchsia', '%s%%(levelname)s: %%(message)s'
|
||||||
|
% namefmt)
|
||||||
|
else:
|
||||||
|
# Add exception type to error (if caused by exception)
|
||||||
|
msgPrefix = ''
|
||||||
|
if record.exc_info:
|
||||||
|
msgPrefix = '%s: ' % record.exc_info[0].__name__
|
||||||
|
|
||||||
|
fmtstr = self.colorize('red', '%s%%(levelname)s: %s%%(message)s'
|
||||||
|
% (namefmt, msgPrefix))
|
||||||
|
fmtstr = datefmt + fmtstr
|
||||||
|
if not getattr(record, 'nonl', False):
|
||||||
|
fmtstr += '\n'
|
||||||
|
record.asctime = self.formatTime(record, self.datefmt)
|
||||||
|
s = fmtstr % record.__dict__
|
||||||
|
# never output more exception info -- the exception message is already
|
||||||
|
# part of the log message because of our special logger behavior
|
||||||
|
# if record.exc_info:
|
||||||
|
# # *not* caching exception text on the record, since it's
|
||||||
|
# # only a short version
|
||||||
|
# s += self.formatException(record.exc_info)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def format_extended_frame(frame):
|
||||||
|
ret = []
|
||||||
|
for key, value in frame.f_locals.items():
|
||||||
|
try:
|
||||||
|
valstr = repr(value)[:256]
|
||||||
|
except Exception:
|
||||||
|
valstr = '<cannot be displayed>'
|
||||||
|
ret.append(' %-20s = %s\n' % (key, valstr))
|
||||||
|
ret.append('\n')
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def format_extended_traceback(etype, value, tb):
|
||||||
|
ret = ['Traceback (most recent call last):\n']
|
||||||
|
while tb is not None:
|
||||||
|
frame = tb.tb_frame
|
||||||
|
filename = frame.f_code.co_filename
|
||||||
|
item = ' File "%s", line %d, in %s\n' % (filename, tb.tb_lineno,
|
||||||
|
frame.f_code.co_name)
|
||||||
|
linecache.checkcache(filename)
|
||||||
|
line = linecache.getline(filename, tb.tb_lineno, frame.f_globals)
|
||||||
|
if line:
|
||||||
|
item = item + ' %s\n' % line.strip()
|
||||||
|
ret.append(item)
|
||||||
|
if filename != '<script>':
|
||||||
|
ret += format_extended_frame(tb.tb_frame)
|
||||||
|
tb = tb.tb_next
|
||||||
|
ret += traceback.format_exception_only(etype, value)
|
||||||
|
return ''.join(ret).rstrip('\n')
|
||||||
|
|
||||||
|
|
||||||
|
class LogfileFormatter(Formatter):
|
||||||
|
"""
|
||||||
|
The standard Formatter does not support milliseconds with an explicit
|
||||||
|
datestamp format. It also doesn't show the full traceback for exceptions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
extended_traceback = True
|
||||||
|
|
||||||
|
def formatException(self, ei):
|
||||||
|
if self.extended_traceback:
|
||||||
|
s = format_extended_traceback(*ei)
|
||||||
|
else:
|
||||||
|
s = ''.join(traceback.format_exception(ei[0], ei[1], ei[2],
|
||||||
|
sys.maxsize))
|
||||||
|
if s.endswith('\n'):
|
||||||
|
s = s[:-1]
|
||||||
|
return s
|
||||||
|
|
||||||
|
def formatTime(self, record, datefmt=None):
|
||||||
|
res = time.strftime(DATEFMT, self.converter(record.created))
|
||||||
|
res += ',%03d' % record.msecs
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class StreamHandler(Handler):
|
||||||
|
"""Reimplemented from logging: remove cruft, remove bare excepts."""
|
||||||
|
|
||||||
|
def __init__(self, stream=None):
|
||||||
|
Handler.__init__(self)
|
||||||
|
self.stream = stream
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
self.acquire()
|
||||||
|
try:
|
||||||
|
if self.stream and hasattr(self.stream, 'flush'):
|
||||||
|
self.stream.flush()
|
||||||
|
finally:
|
||||||
|
self.release()
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
try:
|
||||||
|
msg = self.format(record)
|
||||||
|
try:
|
||||||
|
self.stream.write('%s\n' % msg)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
self.stream.write('%s\n' % msg.encode('utf-8'))
|
||||||
|
self.flush()
|
||||||
|
except Exception:
|
||||||
|
self.handleError(record)
|
||||||
|
|
||||||
|
|
||||||
|
class LogfileHandler(StreamHandler):
|
||||||
|
"""
|
||||||
|
Logs to log files with a date stamp appended, and rollover on midnight.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, directory, filenameprefix, dayfmt=DATESTAMP_FMT):
|
||||||
|
self._directory = path.join(directory, filenameprefix)
|
||||||
|
if not path.isdir(self._directory):
|
||||||
|
os.makedirs(self._directory)
|
||||||
|
self._currentsymlink = path.join(self._directory, 'current')
|
||||||
|
self._filenameprefix = filenameprefix
|
||||||
|
self._pathnameprefix = path.join(self._directory, filenameprefix)
|
||||||
|
self._dayfmt = dayfmt
|
||||||
|
# today's logfile name
|
||||||
|
basefn = self._pathnameprefix + '-' + time.strftime(dayfmt) + '.log'
|
||||||
|
self.baseFilename = path.abspath(basefn)
|
||||||
|
self.mode = 'a'
|
||||||
|
StreamHandler.__init__(self)
|
||||||
|
# determine time of first midnight from now on
|
||||||
|
t = time.localtime()
|
||||||
|
self.rollover_at = time.mktime((t[0], t[1], t[2], 0, 0, 0,
|
||||||
|
t[6], t[7], t[8])) + SECONDS_PER_DAY
|
||||||
|
self.setFormatter(LogfileFormatter(LOGFMT, DATEFMT))
|
||||||
|
self.disabled = False
|
||||||
|
|
||||||
|
def getChild(self, name):
|
||||||
|
return LogfileHandler(self._directory, name)
|
||||||
|
|
||||||
|
def filter(self, record):
|
||||||
|
return not self.disabled
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
try:
|
||||||
|
t = int(time.time())
|
||||||
|
if t >= self.rollover_at:
|
||||||
|
self.doRollover()
|
||||||
|
if self.stream is None:
|
||||||
|
self.stream = self._open()
|
||||||
|
StreamHandler.emit(self, record)
|
||||||
|
except Exception:
|
||||||
|
self.handleError(record)
|
||||||
|
|
||||||
|
def enable(self, enabled):
|
||||||
|
if enabled:
|
||||||
|
self.disabled = False
|
||||||
|
self.stream.close()
|
||||||
|
self.stream = self._open()
|
||||||
|
else:
|
||||||
|
self.disabled = True
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.acquire()
|
||||||
|
try:
|
||||||
|
if self.stream:
|
||||||
|
self.flush()
|
||||||
|
if hasattr(self.stream, 'close'):
|
||||||
|
self.stream.close()
|
||||||
|
StreamHandler.close(self)
|
||||||
|
self.stream = None
|
||||||
|
finally:
|
||||||
|
self.release()
|
||||||
|
|
||||||
|
def doRollover(self):
|
||||||
|
self.stream.close()
|
||||||
|
self.baseFilename = self._pathnameprefix + '-' + \
|
||||||
|
time.strftime(self._dayfmt) + '.log'
|
||||||
|
self.stream = self._open()
|
||||||
|
self.rollover_at += SECONDS_PER_DAY
|
||||||
|
|
||||||
|
def _open(self):
|
||||||
|
# update 'current' symlink upon open
|
||||||
|
try:
|
||||||
|
os.remove(self._currentsymlink)
|
||||||
|
except OSError:
|
||||||
|
# if the symlink does not (yet) exist, OSError is raised.
|
||||||
|
# should happen at most once per installation....
|
||||||
|
pass
|
||||||
|
if hasattr(os, 'symlink'):
|
||||||
|
os.symlink(path.basename(self.baseFilename), self._currentsymlink)
|
||||||
|
# finally open the new logfile....
|
||||||
|
return open(self.baseFilename, self.mode)
|
||||||
|
|
||||||
|
|
||||||
|
class ColoredConsoleHandler(StreamHandler):
|
||||||
|
"""
|
||||||
|
A handler class that writes colorized records to standard output.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
StreamHandler.__init__(self, sys.stdout)
|
||||||
|
self.setFormatter(ConsoleFormatter(datefmt=DATEFMT,
|
||||||
|
colorize=colors.colorize))
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
msg = self.format(record)
|
||||||
|
try:
|
||||||
|
self.stream.write(msg)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
self.stream.write(msg.encode('utf-8'))
|
||||||
|
self.stream.flush()
|
67
src/loggers/colors.py
Normal file
67
src/loggers/colors.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Alexander Lenz <alexander.lenz@posteo.de>
|
||||||
|
# Georg Brandl <georg@python.org>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
"""Console coloring handlers."""
|
||||||
|
|
||||||
|
|
||||||
|
_codes = {}
|
||||||
|
|
||||||
|
_attrs = {
|
||||||
|
'reset': '39;49;00m',
|
||||||
|
'bold': '01m',
|
||||||
|
'faint': '02m',
|
||||||
|
'standout': '03m',
|
||||||
|
'underline': '04m',
|
||||||
|
'blink': '05m',
|
||||||
|
}
|
||||||
|
|
||||||
|
for _name, _value in _attrs.items():
|
||||||
|
_codes[_name] = '\x1b[' + _value
|
||||||
|
|
||||||
|
_colors = [
|
||||||
|
('black', 'darkgray'),
|
||||||
|
('darkred', 'red'),
|
||||||
|
('darkgreen', 'green'),
|
||||||
|
('brown', 'yellow'),
|
||||||
|
('darkblue', 'blue'),
|
||||||
|
('purple', 'fuchsia'),
|
||||||
|
('turquoise', 'teal'),
|
||||||
|
('lightgray', 'white'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for _i, (_dark, _light) in enumerate(_colors):
|
||||||
|
_codes[_dark] = '\x1b[%im' % (_i + 30)
|
||||||
|
_codes[_light] = '\x1b[%i;01m' % (_i + 30)
|
||||||
|
|
||||||
|
|
||||||
|
def colorize(name, text):
|
||||||
|
return _codes.get(name, '') + text + _codes.get('reset', '')
|
||||||
|
|
||||||
|
|
||||||
|
def colorcode(name):
|
||||||
|
return _codes.get(name, '')
|
||||||
|
|
||||||
|
|
||||||
|
def nocolor():
|
||||||
|
for key in list(_codes):
|
||||||
|
_codes[key] = ''
|
@ -161,7 +161,7 @@ class Dispatcher(object):
|
|||||||
dn.append(devname)
|
dn.append(devname)
|
||||||
dev = self.get_device(devname)
|
dev = self.get_device(devname)
|
||||||
descriptive_data = {
|
descriptive_data = {
|
||||||
'class': dev.__class__,
|
'class': dev.__class__.__name__,
|
||||||
#'bases': dev.__bases__,
|
#'bases': dev.__bases__,
|
||||||
'parameters': dev.PARAMS.keys(),
|
'parameters': dev.PARAMS.keys(),
|
||||||
'commands': dev.CMDS.keys(),
|
'commands': dev.CMDS.keys(),
|
||||||
|
176
src/server.py
Normal file
176
src/server.py
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# *****************************************************************************
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation; either version 2 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Module authors:
|
||||||
|
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
||||||
|
# Alexander Lenz <alexander.lenz@frm2.tum.de>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
|
||||||
|
"""Define helpers"""
|
||||||
|
import os
|
||||||
|
import psutil
|
||||||
|
import ConfigParser
|
||||||
|
|
||||||
|
# apt install python-daemon !!!do not use pip install daemon <- wrong version!
|
||||||
|
import daemon
|
||||||
|
#from daemon import pidlockfile
|
||||||
|
|
||||||
|
import loggers
|
||||||
|
from lib import read_pidfile, write_pidfile, get_class, check_pidfile
|
||||||
|
from protocol.dispatcher import Dispatcher
|
||||||
|
from protocol.interface import INTERFACES
|
||||||
|
from protocol.transport import ENCODERS, FRAMERS
|
||||||
|
from errors import ConfigError
|
||||||
|
|
||||||
|
|
||||||
|
class Server(object):
|
||||||
|
def __init__(self, name, workdir, parentLogger=None):
|
||||||
|
self._name = name
|
||||||
|
self._workdir = workdir
|
||||||
|
|
||||||
|
if parentLogger is None:
|
||||||
|
parentLogger = loggers.log
|
||||||
|
self.log = parentLogger.getChild(name, True)
|
||||||
|
|
||||||
|
self._pidfile = os.path.join(workdir, 'pid', name + '.pid')
|
||||||
|
self._cfgfile = os.path.join(workdir, 'etc', name + '.cfg')
|
||||||
|
|
||||||
|
self._dispatcher = None
|
||||||
|
self._interface = None
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
pidfile = pidlockfile.TimeoutPIDLockFile(self._pidfile, 3)
|
||||||
|
|
||||||
|
with daemon.DaemonContext(
|
||||||
|
# files_preserve=[logFileHandler.stream],
|
||||||
|
pidfile=pidfile,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
# write_pidfile(pidfilename, os.getpid())
|
||||||
|
self.run()
|
||||||
|
except Exception as e:
|
||||||
|
self.log.exception(e)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
pid = read_pidfile(self._pidfile)
|
||||||
|
if pid is None:
|
||||||
|
# already dead/not started yet
|
||||||
|
return
|
||||||
|
# get process for this pid
|
||||||
|
for proc in psutil.process_iter():
|
||||||
|
if proc.pid == pid:
|
||||||
|
break
|
||||||
|
proc.terminate()
|
||||||
|
proc.wait(3)
|
||||||
|
proc.kill()
|
||||||
|
|
||||||
|
def isAlive(self):
|
||||||
|
if check_pidfile(self._pidfile):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# write pidfile before doing actual things
|
||||||
|
write_pidfile(self._pidfile, os.getpid())
|
||||||
|
self._processCfg()
|
||||||
|
|
||||||
|
self.log.info('startup done, handling transport messages')
|
||||||
|
self._interface.serve_forever()
|
||||||
|
|
||||||
|
def _processCfg(self):
|
||||||
|
self.log.debug('Parse config file %s ...' % self._cfgfile)
|
||||||
|
|
||||||
|
parser = ConfigParser.SafeConfigParser()
|
||||||
|
if not parser.read([self._cfgfile]):
|
||||||
|
self.log.error('Couldn\'t read cfg file !')
|
||||||
|
raise ConfigError('Couldn\'t read cfg file %r' % self._cfgfile)
|
||||||
|
|
||||||
|
if not parser.has_section('server'):
|
||||||
|
raise ConfigError(
|
||||||
|
'cfg file %r needs a \'server\' section!' % self._cfgfile)
|
||||||
|
|
||||||
|
deviceopts = []
|
||||||
|
serveropts = {}
|
||||||
|
for section in parser.sections():
|
||||||
|
if section == 'server':
|
||||||
|
# store for later
|
||||||
|
serveropts = dict(item for item in parser.items('server'))
|
||||||
|
if section.lower().startswith('device '):
|
||||||
|
# device section
|
||||||
|
# omit leading 'device ' string
|
||||||
|
devname = section[len('device '):]
|
||||||
|
devopts = dict(item for item in parser.items(section))
|
||||||
|
if 'class' not in devopts:
|
||||||
|
self.log.error('Device %s needs a class option!')
|
||||||
|
raise ConfigError(
|
||||||
|
'cfgfile %r: Device %s needs a class option!'
|
||||||
|
% (self._cfgfile, devname))
|
||||||
|
# try to import the class, raise if this fails
|
||||||
|
devopts['class'] = get_class(devopts['class'])
|
||||||
|
# all went well so far
|
||||||
|
deviceopts.append([devname, devopts])
|
||||||
|
|
||||||
|
self._processServerOptions(serveropts)
|
||||||
|
self._processDeviceOptions(deviceopts)
|
||||||
|
|
||||||
|
def _processDeviceOptions(self, deviceopts):
|
||||||
|
# check devices opts by creating them
|
||||||
|
devs = []
|
||||||
|
for devname, devopts in deviceopts:
|
||||||
|
devclass = devopts.pop('class')
|
||||||
|
# create device
|
||||||
|
self.log.debug('Creating Device %r' % devname)
|
||||||
|
devobj = devclass(self.log.getChild(devname), devopts, devname,
|
||||||
|
self._dispatcher)
|
||||||
|
devs.append([devname, devobj])
|
||||||
|
|
||||||
|
# connect devices with dispatcher
|
||||||
|
for devname, devobj in devs:
|
||||||
|
self.log.info('registering device %r' % devname)
|
||||||
|
self._dispatcher.register_device(devobj, devname)
|
||||||
|
# also call init on the devices
|
||||||
|
devobj.init()
|
||||||
|
|
||||||
|
def _processServerOptions(self, serveropts):
|
||||||
|
# eval serveropts
|
||||||
|
framingClass = FRAMERS[serveropts.pop('framing')]
|
||||||
|
encodingClass = ENCODERS[serveropts.pop('encoding')]
|
||||||
|
interfaceClass = INTERFACES[serveropts.pop('interface')]
|
||||||
|
|
||||||
|
self._dispatcher = self._buildObject('Dispatcher', Dispatcher,
|
||||||
|
dict(encoding=encodingClass(),
|
||||||
|
framing=framingClass()))
|
||||||
|
|
||||||
|
# split 'server' section to allow more than one interface
|
||||||
|
# also means to move encoding and framing to the interface,
|
||||||
|
# so that the dispatcher becomes agnostic
|
||||||
|
self._interface = self._buildObject('Interface', interfaceClass,
|
||||||
|
serveropts,
|
||||||
|
self._dispatcher)
|
||||||
|
|
||||||
|
def _buildObject(self, name, cls, options, *args):
|
||||||
|
self.log.debug('Creating %s ...' % name)
|
||||||
|
# cls.__init__ should pop all used args from options!
|
||||||
|
obj = cls(self.log.getChild(name.lower()), options, *args)
|
||||||
|
if options:
|
||||||
|
raise ConfigError('%s: don\'t know how to handle option(s): %s' % (
|
||||||
|
cls.__name__,
|
||||||
|
', '.join(options.keys())))
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
167
src/startup.py
167
src/startup.py
@ -1,167 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# *****************************************************************************
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify it under
|
|
||||||
# the terms of the GNU General Public License as published by the Free Software
|
|
||||||
# Foundation; either version 2 of the License, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License along with
|
|
||||||
# this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
# Module authors:
|
|
||||||
# Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
|
|
||||||
#
|
|
||||||
# *****************************************************************************
|
|
||||||
|
|
||||||
"""Define helpers"""
|
|
||||||
import os
|
|
||||||
import psutil
|
|
||||||
import ConfigParser
|
|
||||||
|
|
||||||
# apt install python-daemon !!!do not use pip install daemon <- wrong version!
|
|
||||||
import daemon
|
|
||||||
from daemon import pidlockfile
|
|
||||||
|
|
||||||
from lib import read_pidfile, write_pidfile, get_class
|
|
||||||
from protocol.dispatcher import Dispatcher
|
|
||||||
from protocol.interface import INTERFACES
|
|
||||||
from protocol.transport import ENCODERS, FRAMERS
|
|
||||||
from errors import ConfigError
|
|
||||||
from logger import get_logger
|
|
||||||
|
|
||||||
__ALL__ = ['kill_server', 'start_server']
|
|
||||||
|
|
||||||
|
|
||||||
def kill_server(pidfile):
|
|
||||||
"""kill a server specified by a pidfile"""
|
|
||||||
pid = read_pidfile(pidfile)
|
|
||||||
if pid is None:
|
|
||||||
# already dead/not started yet
|
|
||||||
return
|
|
||||||
# get process for this pid
|
|
||||||
for proc in psutil.process_iter():
|
|
||||||
if proc.pid == pid:
|
|
||||||
break
|
|
||||||
proc.terminate()
|
|
||||||
proc.wait(3)
|
|
||||||
proc.kill()
|
|
||||||
|
|
||||||
|
|
||||||
def start_server(srvname, base_path, loglevel, daemonize=False):
|
|
||||||
"""start a server, part1
|
|
||||||
|
|
||||||
handle the daemonizing and logging stuff and call the second step
|
|
||||||
"""
|
|
||||||
pidfilename = os.path.join(base_path, 'pid', srvname + '.pid')
|
|
||||||
pidfile = pidlockfile.TimeoutPIDLockFile(pidfilename, 3)
|
|
||||||
if daemonize:
|
|
||||||
with daemon.DaemonContext(
|
|
||||||
#files_preserve=[logFileHandler.stream],
|
|
||||||
pidfile=pidfile,
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
#write_pidfile(pidfilename, os.getpid())
|
|
||||||
startup(srvname, base_path, loglevel)
|
|
||||||
except Exception as e:
|
|
||||||
logging.exception(e)
|
|
||||||
else:
|
|
||||||
write_pidfile(pidfilename, os.getpid())
|
|
||||||
startup(srvname, base_path, loglevel) # blocks!
|
|
||||||
|
|
||||||
|
|
||||||
# unexported stuff here
|
|
||||||
|
|
||||||
def startup(srvname, base_path, loglevel):
|
|
||||||
"""really start a server (part2)
|
|
||||||
|
|
||||||
loads the config, initiate all objects, link them together
|
|
||||||
and finally start the interface server.
|
|
||||||
Never returns. (may raise)
|
|
||||||
"""
|
|
||||||
cfgfile = os.path.join(base_path, 'etc', srvname + '.cfg')
|
|
||||||
|
|
||||||
logger = get_logger(srvname, loglevel=loglevel)
|
|
||||||
logger.debug('parsing %r' % cfgfile)
|
|
||||||
|
|
||||||
parser = ConfigParser.SafeConfigParser()
|
|
||||||
if not parser.read([cfgfile]):
|
|
||||||
logger.error('Couldn\'t read cfg file !')
|
|
||||||
raise ConfigError('Couldn\'t read cfg file %r' % cfgfile)
|
|
||||||
|
|
||||||
# iterate over all sections, checking for devices/server
|
|
||||||
deviceopts = []
|
|
||||||
serveropts = {}
|
|
||||||
for section in parser.sections():
|
|
||||||
if section == 'server':
|
|
||||||
# store for later
|
|
||||||
serveropts = dict(item for item in parser.items('server'))
|
|
||||||
if section.lower().startswith('device '):
|
|
||||||
# device section
|
|
||||||
# omit leading 'device ' string
|
|
||||||
devname = section[len('device '):]
|
|
||||||
devopts = dict(item for item in parser.items(section))
|
|
||||||
if 'class' not in devopts:
|
|
||||||
logger.error('Device %s needs a class option!')
|
|
||||||
raise ConfigError('cfgfile %r: Device %s needs a class option!'
|
|
||||||
% (cfgfile, devname))
|
|
||||||
# try to import the class, raise if this fails
|
|
||||||
devopts['class'] = get_class(devopts['class'])
|
|
||||||
# all went well so far
|
|
||||||
deviceopts.append([devname, devopts])
|
|
||||||
|
|
||||||
# there are several sections which resultin almost identical code: refactor
|
|
||||||
def init_object(name, cls, logger, options={}, *args):
|
|
||||||
logger.debug('Creating ' + name)
|
|
||||||
# cls.__init__ should pop all used args from options!
|
|
||||||
obj = cls(logger, options, *args)
|
|
||||||
if options:
|
|
||||||
raise ConfigError('%s: don\'t know how to handle option(s): %s' % (
|
|
||||||
cls.__name__,
|
|
||||||
', '.join(options.keys())))
|
|
||||||
return obj
|
|
||||||
|
|
||||||
# evaluate Server specific stuff
|
|
||||||
if not serveropts:
|
|
||||||
raise ConfigError('cfg file %r needs a \'server\' section!' % cfgfile)
|
|
||||||
|
|
||||||
# eval serveropts
|
|
||||||
Framing = FRAMERS[serveropts.pop('framing')]
|
|
||||||
Encoding = ENCODERS[serveropts.pop('encoding')]
|
|
||||||
Interface = INTERFACES[serveropts.pop('interface')]
|
|
||||||
|
|
||||||
dispatcher = init_object('Dispatcher', Dispatcher, logger,
|
|
||||||
dict(encoding=Encoding(),
|
|
||||||
framing=Framing()))
|
|
||||||
# split 'server' section to allow more than one interface
|
|
||||||
# also means to move encoding and framing to the interface,
|
|
||||||
# so that the dispatcher becomes agnostic
|
|
||||||
interface = init_object('Interface', Interface, logger, serveropts,
|
|
||||||
dispatcher)
|
|
||||||
|
|
||||||
# check devices opts by creating them
|
|
||||||
devs = []
|
|
||||||
for devname, devopts in deviceopts:
|
|
||||||
devclass = devopts.pop('class')
|
|
||||||
# create device
|
|
||||||
logger.debug('Creating Device %r' % devname)
|
|
||||||
devobj = devclass(logger, devopts, devname, dispatcher)
|
|
||||||
devs.append([devname, devobj])
|
|
||||||
|
|
||||||
# connect devices with dispatcher
|
|
||||||
for devname, devobj in devs:
|
|
||||||
logger.info('registering device %r' % devname)
|
|
||||||
dispatcher.register_device(devobj, devname)
|
|
||||||
# also call init on the devices
|
|
||||||
logger.debug('device.init()')
|
|
||||||
devobj.init()
|
|
||||||
|
|
||||||
# handle requests until stop is requsted
|
|
||||||
logger.info('startup done, handling transport messages')
|
|
||||||
interface.serve_forever()
|
|
Loading…
x
Reference in New Issue
Block a user