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