1from __future__ import absolute_import 2import args 3import sys 4import os 5 6# Ugly stuff to have curses called ONLY once, instead of for each 7# new Configure object created (and flashing the screen) 8global LineWidth 9global RemoveDirectory 10global backupRemoveDirectory 11LineWidth = -1 12RemoveDirectory = os.path.join(os.getcwd(),'') 13backupRemoveDirectory = '' 14 15class Logger(args.ArgumentProcessor): 16 '''This class creates a shared log and provides methods for writing to it''' 17 defaultLog = None 18 defaultOut = sys.stdout 19 20 def __init__(self, clArgs = None, argDB = None, log = None, out = defaultOut, debugLevel = None, debugSections = None, debugIndent = None): 21 args.ArgumentProcessor.__init__(self, clArgs, argDB) 22 self.logName = None 23 self.log = log 24 self.out = out 25 self.debugLevel = debugLevel 26 self.debugSections = debugSections 27 self.debugIndent = debugIndent 28 self.getRoot() 29 return 30 31 def __getstate__(self): 32 '''We do not want to pickle the default log stream''' 33 d = args.ArgumentProcessor.__getstate__(self) 34 if 'logBkp' in d: 35 del d['logBkp'] 36 if 'log' in d: 37 if d['log'] is Logger.defaultLog: 38 del d['log'] 39 else: 40 d['log'] = None 41 if 'out' in d: 42 if d['out'] is Logger.defaultOut: 43 del d['out'] 44 else: 45 d['out'] = None 46 return d 47 48 def __setstate__(self, d): 49 '''We must create the default log stream''' 50 args.ArgumentProcessor.__setstate__(self, d) 51 if not 'log' in d: 52 self.log = self.createLog(None) 53 if not 'out' in d: 54 self.out = Logger.defaultOut 55 self.__dict__.update(d) 56 return 57 58 def setupArguments(self, argDB): 59 '''Setup types in the argument database''' 60 import nargs 61 62 argDB = args.ArgumentProcessor.setupArguments(self, argDB) 63 argDB.setType('log', nargs.Arg(None, 'buildsystem.log', 'The filename for the log')) 64 argDB.setType('logAppend', nargs.ArgBool(None, 0, 'The flag determining whether we backup or append to the current log', isTemporary = 1)) 65 argDB.setType('debugLevel', nargs.ArgInt(None, 3, 'Integer 0 to 4, where a higher level means more detail', 0, 5)) 66 argDB.setType('debugSections', nargs.Arg(None, [], 'Message types to print, e.g. [compile,link,hg,install]')) 67 argDB.setType('debugIndent', nargs.Arg(None, ' ', 'The string used for log indentation')) 68 argDB.setType('scrollOutput', nargs.ArgBool(None, 0, 'Flag to allow output to scroll rather than overwriting a single line')) 69 argDB.setType('noOutput', nargs.ArgBool(None, 0, 'Flag to suppress output to the terminal')) 70 return argDB 71 72 def setup(self): 73 '''Setup the terminal output and filtering flags''' 74 self.log = self.createLog(self.logName, self.log) 75 args.ArgumentProcessor.setup(self) 76 77 if self.argDB['noOutput']: 78 self.out = None 79 if self.debugLevel is None: 80 self.debugLevel = self.argDB['debugLevel'] 81 if self.debugSections is None: 82 self.debugSections = self.argDB['debugSections'] 83 if self.debugIndent is None: 84 self.debugIndent = self.argDB['debugIndent'] 85 return 86 87 def checkLog(self, logName): 88 import nargs 89 import os 90 91 if logName is None: 92 logName = nargs.Arg.findArgument('log', self.clArgs) 93 if logName is None: 94 if not self.argDB is None and 'log' in self.argDB: 95 logName = self.argDB['log'] 96 else: 97 logName = 'default.log' 98 self.logName = logName 99 self.logExists = os.path.exists(self.logName) 100 return self.logExists 101 102 def createLog(self, logName, initLog = None): 103 '''Create a default log stream, unless initLog is given''' 104 import nargs 105 106 if not initLog is None: 107 log = initLog 108 else: 109 if Logger.defaultLog is None: 110 appendArg = nargs.Arg.findArgument('logAppend', self.clArgs) 111 if self.checkLog(logName): 112 if not self.argDB is None and ('logAppend' in self.argDB and self.argDB['logAppend']) or (not appendArg is None and bool(appendArg)): 113 Logger.defaultLog = open(self.logName, 'a') 114 else: 115 try: 116 import os 117 118 os.rename(self.logName, self.logName+'.bkp') 119 Logger.defaultLog = open(self.logName, 'w') 120 except OSError: 121 sys.stdout.write('WARNING: Cannot backup log file, appending instead.\n') 122 Logger.defaultLog = open(self.logName, 'a') 123 else: 124 Logger.defaultLog = open(self.logName, 'w') 125 log = Logger.defaultLog 126 return log 127 128 def closeLog(self): 129 '''Closes the log file''' 130 self.log.close() 131 132 def saveLog(self): 133 import io 134 self.logBkp = self.log 135 if sys.version_info < (3,): 136 self.log = io.BytesIO() 137 else: 138 self.log = io.StringIO() 139 140 def restoreLog(self): 141 s = self.log.getvalue() 142 self.log.close() 143 self.log = self.logBkp 144 del(self.logBkp) 145 return s 146 147 def getLinewidth(self): 148 global LineWidth 149 if not hasattr(self, '_linewidth'): 150 if self.out is None or not self.out.isatty() or self.argDB['scrollOutput']: 151 self._linewidth = -1 152 else: 153 if LineWidth == -1: 154 try: 155 import curses 156 157 try: 158 curses.setupterm() 159 (y, self._linewidth) = curses.initscr().getmaxyx() 160 curses.endwin() 161 except curses.error: 162 self._linewidth = -1 163 except: 164 self._linewidth = -1 165 LineWidth = self._linewidth 166 else: 167 self._linewidth = LineWidth 168 return self._linewidth 169 def setLinewidth(self, linewidth): 170 self._linewidth = linewidth 171 return 172 linewidth = property(getLinewidth, setLinewidth, doc = 'The maximum number of characters per log line') 173 174 def checkWrite(self, f, debugLevel, debugSection, writeAll = 0): 175 '''Check whether the log line should be written 176 - If writeAll is true, return true 177 - If debugLevel >= current level, and debugSection in current section or sections is empty, return true''' 178 if not isinstance(debugLevel, int): 179 raise RuntimeError('Debug level must be an integer: '+str(debugLevel)) 180 if f is None: 181 return False 182 if writeAll: 183 return True 184 if self.debugLevel >= debugLevel and (not len(self.debugSections) or debugSection in self.debugSections): 185 return True 186 return False 187 188 def logIndent(self, debugLevel = -1, debugSection = None, comm = None): 189 '''Write the proper indentation to the log streams''' 190 import traceback 191 192 indentLevel = len(traceback.extract_stack())-5 193 for writeAll, f in enumerate([self.out, self.log]): 194 if self.checkWrite(f, debugLevel, debugSection, writeAll): 195 if not comm is None: 196 f.write('[') 197 f.write(str(comm.rank())) 198 f.write(']') 199 for i in range(indentLevel): 200 f.write(self.debugIndent) 201 return 202 203 def logBack(self): 204 '''Backup the current line if we are not scrolling output''' 205 if not self.out is None and self.linewidth > 0: 206 self.out.write('\r') 207 return 208 209 def logClear(self): 210 '''Clear the current line if we are not scrolling output''' 211 if not self.out is None and self.linewidth > 0: 212 self.out.write('\r') 213 self.out.write(''.join([' '] * self.linewidth)) 214 self.out.write('\r') 215 return 216 217 def logPrintDivider(self, debugLevel = -1, debugSection = None, single = 0): 218 if single: 219 self.logPrint('-------------------------------------------------------------------------------', debugLevel = debugLevel, debugSection = debugSection) 220 else: 221 self.logPrint('===============================================================================', debugLevel = debugLevel, debugSection = debugSection) 222 return 223 224 def logPrintBox(self,msg, debugLevel = -1, debugSection = 'screen', indent = 1, comm = None): 225 self.logClear() 226 self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection) 227 [self.logPrint(' '+line, debugLevel = debugLevel, debugSection = debugSection) for line in msg.split('\n')] 228 self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection) 229 self.logPrint('', debugLevel = debugLevel, debugSection = debugSection) 230 return 231 232 def logClearRemoveDirectory(self): 233 global RemoveDirectory 234 global backupRemoveDirectory 235 backupRemoveDirectory = RemoveDirectory 236 RemoveDirectory = '' 237 238 def logResetRemoveDirectory(self): 239 global RemoveDirectory 240 global backupRemoveDirectory 241 RemoveDirectory = backupRemoveDirectory 242 243 244 def logWrite(self, msg, debugLevel = -1, debugSection = None, forceScroll = 0): 245 '''Write the message to the log streams''' 246 for writeAll, f in enumerate([self.out, self.log]): 247 if self.checkWrite(f, debugLevel, debugSection, writeAll): 248 if not forceScroll and not writeAll and self.linewidth > 0: 249 global RemoveDirectory 250 self.logBack() 251 msg = msg.replace(RemoveDirectory,'') 252 for ms in msg.split('\n'): 253 f.write(ms[0:self.linewidth]) 254 f.write(''.join([' '] * (self.linewidth - len(ms)))) 255 else: 256 if not debugSection is None and not debugSection == 'screen' and len(msg): 257 f.write(str(debugSection)) 258 f.write(': ') 259 f.write(msg) 260 if hasattr(f, 'flush'): 261 f.flush() 262 return 263 264 def logPrint(self, msg, debugLevel = -1, debugSection = None, indent = 1, comm = None, forceScroll = 0): 265 '''Write the message to the log streams with proper indentation and a newline''' 266 if indent: 267 self.logIndent(debugLevel, debugSection, comm) 268 self.logWrite(msg, debugLevel, debugSection, forceScroll = forceScroll) 269 for writeAll, f in enumerate([self.out, self.log]): 270 if self.checkWrite(f, debugLevel, debugSection, writeAll): 271 if writeAll or self.linewidth < 0: 272 f.write('\n') 273 return 274 275 276 def getRoot(self): 277 '''Return the directory containing this module 278 - This has the problem that when we reload a module of the same name, this gets screwed up 279 Therefore, we call it in the initializer, and stash it''' 280 #print ' In getRoot' 281 #print hasattr(self, '__root') 282 #print ' done checking' 283 if not hasattr(self, '__root'): 284 import os 285 import sys 286 287 # Work around a bug with pdb in 2.3 288 if hasattr(sys.modules[self.__module__], '__file__') and not os.path.basename(sys.modules[self.__module__].__file__) == 'pdb.py': 289 self.__root = os.path.abspath(os.path.dirname(sys.modules[self.__module__].__file__)) 290 else: 291 self.__root = os.getcwd() 292 #print ' Exiting getRoot' 293 return self.__root 294 def setRoot(self, root): 295 self.__root = root 296 return 297 root = property(getRoot, setRoot, doc = 'The directory containing this module') 298