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