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 if self.debugLevel <= 3: return 134 import io 135 self.logBkp = self.log 136 if sys.version_info < (3,): 137 self.log = io.BytesIO() 138 else: 139 self.log = io.StringIO() 140 141 def restoreLog(self): 142 if self.debugLevel <= 3: return 143 s = self.log.getvalue() 144 self.log.close() 145 self.log = self.logBkp 146 del(self.logBkp) 147 return s 148 149 def getLinewidth(self): 150 global LineWidth 151 if not hasattr(self, '_linewidth'): 152 if self.out is None or not self.out.isatty() or self.argDB['scrollOutput']: 153 self._linewidth = -1 154 else: 155 if LineWidth == -1: 156 try: 157 import curses 158 159 try: 160 curses.setupterm() 161 (y, self._linewidth) = curses.initscr().getmaxyx() 162 curses.endwin() 163 except curses.error: 164 self._linewidth = -1 165 except: 166 self._linewidth = -1 167 LineWidth = self._linewidth 168 else: 169 self._linewidth = LineWidth 170 return self._linewidth 171 def setLinewidth(self, linewidth): 172 self._linewidth = linewidth 173 return 174 linewidth = property(getLinewidth, setLinewidth, doc = 'The maximum number of characters per log line') 175 176 def checkWrite(self, f, debugLevel, debugSection, writeAll = 0): 177 '''Check whether the log line should be written 178 - If writeAll is true, return true 179 - If debugLevel >= current level, and debugSection in current section or sections is empty, return true''' 180 if not isinstance(debugLevel, int): 181 raise RuntimeError('Debug level must be an integer: '+str(debugLevel)) 182 if f is None: 183 return False 184 if writeAll: 185 return True 186 if self.debugLevel >= debugLevel and (not len(self.debugSections) or debugSection in self.debugSections): 187 return True 188 return False 189 190 def logIndent(self, debugLevel = -1, debugSection = None, comm = None): 191 '''Write the proper indentation to the log streams''' 192 import traceback 193 194 indentLevel = len(traceback.extract_stack())-5 195 for writeAll, f in enumerate([self.out, self.log]): 196 if self.checkWrite(f, debugLevel, debugSection, writeAll): 197 if not comm is None: 198 f.write('[') 199 f.write(str(comm.rank())) 200 f.write(']') 201 for i in range(indentLevel): 202 f.write(self.debugIndent) 203 return 204 205 def logBack(self): 206 '''Backup the current line if we are not scrolling output''' 207 if not self.out is None and self.linewidth > 0: 208 self.out.write('\r') 209 return 210 211 def logClear(self): 212 '''Clear the current line if we are not scrolling output''' 213 if not self.out is None and self.linewidth > 0: 214 self.out.write('\r') 215 self.out.write(''.join([' '] * self.linewidth)) 216 self.out.write('\r') 217 return 218 219 def logPrintDivider(self, debugLevel = -1, debugSection = None, single = 0): 220 if single: 221 self.logPrint('---------------------------------------------------------------------------------------------', debugLevel = debugLevel, debugSection = debugSection) 222 else: 223 self.logPrint('=============================================================================================', debugLevel = debugLevel, debugSection = debugSection) 224 return 225 226 def logPrintBox(self,msg, debugLevel = -1, debugSection = 'screen', indent = 1, comm = None, rmDir = 1): 227 self.logClear() 228 self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection) 229 [self.logPrint(' '+line, debugLevel = debugLevel, debugSection = debugSection, rmDir = rmDir) for line in msg.split('\n')] 230 self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection) 231 self.logPrint('', debugLevel = debugLevel, debugSection = debugSection) 232 return 233 234 def logClearRemoveDirectory(self): 235 global RemoveDirectory 236 global backupRemoveDirectory 237 backupRemoveDirectory = RemoveDirectory 238 RemoveDirectory = '' 239 240 def logResetRemoveDirectory(self): 241 global RemoveDirectory 242 global backupRemoveDirectory 243 RemoveDirectory = backupRemoveDirectory 244 245 246 def logWrite(self, msg, debugLevel = -1, debugSection = None, forceScroll = 0, rmDir = 1): 247 '''Write the message to the log streams''' 248 '''Generally goes to the file but not the screen''' 249 if not msg: return 250 for writeAll, f in enumerate([self.out, self.log]): 251 if self.checkWrite(f, debugLevel, debugSection, writeAll): 252 if not forceScroll and not writeAll and self.linewidth > 0: 253 global RemoveDirectory 254 self.logBack() 255 if rmDir: msg = msg.replace(RemoveDirectory,'') 256 for ms in msg.split('\n'): 257 f.write(ms[0:self.linewidth]) 258 f.write(''.join([' '] * (self.linewidth - len(ms)))) 259 else: 260 if not debugSection is None and not debugSection == 'screen' and len(msg): 261 f.write(str(debugSection)) 262 f.write(': ') 263 f.write(msg) 264 if hasattr(f, 'flush'): 265 f.flush() 266 return 267 268 def logPrint(self, msg, debugLevel = -1, debugSection = None, indent = 1, comm = None, forceScroll = 0, rmDir = 1): 269 '''Write the message to the log streams with proper indentation and a newline''' 270 '''Generally goes to the file and the screen''' 271 if indent: 272 self.logIndent(debugLevel, debugSection, comm) 273 self.logWrite(msg, debugLevel, debugSection, forceScroll = forceScroll, rmDir = rmDir) 274 for writeAll, f in enumerate([self.out, self.log]): 275 if self.checkWrite(f, debugLevel, debugSection, writeAll): 276 if writeAll or self.linewidth < 0: 277 f.write('\n') 278 return 279 280 281 def getRoot(self): 282 '''Return the directory containing this module 283 - This has the problem that when we reload a module of the same name, this gets screwed up 284 Therefore, we call it in the initializer, and stash it''' 285 #print ' In getRoot' 286 #print hasattr(self, '__root') 287 #print ' done checking' 288 if not hasattr(self, '__root'): 289 import os 290 import sys 291 292 # Work around a bug with pdb in 2.3 293 if hasattr(sys.modules[self.__module__], '__file__') and not os.path.basename(sys.modules[self.__module__].__file__) == 'pdb.py': 294 self.__root = os.path.abspath(os.path.dirname(sys.modules[self.__module__].__file__)) 295 else: 296 self.__root = os.getcwd() 297 #print ' Exiting getRoot' 298 return self.__root 299 def setRoot(self, root): 300 self.__root = root 301 return 302 root = property(getRoot, setRoot, doc = 'The directory containing this module') 303