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