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 self.log = io.StringIO() 139 140 def restoreLog(self): 141 if self.debugLevel <= 3: return 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 self.logPrint(('-' if single else '=')*self.dividerLength, debugLevel = debugLevel, debugSection = debugSection) 220 return 221 222 def logPrintBox(self,msg, debugLevel = -1, debugSection = 'screen', indent = 1, comm = None, rmDir = 1, prefix = None): 223 def center_wrap(banner,text,**kwargs): 224 def center_line(line): 225 return line.center(self.dividerLength).rstrip() 226 227 wrapped = textwrap.wrap(textwrap.dedent(text),**kwargs) 228 if len(wrapped) == 1: 229 # center-justify single lines 230 wrapped[0] = center_line(wrapped[0]) 231 if banner: 232 # add the banner 233 wrapped.insert(0,center_line(banner)) 234 return '\n'.join(wrapped) 235 236 def prepend_banner(msg,*args): 237 lo_msg = msg.lower() 238 banner = None 239 240 for title in args: 241 lo_title = title.lower()+':' 242 if lo_msg.startswith(lo_title): 243 banner = '***** {} *****'.format(title.upper()) 244 msg = msg.replace(lo_title,'').replace(lo_title.title(),'').lstrip() 245 break 246 return banner,msg 247 248 249 msg = msg.strip() 250 if prefix is None: 251 prefix = ' '*2 252 msg = 'warning: '+msg 253 # check if the message already has prefixes, in which case we should insert that 254 # prefix as a banner (and remove any existing copies of it) 255 banner,msg = prepend_banner(msg,'warning','error') 256 else: 257 banner = None 258 259 msg = center_wrap(banner,msg,width=self.dividerLength-2,initial_indent=prefix,subsequent_indent=prefix) 260 self.logClear() 261 self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection) 262 self.logPrint(msg, debugLevel = debugLevel, debugSection = debugSection, rmDir = rmDir) 263 self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection) 264 self.logPrint('', debugLevel = debugLevel, debugSection = debugSection) 265 return 266 267 def logClearRemoveDirectory(self): 268 global RemoveDirectory 269 global backupRemoveDirectory 270 backupRemoveDirectory = RemoveDirectory 271 RemoveDirectory = '' 272 273 def logResetRemoveDirectory(self): 274 global RemoveDirectory 275 global backupRemoveDirectory 276 RemoveDirectory = backupRemoveDirectory 277 278 279 def logWrite(self, msg, debugLevel = -1, debugSection = None, forceScroll = 0, rmDir = 1): 280 '''Write the message to the log streams''' 281 '''Generally goes to the file but not the screen''' 282 if not msg: return 283 for writeAll, f in enumerate([self.out, self.log]): 284 if self.checkWrite(f, debugLevel, debugSection, writeAll): 285 if not forceScroll and not writeAll and self.linewidth > 0: 286 global RemoveDirectory 287 self.logBack() 288 if rmDir: msg = msg.replace(RemoveDirectory,'') 289 for ms in msg.split('\n'): 290 f.write(ms[0:self.linewidth]) 291 f.write(''.join([' '] * (self.linewidth - len(ms)))) 292 else: 293 if not debugSection is None and not debugSection == 'screen' and len(msg): 294 f.write(str(debugSection)) 295 f.write(': ') 296 f.write(msg) 297 if hasattr(f, 'flush'): 298 f.flush() 299 return 300 301 def logPrint(self, msg, debugLevel = -1, debugSection = None, indent = 1, comm = None, forceScroll = 0, rmDir = 1): 302 '''Write the message to the log streams with proper indentation and a newline''' 303 '''Generally goes to the file and the screen''' 304 if indent: 305 self.logIndent(debugLevel, debugSection, comm) 306 self.logWrite(msg, debugLevel, debugSection, forceScroll = forceScroll, rmDir = rmDir) 307 for writeAll, f in enumerate([self.out, self.log]): 308 if self.checkWrite(f, debugLevel, debugSection, writeAll): 309 if writeAll or self.linewidth < 0: 310 f.write('\n') 311 return 312 313 314 def getRoot(self): 315 '''Return the directory containing this module 316 - This has the problem that when we reload a module of the same name, this gets screwed up 317 Therefore, we call it in the initializer, and stash it''' 318 #print ' In getRoot' 319 #print hasattr(self, '__root') 320 #print ' done checking' 321 if not hasattr(self, '__root'): 322 import os 323 import sys 324 325 # Work around a bug with pdb in 2.3 326 if hasattr(sys.modules[self.__module__], '__file__') and not os.path.basename(sys.modules[self.__module__].__file__) == 'pdb.py': 327 self.__root = os.path.abspath(os.path.dirname(sys.modules[self.__module__].__file__)) 328 else: 329 self.__root = os.getcwd() 330 #print ' Exiting getRoot' 331 return self.__root 332 def setRoot(self, root): 333 self.__root = root 334 return 335 root = property(getRoot, setRoot, doc = 'The directory containing this module') 336