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 self.out is not 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 self.out is not None and self.linewidth > 0: 213 self.out.write((' '*self.linewidth).join(('\r','\r'))) 214 return 215 216 def logPrintDivider(self, single = False, **kwargs): 217 divider = ('-' if single else '=')*self.dividerLength 218 return self.logPrint(divider, **kwargs) 219 220 def logPrintWarning(self, msg, title = None, **kwargs): 221 if title is None: 222 title = 'WARNING' 223 return self.logPrintBox(msg,title='***** {} *****'.format(title),**kwargs) 224 225 def logPrintBox(self, msg, debugLevel = -1, debugSection = 'screen', indent = 1, comm = None, rmDir = 1, prefix = None, title = 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, and remove the bogus prefix 233 wrapped[0] = center_line(wrapped[0].lstrip()) 234 if banner: 235 # add the banner 236 wrapped.insert(0,center_line(banner)) 237 return '\n'.join(wrapped) 238 239 240 if prefix is None: 241 prefix = ' '*2 242 243 if rmDir: 244 rmDir = center_wrap(title,self.logStripDirectory(msg),width=self.dividerLength-2,initial_indent=prefix,subsequent_indent=prefix) 245 msg = center_wrap(title,msg,width=self.dividerLength-2,initial_indent=prefix,subsequent_indent=prefix) 246 self.logClear() 247 self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection, forceNewLine = True) 248 self.logPrint(msg, debugLevel = debugLevel, debugSection = debugSection, rmDir = rmDir, indent = indent, comm = comm, forceNewLine = True) 249 self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection, forceNewLine = True) 250 self.logPrint('', debugLevel = debugLevel, debugSection = debugSection) 251 return 252 253 def logStripDirectory(self,msg): 254 return msg.replace(RemoveDirectory,'') 255 256 def logClearRemoveDirectory(self): 257 global RemoveDirectory 258 global backupRemoveDirectory 259 backupRemoveDirectory = RemoveDirectory 260 RemoveDirectory = '' 261 262 def logResetRemoveDirectory(self): 263 global RemoveDirectory 264 global backupRemoveDirectory 265 RemoveDirectory = backupRemoveDirectory 266 267 268 def logWrite(self, msg, debugLevel = -1, debugSection = None, forceScroll = 0, rmDir = 1): 269 '''Write the message to the log streams''' 270 '''Generally goes to the file but not the screen''' 271 if not msg: return 272 for writeAll, f in enumerate([self.out, self.log]): 273 if self.checkWrite(f, debugLevel, debugSection, writeAll): 274 if not forceScroll and not writeAll and self.linewidth > 0: 275 self.logClear() 276 if rmDir: 277 if isinstance(rmDir,str): 278 msg = rmDir 279 else: 280 msg = self.logStripDirectory(msg) 281 for ms in msg.splitlines(): 282 f.write(ms[:self.linewidth]) 283 else: 284 if not debugSection is None and not debugSection == 'screen' and len(msg): 285 f.write(str(debugSection)) 286 f.write(': ') 287 f.write(msg) 288 if hasattr(f, 'flush'): 289 f.flush() 290 return 291 292 def logPrint(self, msg, debugLevel = -1, debugSection = None, indent = 1, comm = None, forceScroll = 0, rmDir = 1, forceNewLine = False): 293 '''Write the message to the log streams with proper indentation and a newline''' 294 '''Generally goes to the file and the screen''' 295 if indent: 296 self.logIndent(debugLevel, debugSection, comm) 297 self.logWrite(msg, debugLevel, debugSection, forceScroll = forceScroll, rmDir = rmDir) 298 for writeAll, f in enumerate([self.out, self.log]): 299 if self.checkWrite(f, debugLevel, debugSection, writeAll): 300 if forceNewLine or writeAll or self.linewidth < 0: 301 f.write('\n') 302 return 303 304 305 def getRoot(self): 306 '''Return the directory containing this module 307 - This has the problem that when we reload a module of the same name, this gets screwed up 308 Therefore, we call it in the initializer, and stash it''' 309 #print ' In getRoot' 310 #print hasattr(self, '__root') 311 #print ' done checking' 312 if not hasattr(self, '__root'): 313 import os 314 import sys 315 316 # Work around a bug with pdb in 2.3 317 if hasattr(sys.modules[self.__module__], '__file__') and not os.path.basename(sys.modules[self.__module__].__file__) == 'pdb.py': 318 self.__root = os.path.abspath(os.path.dirname(sys.modules[self.__module__].__file__)) 319 else: 320 self.__root = os.getcwd() 321 #print ' Exiting getRoot' 322 return self.__root 323 def setRoot(self, root): 324 self.__root = root 325 return 326 root = property(getRoot, setRoot, doc = 'The directory containing this module') 327