xref: /petsc/config/BuildSystem/logger.py (revision 9911f03573c2e4728d9655040157a1ae01ac3a2c)
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
15class Logger(args.ArgumentProcessor):
16  '''This class creates a shared log and provides methods for writing to it'''
17  defaultLog = None
18  defaultOut = sys.stdout
19
20  def __init__(self, clArgs = None, argDB = None, log = None, out = defaultOut, debugLevel = None, debugSections = None, debugIndent = None):
21    args.ArgumentProcessor.__init__(self, clArgs, argDB)
22    self.logName       = None
23    self.log           = log
24    self.out           = out
25    self.debugLevel    = debugLevel
26    self.debugSections = debugSections
27    self.debugIndent   = debugIndent
28    self.linewidth     = -1
29    self.getRoot()
30    return
31
32  def __getstate__(self):
33    '''We do not want to pickle the default log stream'''
34    d = args.ArgumentProcessor.__getstate__(self)
35    if 'logBkp' in d:
36        del d['logBkp']
37    if 'log' in d:
38      if d['log'] is Logger.defaultLog:
39        del d['log']
40      else:
41        d['log'] = None
42    if 'out' in d:
43      if d['out'] is Logger.defaultOut:
44        del d['out']
45      else:
46        d['out'] = None
47    return d
48
49  def __setstate__(self, d):
50    '''We must create the default log stream'''
51    args.ArgumentProcessor.__setstate__(self, d)
52    if not 'log' in d:
53      self.log = self.createLog(None)
54    if not 'out' in d:
55      self.out = Logger.defaultOut
56    self.__dict__.update(d)
57    return
58
59  def setupArguments(self, argDB):
60    '''Setup types in the argument database'''
61    import nargs
62
63    argDB = args.ArgumentProcessor.setupArguments(self, argDB)
64    argDB.setType('log',           nargs.Arg(None, 'buildsystem.log', 'The filename for the log'))
65    argDB.setType('logAppend',     nargs.ArgBool(None, 0, 'The flag determining whether we backup or append to the current log', isTemporary = 1))
66    argDB.setType('debugLevel',    nargs.ArgInt(None, 3, 'Integer 0 to 4, where a higher level means more detail', 0, 5))
67    argDB.setType('debugSections', nargs.Arg(None, [], 'Message types to print, e.g. [compile,link,hg,install]'))
68    argDB.setType('debugIndent',   nargs.Arg(None, '  ', 'The string used for log indentation'))
69    argDB.setType('scrollOutput',  nargs.ArgBool(None, 0, 'Flag to allow output to scroll rather than overwriting a single line'))
70    argDB.setType('noOutput',      nargs.ArgBool(None, 0, 'Flag to suppress output to the terminal'))
71    return argDB
72
73  def setup(self):
74    '''Setup the terminal output and filtering flags'''
75    self.log = self.createLog(self.logName, self.log)
76    args.ArgumentProcessor.setup(self)
77
78    if self.argDB['noOutput']:
79      self.out           = None
80    if self.debugLevel is None:
81      self.debugLevel    = self.argDB['debugLevel']
82    if self.debugSections is None:
83      self.debugSections = self.argDB['debugSections']
84    if self.debugIndent is None:
85      self.debugIndent   = self.argDB['debugIndent']
86    return
87
88  def checkLog(self, logName):
89    import nargs
90    import os
91
92    if logName is None:
93      logName = nargs.Arg.findArgument('log', self.clArgs)
94    if logName is None:
95      if not self.argDB is None and 'log' in self.argDB:
96        logName    = self.argDB['log']
97      else:
98        logName    = 'default.log'
99    self.logName   = logName
100    self.logExists = os.path.exists(self.logName)
101    return self.logExists
102
103  def createLog(self, logName, initLog = None):
104    '''Create a default log stream, unless initLog is given'''
105    import nargs
106
107    if not initLog is None:
108      log = initLog
109    else:
110      if Logger.defaultLog is None:
111        appendArg = nargs.Arg.findArgument('logAppend', self.clArgs)
112        if self.checkLog(logName):
113          if not self.argDB is None and ('logAppend' in self.argDB and self.argDB['logAppend']) or (not appendArg is None and bool(appendArg)):
114            Logger.defaultLog = open(self.logName, 'a')
115          else:
116            try:
117              import os
118
119              os.rename(self.logName, self.logName+'.bkp')
120              Logger.defaultLog = open(self.logName, 'w')
121            except OSError:
122              sys.stdout.write('WARNING: Cannot backup log file, appending instead.\n')
123              Logger.defaultLog = open(self.logName, 'a')
124        else:
125          Logger.defaultLog = open(self.logName, 'w')
126      log = Logger.defaultLog
127    return log
128
129  def closeLog(self):
130    '''Closes the log file'''
131    self.log.close()
132
133  def saveLog(self):
134    import io
135    self.logBkp = self.log
136    if sys.version_info < (3,):
137      self.log = io.BytesIO()
138    else:
139      self.log = io.StringIO()
140
141  def restoreLog(self):
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 not self.out is 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 not self.out is None and self.linewidth > 0:
213      self.out.write('\r')
214      self.out.write(''.join([' '] * self.linewidth))
215      self.out.write('\r')
216    return
217
218  def logPrintDivider(self, debugLevel = -1, debugSection = None, single = 0):
219    if single:
220      self.logPrint('-------------------------------------------------------------------------------', debugLevel = debugLevel, debugSection = debugSection)
221    else:
222      self.logPrint('===============================================================================', debugLevel = debugLevel, debugSection = debugSection)
223    return
224
225  def logPrintBox(self,msg, debugLevel = -1, debugSection = 'screen', indent = 1, comm = None):
226    self.logClear()
227    self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection)
228    [self.logPrint('      '+line, debugLevel = debugLevel, debugSection = debugSection) for line in msg.split('\n')]
229    self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection)
230    self.logPrint('', debugLevel = debugLevel, debugSection = debugSection)
231    return
232
233  def logClearRemoveDirectory(self):
234    global RemoveDirectory
235    global backupRemoveDirectory
236    backupRemoveDirectory = RemoveDirectory
237    RemoveDirectory = ''
238
239  def logResetRemoveDirectory(self):
240    global RemoveDirectory
241    global backupRemoveDirectory
242    RemoveDirectory = backupRemoveDirectory
243
244
245  def logWrite(self, msg, debugLevel = -1, debugSection = None, forceScroll = 0):
246    '''Write the message to the log streams'''
247    for writeAll, f in enumerate([self.out, self.log]):
248      if self.checkWrite(f, debugLevel, debugSection, writeAll):
249        if not forceScroll and not writeAll and self.linewidth > 0:
250          global RemoveDirectory
251          self.logBack()
252          msg = msg.replace(RemoveDirectory,'')
253          for ms in msg.split('\n'):
254            f.write(ms[0:self.linewidth])
255            f.write(''.join([' '] * (self.linewidth - len(ms))))
256        else:
257          if not debugSection is None and not debugSection == 'screen' and len(msg):
258            f.write(str(debugSection))
259            f.write(': ')
260          f.write(msg)
261        if hasattr(f, 'flush'):
262          f.flush()
263    return
264
265  def logPrint(self, msg, debugLevel = -1, debugSection = None, indent = 1, comm = None, forceScroll = 0):
266    '''Write the message to the log streams with proper indentation and a newline'''
267    if indent:
268      self.logIndent(debugLevel, debugSection, comm)
269    self.logWrite(msg, debugLevel, debugSection, forceScroll = forceScroll)
270    for writeAll, f in enumerate([self.out, self.log]):
271      if self.checkWrite(f, debugLevel, debugSection, writeAll):
272        if writeAll or self.linewidth < 0:
273          f.write('\n')
274    return
275
276
277  def getRoot(self):
278    '''Return the directory containing this module
279       - This has the problem that when we reload a module of the same name, this gets screwed up
280         Therefore, we call it in the initializer, and stash it'''
281    #print '      In getRoot'
282    #print hasattr(self, '__root')
283    #print '      done checking'
284    if not hasattr(self, '__root'):
285      import os
286      import sys
287
288      # Work around a bug with pdb in 2.3
289      if hasattr(sys.modules[self.__module__], '__file__') and not os.path.basename(sys.modules[self.__module__].__file__) == 'pdb.py':
290        self.__root = os.path.abspath(os.path.dirname(sys.modules[self.__module__].__file__))
291      else:
292        self.__root = os.getcwd()
293    #print '      Exiting getRoot'
294    return self.__root
295  def setRoot(self, root):
296    self.__root = root
297    return
298  root = property(getRoot, setRoot, doc = 'The directory containing this module')
299