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