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