logging.py (6701B)
1 # Copyright (C) 2012, 2015, 2016, 2018, 2019 David Maxwell 2 # 3 # This file is part of PISM. 4 # 5 # PISM is free software; you can redistribute it and/or modify it under the 6 # terms of the GNU General Public License as published by the Free Software 7 # Foundation; either version 3 of the License, or (at your option) any later 8 # version. 9 # 10 # PISM is distributed in the hope that it will be useful, but WITHOUT ANY 11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 13 # details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with PISM; if not, write to the Free Software 17 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 19 """Implements a rudimentary logging system. Messages are sent in client code via :func:`logError`, :func:`logMessage`, 20 etc. These messages are then forwarded to any loggers that have been previously registered with :func:`add_logger`. 21 22 A logger is either a function with signature:: 23 24 def myLogger(message, verbosity) 25 26 or a class that implements:: 27 28 def __call__(self, message, verbosity) 29 30 The string message is passed as ``message`` and verbosity is a standard PISM verbosity (an integer between 1-5). 31 The following aliases are available 32 33 * ``PISM.logging.kError`` 34 * ``PISM.logging.kWarning`` 35 * ``PISM.logging.kMessage`` 36 * ``PISM.logging.kDebug`` 37 * ``PISM.logging.kPrattle`` 38 39 which are listed in increasing verbosity. Note that ``kError`` need not signify an error message, only a message with 40 verbosity 1 that is ensured to be printed. Conversely, ``kPrattle`` signifies a verbosity level 5 with messages that 41 rarely need to be displayed. 42 43 The default logger, :func:`print_logger`, simply passes the message along as a call to :cpp:func:`verbPrintf`. 44 See also the :class:`CaptureLogger`, which saves logged messages into an attribute of an :file:`.nc` file. 45 46 The logging system does not log calls to verbPrintf directly. In particular, calls to verbPrintf from within PISM's C++ 47 code do not pass through the python-based logging system. 48 """ 49 50 import PISM 51 import time 52 53 kError = 1 54 kWarning = 2 55 kMessage = 2 56 kDebug = 4 57 kPrattle = 5 58 59 _loggers = [] 60 61 def clear_loggers(): 62 """Removes all members from the global list of loggers.""" 63 global _loggers 64 _loggers = [] 65 66 67 def add_logger(logger): 68 """Appends a new logger to the global list of loggers.""" 69 global _loggers 70 _loggers.append(logger) 71 72 73 def log(message, verbosity): 74 """Logs a message with the specified verbosity""" 75 for l in _loggers: 76 l(message, verbosity) 77 78 79 def logError(message): 80 """Convenience function for logging a message at the level of ``kError``""" 81 log(message, kError) 82 83 84 def logWarning(message): 85 """Convenience function for logging a message at the level of ``kWarning``""" 86 log(message, kWarning) 87 88 89 def logMessage(message): 90 """Convenience function for logging a message at the level of ``kMessage``""" 91 log(message, kMessage) 92 93 94 def logDebug(message): 95 """Convenience function for logging a message at the level of ``kDebug``""" 96 log(message, kDebug) 97 98 99 def logPrattle(message): 100 """Convenience function for logging a message at the level of ``kPrattle``""" 101 log(message, kPrattle) 102 103 104 def print_logger(message, verbosity): 105 """Implements a logger that forwards messages to :cpp:func:`verbPrintf`.""" 106 com = PISM.Context().com 107 msg = str(message) 108 PISM.verbPrintf(verbosity, com, msg) 109 110 # The global list of loggers. 111 _loggers = [print_logger] 112 113 114 class CaptureLogger(object): 115 116 """Implements a logger that appends log messages as they occur 117 to an attribute of an :file:`.nc` file.""" 118 119 def __init__(self, filename, attribute='pism_log', verbosity_threshold=2): 120 """:param filename: Name of :file:`.nc` file to save the log to. 121 :param attribute: Attribute name to save the log as.""" 122 self.com = PISM.Context().com 123 self.rank = PISM.Context().rank 124 self.log = "" 125 self.filename = filename 126 self.attr = attribute 127 self.verbosity_threshold = verbosity_threshold 128 129 def __call__(self, message, verbosity): 130 """Saves the message to our internal log string and writes the string out to the file.""" 131 if verbosity <= self.verbosity_threshold: 132 timestamp = time.strftime('%Y-%m-%d %H:%M:%S') 133 self.log = "%s%s: %s" % (self.log, timestamp, message) 134 d = PISM.File(PISM.Context().com, self.filename, PISM.PISM_NETCDF3, PISM.PISM_READWRITE) 135 d.redef() 136 d.write_attribute("PISM_GLOBAL", self.attr, self.log) 137 d.close() 138 139 def readOldLog(self): 140 """If the :file:`.nc` file we are logging to already has a log, 141 read it in to the log we are about to make so that we append to it rather 142 than overwriting it.""" 143 d = PISM.File(PISM.Context().com, self.filename, PISM.PISM_NETCDF3, PISM.PISM_READONLY) 144 self.log += d.read_text_attribute("PISM_GLOBAL", self.attr) 145 d.close() 146 147 def write(self, filename=None, attribute=None): 148 """Save a copy of our log to the specified file and attribute.""" 149 if filename is None: 150 filename = self.filename 151 if attribute is None: 152 attribute = self.attr 153 d = PISM.File(PISM.Context().com, filename, PISM.PISM_NETCDF3, PISM.PISM_READWRITE) 154 d.redef() 155 d.write_attribute("PISM_GLOBAL", attribute, self.log) 156 d.close() 157 158 import termios 159 import sys 160 import os 161 TERMIOS = termios 162 163 164 def getkey(): 165 """Helper function for grabbing a single key press""" 166 fd = sys.stdin.fileno() 167 c = None 168 if os.isatty(fd): 169 old = termios.tcgetattr(fd) 170 new = termios.tcgetattr(fd) 171 new[3] = new[3] & ~TERMIOS.ICANON & ~TERMIOS.ECHO 172 new[6][TERMIOS.VMIN] = 1 173 new[6][TERMIOS.VTIME] = 0 174 termios.tcsetattr(fd, TERMIOS.TCSANOW, new) 175 try: 176 c = os.read(fd, 1) 177 finally: 178 termios.tcsetattr(fd, TERMIOS.TCSAFLUSH, old) 179 else: 180 # FIXME: The following is here for multi-processor runs. 181 # Termios is not available and I don't know a better solution. 182 c = sys.stdin.read(1) 183 return c 184 185 186 def pause(message_in=None, message_out=None): 187 """Prints a message and waits for a key press. 188 189 :param message_in: Message to display before waiting. 190 :param message_out: Message to display after waiting.""" 191 com = PISM.Context().com 192 if not message_in is None: 193 PISM.verbPrintf(1, com, message_in + "\n") 194 _ = getkey() 195 if not message_out is None: 196 PISM.verbPrintf(1, com, message_out + "\n")