From e0bf201b487438501db4e0639fe5f8d1c0d3345c Mon Sep 17 00:00:00 2001 From: Alexander Lenz Date: Thu, 4 Aug 2016 11:18:35 +0200 Subject: [PATCH] Improvements. Change-Id: I773b69e72b7167292dbe0ec751039f15e76523a5 --- bin/server.py | 139 ++++++++------- src/devices/core.py | 2 +- src/logger.py | 47 ----- src/loggers/__init__.py | 343 +++++++++++++++++++++++++++++++++++++ src/loggers/colors.py | 67 ++++++++ src/protocol/dispatcher.py | 2 +- src/server.py | 176 +++++++++++++++++++ src/startup.py | 167 ------------------ 8 files changed, 663 insertions(+), 280 deletions(-) delete mode 100644 src/logger.py create mode 100644 src/loggers/__init__.py create mode 100644 src/loggers/colors.py create mode 100644 src/server.py delete mode 100644 src/startup.py diff --git a/bin/server.py b/bin/server.py index 160a2cd..0b3cced 100755 --- a/bin/server.py +++ b/bin/server.py @@ -18,11 +18,13 @@ # # Module authors: # Enrico Faulhaber +# Alexander Lenz # # ***************************************************************************** import os import sys +import argparse from os import path # Pathes magic to make python find out stuff. @@ -33,75 +35,84 @@ pid_path = path.join(basepath, 'pid') log_path = path.join(basepath, 'log') sys.path[0] = path.join(basepath, 'src') -import logger -import argparse -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() +import loggers +from server import Server -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): - pidfile = path.join(pid_path, name + '.pid') - cfgfile = path.join(etc_path, name + '.cfg') - if action == "restart": - handle_servername(name, 'stop') - handle_servername(name, 'start') - return - elif action == "start": - logger.info("Starting server %s" % name) - # XXX also do it ! - start_server(name, basepath, loglevel) - elif action == "stop": - pid = check_pidfile(pidfile) - if pid: - logger.info("Stopping server %s" % name) - # XXX also do it! - stop_server(pidfile) - else: - logger.info("Server %s already dead" % name) - elif action == "status": - if check_pidfile(pidfile): - logger.info("Server %s is running." % name) - else: - logger.info("Server %s is DEAD!" % name) +def main(argv=None): + if argv is None: + argv = sys.argv + + args = parseArgv(argv[1:]) + + loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info') + loggers.initLogging('secop', loglevel, path.join(log_path)) + logger = loggers.log + + srvNames = [] + + if not args.name: + print('No name given, iterating over all specified servers') + for dirpath, dirs, files in os.walk(etc_path): + for fn in files: + if fn.endswith('.cfg'): + srvNames.append(fn[:-4]) + else: + logger.debug('configfile with strange extension found: %r' + % path.basename(fn)) + # ignore subdirs! + while (dirs): + dirs.pop() else: - logger.error("invalid action specified: How can this ever happen???") + srvNames = [args.name] -print "================================" -if not args.name: - logger.debug("No name given, iterating over all specified servers") - for dirpath, dirs, files in os.walk(etc_path): - for fn in files: - if fn.endswith('.cfg'): - handle_servername(fn[:-4], args.action) + srvs = [] + for entry in srvNames: + srv = Server(entry, basepath) + srvs.append(srv) + + if args.action == "restart": + srv.stop() + srv.start() + elif args.action == "start": + if len(srvNames) > 1 or args.daemonize: + srv.start() else: - logger.debug('configfile with strange extension found: %r' - % path.basename(fn)) - # ignore subdirs! - while(dirs): - dirs.pop() -else: - handle_servername(args.name, args.action) -print "================================" + srv.run() + elif args.action == "stop": + srv.stop() + elif args.action == "status": + if srv.isAlive(): + logger.info("Server %s is running." % entry) + else: + logger.info("Server %s is DEAD!" % entry) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/src/devices/core.py b/src/devices/core.py index 605754e..62d97c7 100644 --- a/src/devices/core.py +++ b/src/devices/core.py @@ -161,7 +161,7 @@ class Device(object): def init(self): # may be overriden in other classes - pass + self.log.debug('init()') class Readable(Device): diff --git a/src/logger.py b/src/logger.py deleted file mode 100644 index 55fb183..0000000 --- a/src/logger.py +++ /dev/null @@ -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 -# -# ***************************************************************************** - -"""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 diff --git a/src/loggers/__init__.py b/src/loggers/__init__.py new file mode 100644 index 0000000..2daac0e --- /dev/null +++ b/src/loggers/__init__.py @@ -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 +# Georg Brandl +# +# ***************************************************************************** + + +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 = '' + 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 != '