__author__ = 'hofmann' __version__ = '0.1.3' import sys import io import StringIO import logging class LoggingWrapper(object): CRITICAL = logging.CRITICAL FATAL = logging.CRITICAL ERROR = logging.ERROR WARNING = logging.WARNING WARN = logging.WARN INFO = logging.INFO DEBUG = logging.DEBUG NOTSET = logging.NOTSET _levelNames = logging._levelNames _map_logfile_handler = dict() def __init__(self, label="", verbose=True, message_format=None, date_format=None, stream=sys.stderr): """ Wrapper for the logging module for easy use. @attention: 'labels' are unique, LoggingWrapper with the same label will have the same streams! @param label: unique label for a LoggingWrapper @type label: basestring @param verbose: Not verbose means that only warnings and errors will be past to stream @type verbose: bool @param message_format: "%(asctime)s %(levelname)s: [%(name)s] %(message)s" @type message_format: basestring @param date_format: "%Y-%m-%d %H:%M:%S" @type date_format: basestring @param stream: To have no output at all, use "stream=None", stderr by default @type stream: file | FileIO | StringIO | None @return: None @rtype: None """ assert isinstance(label, basestring) assert isinstance(verbose, bool) assert message_format is None or isinstance(message_format, basestring) assert message_format is None or isinstance(date_format, basestring) assert stream is None or self.is_stream(stream) if message_format is None: message_format = "%(asctime)s %(levelname)s: [%(name)s] %(message)s" if date_format is None: date_format = "%Y-%m-%d %H:%M:%S" self.message_formatter = logging.Formatter(message_format, date_format) self._label = label self._logger = logging.getLogger(label) if label in LoggingWrapper._map_logfile_handler: return LoggingWrapper._map_logfile_handler[label] = None self._logger.setLevel(logging.DEBUG) if stream is not None: if verbose: self.add_log_stream(stream=stream, level=logging.INFO) else: self.add_log_stream(stream=stream, level=logging.WARNING) def __exit__(self, type, value, traceback): self._close() def __enter__(self): return self def __del__(self): self._close() @staticmethod def is_stream(stream): return isinstance(stream, (file, io.FileIO, StringIO.StringIO)) or stream.__class__ is StringIO.StringIO def get_label(self): return self._label def _close(self): """ Close all logfile handler, unless given as stream. Remove all stream handler from handler list, stopping the log service @attention: only files opened by LoggingWrapper will be closed! If given as stream, logfiles will be kept open! @return: None @rtype: None """ list_of_handlers = list(self._logger.handlers) for item in list_of_handlers: self._logger.removeHandler(item) if self._label not in LoggingWrapper._map_logfile_handler: return logfile_handler = LoggingWrapper._map_logfile_handler.pop(self._label) if logfile_handler is not None: logfile_handler.close() def info(self, message): """ Log general informative messages, that might be useful for the user. @param message: Message to be logged @type message: basestring @return: None @rtype: None """ self._logger.info(message) def error(self, message): """ Log an significant error that occured. @param message: Message to be logged @type message: basestring @return: None @rtype: None """ self._logger.error(message) def debug(self, message): """ Log a message for debugging puposes only. @param message: Message to be logged @type message: basestring @return: None @rtype: None """ self._logger.debug(message) def critical(self, message): """ Log a catastrophic error! @param message: Message to be logged @type message: basestring @return: None @rtype: None """ self._logger.critical(message) def exception(self, message): """ Log a exception with messages, that might be useful for the user. @attention: Call this only after an exception occurred, like in a "try..except.."! @param message: Message to be logged @type message: basestring @return: None @rtype: None """ self._logger.exception(message) def warning(self, message): """ Log warning messages, that the user should pay attention to. @param message: Message to be logged @type message: basestring @return: None @rtype: None """ self._logger.warning(message) def set_level(self, level): """ Set the minimum level of messages to be logged. Level of Log Messages CRITICAL 50 ERROR 40 WARNING 30 INFO 20 DEBUG 10 NOTSET 0 @param level: minimum level of messages to be logged @type level: int or long @return: None @rtype: None """ assert level in self._levelNames list_of_handlers = self._logger.handlers for handler in list_of_handlers: handler.setLevel(level) def add_log_stream(self, stream=sys.stderr, level=logging.INFO): """ Add a stream where messages are outputted to. @param stream: stderr/stdout or a file stream @type stream: file | FileIO | StringIO @param level: minimum level of messages to be logged @type level: int | long @return: None @rtype: None """ assert self.is_stream(stream) # assert isinstance(stream, (file, io.FileIO)) assert level in self._levelNames err_handler = logging.StreamHandler(stream) err_handler.setFormatter(self.message_formatter) err_handler.setLevel(level) self._logger.addHandler(err_handler) def set_log_file(self, log_file, mode='a', level=logging.INFO): """ Add a stream where messages are outputted to. @attention: file stream will only be closed if a file path is given! @param log_file: file stream or file path of logfile @type log_file: file | FileIO | StringIO | basestring @param mode: opening mode for logfile, if a file path is given @type mode: basestring @param level: minimum level of messages to be logged @type level: int or long @return: None @rtype: None """ assert isinstance(log_file, basestring) or self.is_stream(log_file) assert level in self._levelNames if LoggingWrapper._map_logfile_handler[self._label] is not None: self._logger.removeHandler(LoggingWrapper._map_logfile_handler[self._label]) LoggingWrapper._map_logfile_handler[self._label].close() LoggingWrapper._map_logfile_handler[self._label] = None if self.is_stream(log_file): self.add_log_stream(stream=log_file, level=level) return try: err_handler_file = logging.FileHandler(log_file, mode) err_handler_file.setFormatter(self.message_formatter) err_handler_file.setLevel(level) self._logger.addHandler(err_handler_file) LoggingWrapper._map_logfile_handler[self._label] = err_handler_file except Exception: sys.stderr.write("[LoggingWrapper] Could not open '{}' for logging\n".format(log_file)) return class DefaultLogging(object): _label = "Logging" def __init__(self, logfile=None, verbose=False, debug=False): """ Prototype class for any class needing a logger @attention: @param logfile: file handler or file path to a log file @type logfile: file | FileIO | StringIO | basestring @param verbose: Not verbose means that only warnings and errors will be past to stream @type verbose: bool @param debug: Display debug messages @type debug: bool @return: None @rtype: None """ assert isinstance(debug, bool) self._logger = LoggingWrapper(self._label, verbose=verbose) if logfile: self._logger.set_log_file(logfile, mode='a') self._debug = debug if debug: self._logger.set_level(self._logger.DEBUG) self._logfile = None if isinstance(logfile, basestring): self._logfile = logfile elif isinstance(logfile, (file, io.FileIO)): self._logfile = logfile.name self._verbose = verbose def __exit__(self, type, value, traceback): self._close() def __enter__(self): return self def __del__(self): self._close() def _close(self): self._logger = None def set_log_level(self, verbose, debug): """ Simplified way to set log level. @attention verbose: Ignored if 'debug' true @param verbose: Display info messages and higher @type verbose: bool @param debug: Display debug messages and higher @type debug: bool @return: Nothing @rtype: None """ if debug: self._logger.set_level(self._logger.DEBUG) elif verbose: self._logger.set_level(self._logger.INFO) else: self._logger.set_level(self._logger.WARNING) @staticmethod def is_stream(stream): """ Test for streams @param stream: Any kind of stream type @type stream: file | io.FileIO | StringIO.StringIO @return: True if stream @rtype: bool """ return isinstance(stream, (file, io.FileIO, StringIO.StringIO)) or stream.__class__ is StringIO.StringIO