# Copyright (C) 2012 David Maxwell # # This file is part of PISM. # # PISM 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 3 of the License, or (at your option) any later # version. # # PISM 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 PISM; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Implements a rudimentary logging system. Messages are sent in client code via :func:`logError`, :func:`logMessage`, etc. These messages are then forwarded to any loggers that have been previously registered with :func:`add_logger`. A logger is either a function with signature:: def myLogger(message,verbosity) or a class that implements:: def __call__(self, message, verbosity) The string message is passed as ``message`` and verbosity is a standard PISM verbosity (an integer between 1-5). The following aliases are available * ``PISM.logging.kError`` * ``PISM.logging.kWarning`` * ``PISM.logging.kMessage`` * ``PISM.logging.kDebug`` * ``PISM.logging.kPrattle`` which are listed in increasing verbosity. Note that ``kError`` need not signify an error message, only a message with verbosity 1 that is ensured to be printed. Conversely, ``kPrattle`` signifies a verbosity level 5 with messages that rarely need to be displayed. The default logger, :func:`print_logger`, simply passes the message along as a call to :cpp:func:`verbPrintf`. See also the :class:`CaptureLogger`, which saves logged messages into an attribute of an :file:`.nc` file. The logging system does not log calls to verbPrintf directly. In particular, calls to verbPrintf from within PISM's C++ code do not pass through the python-based logging system. """ import PISM import time kError = 1 kWarning = 2 kMessage = 2 kDebug = 4 kPrattle = 5 def clear_loggers(): """Removes all members from the global list of loggers.""" global _loggers _loggers = [] def add_logger(logger): """Appends a new logger to the global list of loggers.""" global _loggers _loggers.append(logger) def log(message,verbosity): """Logs a message with the specified verbosity""" for l in _loggers: l(message,verbosity) def logError(message): """Convenience function for logging a message at the level of ``kError``""" log(message,kError) def logWarning(message): """Convenience function for logging a message at the level of ``kWarning``""" log(message,kWarning) def logMessage(message): """Convenience function for logging a message at the level of ``kMessage``""" log(message,kMessage) def logDebug(message): """Convenience function for logging a message at the level of ``kDebug``""" log(message,kDebug) def logPrattle(message): """Convenience function for logging a message at the level of ``kPrattle``""" log(message,kPrattle) def print_logger(message,verbosity): """Implements a logger that forwards messages to :cpp:func:`verbPrintf`.""" com = PISM.Context().com msg = str(message) PISM.verbPrintf(verbosity,com, msg) # The global list of loggers. _loggers = [ print_logger ] class CaptureLogger: """Implements a logger that appends log messages as they occur to an attribute of an :file:`.nc` file.""" def __init__(self,filename,attribute='pism_log'): """:param filename: Name of :file:`.nc` file to save the log to. :param attribute: Attribute name to save the log as.""" self.com = PISM.Context().com self.rank = PISM.Context().rank self.log = "" self.filename = filename self.attr = attribute def __call__(self,message,verbosity): """Saves the message to our internal log string and writes the string out to the file.""" if self.rank == 0 and PISM.getVerbosityLevel() >= verbosity: timestamp = time.strftime('%Y-%m-%d %H:%M:%S') self.log = "%s%s: %s" % (self.log,timestamp,message) d = PISM.netCDF.Dataset(self.filename,'a') d.__setattr__(self.attr,self.log) d.close() self.com.barrier() def readOldLog(self): """If the :file:`.nc` file we are logging to already has a log, read it in to the log we are about to make so that we append to it rather than overwriting it.""" if PISM.Context().rank == 0: d = PISM.netCDF.Dataset(self.filename,'a') if self.attr in d.ncattrs(): self.log += d.__getattr__(self.attr) d.close() self.com.barrier() def write(self,filename=None,attribute=None): """Save a copy of our log to the specified file and attribute.""" if filename is None: filename = self.filename if attribute is None: attribute = self.attr if PISM.Context().rank == 0: d = PISM.netCDF.Dataset(filename,'a') d.__setattr__(attribute,self.log) d.close() self.com.barrier() import termios, sys, os TERMIOS = termios def getkey(): """Helper function for grabbing a single key press""" fd = sys.stdin.fileno() c = None if os.isatty(fd): old = termios.tcgetattr(fd) new = termios.tcgetattr(fd) new[3] = new[3] & ~TERMIOS.ICANON & ~TERMIOS.ECHO new[6][TERMIOS.VMIN] = 1 new[6][TERMIOS.VTIME] = 0 termios.tcsetattr(fd, TERMIOS.TCSANOW, new) try: c = os.read(fd, 1) finally: termios.tcsetattr(fd, TERMIOS.TCSAFLUSH, old) else: # FIXME: The following is here for multi-processor runs. # Termios is not available and I don't know a better solution. c = sys.stdin.read(1) return c def pause(message_in=None,message_out=None): """Prints a message and waits for a key press. :param message_in: Message to display before waiting. :param message_out: Message to display after waiting.""" com = PISM.Context().com if not message_in is None: PISM.verbPrintf(1,com,message_in+"\n") c = getkey() if not message_out is None: PISM.verbPrintf(1,com,message_out+"\n")