xref: /petsc/config/BuildSystem/logger.py (revision 1edc1b4b9fa462f3a4b92a212884d2763c6b6846)
15b6bfdb9SJed Brownfrom __future__ import absolute_import
2179860b2SJed Brownimport args
3179860b2SJed Brownimport sys
4179860b2SJed Brownimport os
59561296dSJacob Faibussowitschimport textwrap
6179860b2SJed Brown
7179860b2SJed Brown# Ugly stuff to have curses called ONLY once, instead of for each
8179860b2SJed Brown# new Configure object created (and flashing the screen)
9179860b2SJed Brownglobal LineWidth
10179860b2SJed Brownglobal RemoveDirectory
11179860b2SJed Brownglobal backupRemoveDirectory
12179860b2SJed BrownLineWidth = -1
13179860b2SJed BrownRemoveDirectory = os.path.join(os.getcwd(),'')
14179860b2SJed BrownbackupRemoveDirectory = ''
15179860b2SJed Brown
16179860b2SJed Brownclass Logger(args.ArgumentProcessor):
17179860b2SJed Brown  '''This class creates a shared log and provides methods for writing to it'''
18179860b2SJed Brown  defaultLog = None
19179860b2SJed Brown  defaultOut = sys.stdout
20179860b2SJed Brown
21179860b2SJed Brown  def __init__(self, clArgs = None, argDB = None, log = None, out = defaultOut, debugLevel = None, debugSections = None, debugIndent = None):
22179860b2SJed Brown    args.ArgumentProcessor.__init__(self, clArgs, argDB)
23179860b2SJed Brown    self.logName       = None
24179860b2SJed Brown    self.log           = log
25179860b2SJed Brown    self.out           = out
26179860b2SJed Brown    self.debugLevel    = debugLevel
27179860b2SJed Brown    self.debugSections = debugSections
28179860b2SJed Brown    self.debugIndent   = debugIndent
299561296dSJacob Faibussowitsch    self.dividerLength = 93
30179860b2SJed Brown    self.getRoot()
31179860b2SJed Brown    return
32179860b2SJed Brown
33179860b2SJed Brown  def __getstate__(self):
34179860b2SJed Brown    '''We do not want to pickle the default log stream'''
35179860b2SJed Brown    d = args.ArgumentProcessor.__getstate__(self)
36b59d1e59SMatthew G. Knepley    if 'logBkp' in d:
37b59d1e59SMatthew G. Knepley        del d['logBkp']
38179860b2SJed Brown    if 'log' in d:
39179860b2SJed Brown      if d['log'] is Logger.defaultLog:
40179860b2SJed Brown        del d['log']
41179860b2SJed Brown      else:
42179860b2SJed Brown        d['log'] = None
43179860b2SJed Brown    if 'out' in d:
44179860b2SJed Brown      if d['out'] is Logger.defaultOut:
45179860b2SJed Brown        del d['out']
46179860b2SJed Brown      else:
47179860b2SJed Brown        d['out'] = None
48179860b2SJed Brown    return d
49179860b2SJed Brown
50179860b2SJed Brown  def __setstate__(self, d):
51179860b2SJed Brown    '''We must create the default log stream'''
52179860b2SJed Brown    args.ArgumentProcessor.__setstate__(self, d)
53179860b2SJed Brown    if not 'log' in d:
54179860b2SJed Brown      self.log = self.createLog(None)
55179860b2SJed Brown    if not 'out' in d:
56179860b2SJed Brown      self.out = Logger.defaultOut
57179860b2SJed Brown    self.__dict__.update(d)
58179860b2SJed Brown    return
59179860b2SJed Brown
60179860b2SJed Brown  def setupArguments(self, argDB):
61179860b2SJed Brown    '''Setup types in the argument database'''
62179860b2SJed Brown    import nargs
63179860b2SJed Brown
64179860b2SJed Brown    argDB = args.ArgumentProcessor.setupArguments(self, argDB)
6503e6d329SSatish Balay    argDB.setType('log',           nargs.Arg(None, 'buildsystem.log', 'The filename for the log'))
66179860b2SJed Brown    argDB.setType('logAppend',     nargs.ArgBool(None, 0, 'The flag determining whether we backup or append to the current log', isTemporary = 1))
67179860b2SJed Brown    argDB.setType('debugLevel',    nargs.ArgInt(None, 3, 'Integer 0 to 4, where a higher level means more detail', 0, 5))
68179860b2SJed Brown    argDB.setType('debugSections', nargs.Arg(None, [], 'Message types to print, e.g. [compile,link,hg,install]'))
69179860b2SJed Brown    argDB.setType('debugIndent',   nargs.Arg(None, '  ', 'The string used for log indentation'))
70179860b2SJed Brown    argDB.setType('scrollOutput',  nargs.ArgBool(None, 0, 'Flag to allow output to scroll rather than overwriting a single line'))
71179860b2SJed Brown    argDB.setType('noOutput',      nargs.ArgBool(None, 0, 'Flag to suppress output to the terminal'))
72179860b2SJed Brown    return argDB
73179860b2SJed Brown
74179860b2SJed Brown  def setup(self):
75179860b2SJed Brown    '''Setup the terminal output and filtering flags'''
76179860b2SJed Brown    self.log = self.createLog(self.logName, self.log)
77179860b2SJed Brown    args.ArgumentProcessor.setup(self)
78179860b2SJed Brown
79179860b2SJed Brown    if self.argDB['noOutput']:
80179860b2SJed Brown      self.out           = None
81179860b2SJed Brown    if self.debugLevel is None:
82179860b2SJed Brown      self.debugLevel    = self.argDB['debugLevel']
83179860b2SJed Brown    if self.debugSections is None:
84179860b2SJed Brown      self.debugSections = self.argDB['debugSections']
85179860b2SJed Brown    if self.debugIndent is None:
86179860b2SJed Brown      self.debugIndent   = self.argDB['debugIndent']
87179860b2SJed Brown    return
88179860b2SJed Brown
89179860b2SJed Brown  def checkLog(self, logName):
90179860b2SJed Brown    import nargs
91179860b2SJed Brown    import os
92179860b2SJed Brown
93179860b2SJed Brown    if logName is None:
94179860b2SJed Brown      logName = nargs.Arg.findArgument('log', self.clArgs)
95179860b2SJed Brown    if logName is None:
96179860b2SJed Brown      if not self.argDB is None and 'log' in self.argDB:
97179860b2SJed Brown        logName    = self.argDB['log']
98179860b2SJed Brown      else:
99179860b2SJed Brown        logName    = 'default.log'
100179860b2SJed Brown    self.logName   = logName
101179860b2SJed Brown    self.logExists = os.path.exists(self.logName)
102179860b2SJed Brown    return self.logExists
103179860b2SJed Brown
104179860b2SJed Brown  def createLog(self, logName, initLog = None):
105179860b2SJed Brown    '''Create a default log stream, unless initLog is given'''
106179860b2SJed Brown    import nargs
107179860b2SJed Brown
108179860b2SJed Brown    if not initLog is None:
109179860b2SJed Brown      log = initLog
110179860b2SJed Brown    else:
111179860b2SJed Brown      if Logger.defaultLog is None:
112179860b2SJed Brown        appendArg = nargs.Arg.findArgument('logAppend', self.clArgs)
113179860b2SJed Brown        if self.checkLog(logName):
114179860b2SJed Brown          if not self.argDB is None and ('logAppend' in self.argDB and self.argDB['logAppend']) or (not appendArg is None and bool(appendArg)):
115c6ef1b5bSJed Brown            Logger.defaultLog = open(self.logName, 'a')
116179860b2SJed Brown          else:
117179860b2SJed Brown            try:
118179860b2SJed Brown              import os
119179860b2SJed Brown
120179860b2SJed Brown              os.rename(self.logName, self.logName+'.bkp')
121c6ef1b5bSJed Brown              Logger.defaultLog = open(self.logName, 'w')
122179860b2SJed Brown            except OSError:
12315ac2963SJed Brown              sys.stdout.write('WARNING: Cannot backup log file, appending instead.\n')
124c6ef1b5bSJed Brown              Logger.defaultLog = open(self.logName, 'a')
125179860b2SJed Brown        else:
126c6ef1b5bSJed Brown          Logger.defaultLog = open(self.logName, 'w')
127179860b2SJed Brown      log = Logger.defaultLog
128179860b2SJed Brown    return log
129179860b2SJed Brown
130179860b2SJed Brown  def closeLog(self):
131179860b2SJed Brown    '''Closes the log file'''
132179860b2SJed Brown    self.log.close()
133179860b2SJed Brown
134a75b4e77SMatthew G. Knepley  def saveLog(self):
135dc0f114dSBarry Smith    if self.debugLevel <= 3: return
1362d964b9fSJed Brown    import io
137a75b4e77SMatthew G. Knepley    self.logBkp = self.log
1382d964b9fSJed Brown    if sys.version_info < (3,):
1392d964b9fSJed Brown      self.log = io.BytesIO()
1402d964b9fSJed Brown    else:
1412d964b9fSJed Brown      self.log = io.StringIO()
142a75b4e77SMatthew G. Knepley
143a75b4e77SMatthew G. Knepley  def restoreLog(self):
144dc0f114dSBarry Smith    if self.debugLevel <= 3: return
145a75b4e77SMatthew G. Knepley    s = self.log.getvalue()
146a75b4e77SMatthew G. Knepley    self.log.close()
147a75b4e77SMatthew G. Knepley    self.log = self.logBkp
148a75b4e77SMatthew G. Knepley    del(self.logBkp)
149a75b4e77SMatthew G. Knepley    return s
150a75b4e77SMatthew G. Knepley
151179860b2SJed Brown  def getLinewidth(self):
152179860b2SJed Brown    global LineWidth
153179860b2SJed Brown    if not hasattr(self, '_linewidth'):
154179860b2SJed Brown      if self.out is None or not self.out.isatty() or self.argDB['scrollOutput']:
155179860b2SJed Brown        self._linewidth = -1
156179860b2SJed Brown      else:
157179860b2SJed Brown        if LineWidth == -1:
158179860b2SJed Brown          try:
159179860b2SJed Brown            import curses
160179860b2SJed Brown
161179860b2SJed Brown            try:
162179860b2SJed Brown              curses.setupterm()
163179860b2SJed Brown              (y, self._linewidth) = curses.initscr().getmaxyx()
164179860b2SJed Brown              curses.endwin()
165179860b2SJed Brown            except curses.error:
166179860b2SJed Brown              self._linewidth = -1
167179860b2SJed Brown          except:
168179860b2SJed Brown            self._linewidth = -1
169179860b2SJed Brown          LineWidth = self._linewidth
170179860b2SJed Brown        else:
171179860b2SJed Brown          self._linewidth = LineWidth
172179860b2SJed Brown    return self._linewidth
173179860b2SJed Brown  def setLinewidth(self, linewidth):
174179860b2SJed Brown    self._linewidth = linewidth
175179860b2SJed Brown    return
176179860b2SJed Brown  linewidth = property(getLinewidth, setLinewidth, doc = 'The maximum number of characters per log line')
177179860b2SJed Brown
178179860b2SJed Brown  def checkWrite(self, f, debugLevel, debugSection, writeAll = 0):
179179860b2SJed Brown    '''Check whether the log line should be written
180179860b2SJed Brown       - If writeAll is true, return true
181179860b2SJed Brown       - If debugLevel >= current level, and debugSection in current section or sections is empty, return true'''
182179860b2SJed Brown    if not isinstance(debugLevel, int):
183179860b2SJed Brown      raise RuntimeError('Debug level must be an integer: '+str(debugLevel))
184179860b2SJed Brown    if f is None:
185179860b2SJed Brown      return False
186179860b2SJed Brown    if writeAll:
187179860b2SJed Brown      return True
188179860b2SJed Brown    if self.debugLevel >= debugLevel and (not len(self.debugSections) or debugSection in self.debugSections):
189179860b2SJed Brown      return True
190179860b2SJed Brown    return False
191179860b2SJed Brown
192179860b2SJed Brown  def logIndent(self, debugLevel = -1, debugSection = None, comm = None):
193179860b2SJed Brown    '''Write the proper indentation to the log streams'''
194179860b2SJed Brown    import traceback
195179860b2SJed Brown
196179860b2SJed Brown    indentLevel = len(traceback.extract_stack())-5
197179860b2SJed Brown    for writeAll, f in enumerate([self.out, self.log]):
198179860b2SJed Brown      if self.checkWrite(f, debugLevel, debugSection, writeAll):
199179860b2SJed Brown        if not comm is None:
200179860b2SJed Brown          f.write('[')
201179860b2SJed Brown          f.write(str(comm.rank()))
202179860b2SJed Brown          f.write(']')
203179860b2SJed Brown        for i in range(indentLevel):
204179860b2SJed Brown          f.write(self.debugIndent)
205179860b2SJed Brown    return
206179860b2SJed Brown
207179860b2SJed Brown  def logBack(self):
208179860b2SJed Brown    '''Backup the current line if we are not scrolling output'''
209179860b2SJed Brown    if not self.out is None and self.linewidth > 0:
210179860b2SJed Brown      self.out.write('\r')
211179860b2SJed Brown    return
212179860b2SJed Brown
213179860b2SJed Brown  def logClear(self):
214179860b2SJed Brown    '''Clear the current line if we are not scrolling output'''
215179860b2SJed Brown    if not self.out is None and self.linewidth > 0:
216179860b2SJed Brown      self.out.write('\r')
217179860b2SJed Brown      self.out.write(''.join([' '] * self.linewidth))
218179860b2SJed Brown      self.out.write('\r')
219179860b2SJed Brown    return
220179860b2SJed Brown
221179860b2SJed Brown  def logPrintDivider(self, debugLevel = -1, debugSection = None, single = 0):
2229561296dSJacob Faibussowitsch    self.logPrint(('-' if single else '=')*self.dividerLength, debugLevel = debugLevel, debugSection = debugSection)
223179860b2SJed Brown    return
224179860b2SJed Brown
2259561296dSJacob Faibussowitsch  def logPrintBox(self,msg, debugLevel = -1, debugSection = 'screen', indent = 1, comm = None, rmDir = 1, prefix = None):
226*1edc1b4bSJacob Faibussowitsch    def center_wrap(banner,text,**kwargs):
227*1edc1b4bSJacob Faibussowitsch      def center_line(line):
228*1edc1b4bSJacob Faibussowitsch        return line.center(self.dividerLength).rstrip()
229*1edc1b4bSJacob Faibussowitsch
230*1edc1b4bSJacob Faibussowitsch      wrapped = textwrap.wrap(textwrap.dedent(text),**kwargs)
231*1edc1b4bSJacob Faibussowitsch      if len(wrapped) == 1:
232*1edc1b4bSJacob Faibussowitsch        # center-justify single lines
233*1edc1b4bSJacob Faibussowitsch        wrapped[0] = center_line(wrapped[0])
234*1edc1b4bSJacob Faibussowitsch      if banner:
235*1edc1b4bSJacob Faibussowitsch        # add the banner
236*1edc1b4bSJacob Faibussowitsch        wrapped.insert(0,center_line(banner))
237*1edc1b4bSJacob Faibussowitsch      return '\n'.join(wrapped)
238*1edc1b4bSJacob Faibussowitsch
239*1edc1b4bSJacob Faibussowitsch    def prepend_banner(msg,*args):
240*1edc1b4bSJacob Faibussowitsch      lo_msg = msg.lower()
241*1edc1b4bSJacob Faibussowitsch      banner = None
242*1edc1b4bSJacob Faibussowitsch
243*1edc1b4bSJacob Faibussowitsch      for title in args:
244*1edc1b4bSJacob Faibussowitsch        lo_title = title.lower()+':'
245*1edc1b4bSJacob Faibussowitsch        if lo_msg.startswith(lo_title):
246*1edc1b4bSJacob Faibussowitsch          banner = '***** {} *****'.format(title.upper())
247*1edc1b4bSJacob Faibussowitsch          msg    = msg.replace(lo_title,'').replace(lo_title.title(),'').lstrip()
248*1edc1b4bSJacob Faibussowitsch          break
249*1edc1b4bSJacob Faibussowitsch      return banner,msg
250*1edc1b4bSJacob Faibussowitsch
251*1edc1b4bSJacob Faibussowitsch
2529561296dSJacob Faibussowitsch    msg = msg.strip()
2539561296dSJacob Faibussowitsch    if prefix is None:
2549561296dSJacob Faibussowitsch      prefix = ' '*2
255*1edc1b4bSJacob Faibussowitsch      msg = 'warning: '+msg
2569561296dSJacob Faibussowitsch      # check if the message already has prefixes, in which case we should insert that
257*1edc1b4bSJacob Faibussowitsch      # prefix as a banner (and remove any existing copies of it)
258*1edc1b4bSJacob Faibussowitsch      banner,msg = prepend_banner(msg,'warning','error')
259*1edc1b4bSJacob Faibussowitsch    else:
260*1edc1b4bSJacob Faibussowitsch      banner = None
2619561296dSJacob Faibussowitsch
262*1edc1b4bSJacob Faibussowitsch    msg = center_wrap(banner,msg,width=self.dividerLength-2,initial_indent=prefix,subsequent_indent=prefix)
263179860b2SJed Brown    self.logClear()
264179860b2SJed Brown    self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection)
2659561296dSJacob Faibussowitsch    self.logPrint(msg, debugLevel = debugLevel, debugSection = debugSection, rmDir = rmDir)
266179860b2SJed Brown    self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection)
267179860b2SJed Brown    self.logPrint('', debugLevel = debugLevel, debugSection = debugSection)
268179860b2SJed Brown    return
269179860b2SJed Brown
270179860b2SJed Brown  def logClearRemoveDirectory(self):
271179860b2SJed Brown    global RemoveDirectory
272179860b2SJed Brown    global backupRemoveDirectory
273179860b2SJed Brown    backupRemoveDirectory = RemoveDirectory
274179860b2SJed Brown    RemoveDirectory = ''
275179860b2SJed Brown
276179860b2SJed Brown  def logResetRemoveDirectory(self):
277179860b2SJed Brown    global RemoveDirectory
278179860b2SJed Brown    global backupRemoveDirectory
279179860b2SJed Brown    RemoveDirectory = backupRemoveDirectory
280179860b2SJed Brown
281179860b2SJed Brown
282d15db81bSBarry Smith  def logWrite(self, msg, debugLevel = -1, debugSection = None, forceScroll = 0, rmDir = 1):
283179860b2SJed Brown    '''Write the message to the log streams'''
284b5f71184SBarry Smith    '''Generally goes to the file but not the screen'''
285dc0f114dSBarry Smith    if not msg: return
286179860b2SJed Brown    for writeAll, f in enumerate([self.out, self.log]):
287179860b2SJed Brown      if self.checkWrite(f, debugLevel, debugSection, writeAll):
288179860b2SJed Brown        if not forceScroll and not writeAll and self.linewidth > 0:
289179860b2SJed Brown          global RemoveDirectory
290179860b2SJed Brown          self.logBack()
291d15db81bSBarry Smith          if rmDir: msg = msg.replace(RemoveDirectory,'')
292179860b2SJed Brown          for ms in msg.split('\n'):
293179860b2SJed Brown            f.write(ms[0:self.linewidth])
294179860b2SJed Brown            f.write(''.join([' '] * (self.linewidth - len(ms))))
295179860b2SJed Brown        else:
296179860b2SJed Brown          if not debugSection is None and not debugSection == 'screen' and len(msg):
297179860b2SJed Brown            f.write(str(debugSection))
298179860b2SJed Brown            f.write(': ')
299179860b2SJed Brown          f.write(msg)
300179860b2SJed Brown        if hasattr(f, 'flush'):
301179860b2SJed Brown          f.flush()
302179860b2SJed Brown    return
303179860b2SJed Brown
304d15db81bSBarry Smith  def logPrint(self, msg, debugLevel = -1, debugSection = None, indent = 1, comm = None, forceScroll = 0, rmDir = 1):
305179860b2SJed Brown    '''Write the message to the log streams with proper indentation and a newline'''
306b5f71184SBarry Smith    '''Generally goes to the file and the screen'''
307179860b2SJed Brown    if indent:
308179860b2SJed Brown      self.logIndent(debugLevel, debugSection, comm)
309d15db81bSBarry Smith    self.logWrite(msg, debugLevel, debugSection, forceScroll = forceScroll, rmDir = rmDir)
310179860b2SJed Brown    for writeAll, f in enumerate([self.out, self.log]):
311179860b2SJed Brown      if self.checkWrite(f, debugLevel, debugSection, writeAll):
312179860b2SJed Brown        if writeAll or self.linewidth < 0:
313179860b2SJed Brown          f.write('\n')
314179860b2SJed Brown    return
315179860b2SJed Brown
316179860b2SJed Brown
317179860b2SJed Brown  def getRoot(self):
318179860b2SJed Brown    '''Return the directory containing this module
319179860b2SJed Brown       - This has the problem that when we reload a module of the same name, this gets screwed up
320179860b2SJed Brown         Therefore, we call it in the initializer, and stash it'''
321179860b2SJed Brown    #print '      In getRoot'
322179860b2SJed Brown    #print hasattr(self, '__root')
323179860b2SJed Brown    #print '      done checking'
324179860b2SJed Brown    if not hasattr(self, '__root'):
325179860b2SJed Brown      import os
326179860b2SJed Brown      import sys
327179860b2SJed Brown
328179860b2SJed Brown      # Work around a bug with pdb in 2.3
329179860b2SJed Brown      if hasattr(sys.modules[self.__module__], '__file__') and not os.path.basename(sys.modules[self.__module__].__file__) == 'pdb.py':
330179860b2SJed Brown        self.__root = os.path.abspath(os.path.dirname(sys.modules[self.__module__].__file__))
331179860b2SJed Brown      else:
332179860b2SJed Brown        self.__root = os.getcwd()
333179860b2SJed Brown    #print '      Exiting getRoot'
334179860b2SJed Brown    return self.__root
335179860b2SJed Brown  def setRoot(self, root):
336179860b2SJed Brown    self.__root = root
337179860b2SJed Brown    return
338179860b2SJed Brown  root = property(getRoot, setRoot, doc = 'The directory containing this module')
339