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