logger.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. """Logger class for IPython's logging facilities.
  2. """
  3. from __future__ import print_function
  4. #*****************************************************************************
  5. # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
  6. # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
  7. #
  8. # Distributed under the terms of the BSD License. The full license is in
  9. # the file COPYING, distributed as part of this software.
  10. #*****************************************************************************
  11. #****************************************************************************
  12. # Modules and globals
  13. # Python standard modules
  14. import glob
  15. import io
  16. import os
  17. import time
  18. from IPython.utils.py3compat import str_to_unicode
  19. #****************************************************************************
  20. # FIXME: This class isn't a mixin anymore, but it still needs attributes from
  21. # ipython and does input cache management. Finish cleanup later...
  22. class Logger(object):
  23. """A Logfile class with different policies for file creation"""
  24. def __init__(self, home_dir, logfname='Logger.log', loghead=u'',
  25. logmode='over'):
  26. # this is the full ipython instance, we need some attributes from it
  27. # which won't exist until later. What a mess, clean up later...
  28. self.home_dir = home_dir
  29. self.logfname = logfname
  30. self.loghead = loghead
  31. self.logmode = logmode
  32. self.logfile = None
  33. # Whether to log raw or processed input
  34. self.log_raw_input = False
  35. # whether to also log output
  36. self.log_output = False
  37. # whether to put timestamps before each log entry
  38. self.timestamp = False
  39. # activity control flags
  40. self.log_active = False
  41. # logmode is a validated property
  42. def _set_mode(self,mode):
  43. if mode not in ['append','backup','global','over','rotate']:
  44. raise ValueError('invalid log mode %s given' % mode)
  45. self._logmode = mode
  46. def _get_mode(self):
  47. return self._logmode
  48. logmode = property(_get_mode,_set_mode)
  49. def logstart(self, logfname=None, loghead=None, logmode=None,
  50. log_output=False, timestamp=False, log_raw_input=False):
  51. """Generate a new log-file with a default header.
  52. Raises RuntimeError if the log has already been started"""
  53. if self.logfile is not None:
  54. raise RuntimeError('Log file is already active: %s' %
  55. self.logfname)
  56. # The parameters can override constructor defaults
  57. if logfname is not None: self.logfname = logfname
  58. if loghead is not None: self.loghead = loghead
  59. if logmode is not None: self.logmode = logmode
  60. # Parameters not part of the constructor
  61. self.timestamp = timestamp
  62. self.log_output = log_output
  63. self.log_raw_input = log_raw_input
  64. # init depending on the log mode requested
  65. isfile = os.path.isfile
  66. logmode = self.logmode
  67. if logmode == 'append':
  68. self.logfile = io.open(self.logfname, 'a', encoding='utf-8')
  69. elif logmode == 'backup':
  70. if isfile(self.logfname):
  71. backup_logname = self.logfname+'~'
  72. # Manually remove any old backup, since os.rename may fail
  73. # under Windows.
  74. if isfile(backup_logname):
  75. os.remove(backup_logname)
  76. os.rename(self.logfname,backup_logname)
  77. self.logfile = io.open(self.logfname, 'w', encoding='utf-8')
  78. elif logmode == 'global':
  79. self.logfname = os.path.join(self.home_dir,self.logfname)
  80. self.logfile = io.open(self.logfname, 'a', encoding='utf-8')
  81. elif logmode == 'over':
  82. if isfile(self.logfname):
  83. os.remove(self.logfname)
  84. self.logfile = io.open(self.logfname,'w', encoding='utf-8')
  85. elif logmode == 'rotate':
  86. if isfile(self.logfname):
  87. if isfile(self.logfname+'.001~'):
  88. old = glob.glob(self.logfname+'.*~')
  89. old.sort()
  90. old.reverse()
  91. for f in old:
  92. root, ext = os.path.splitext(f)
  93. num = int(ext[1:-1])+1
  94. os.rename(f, root+'.'+repr(num).zfill(3)+'~')
  95. os.rename(self.logfname, self.logfname+'.001~')
  96. self.logfile = io.open(self.logfname, 'w', encoding='utf-8')
  97. if logmode != 'append':
  98. self.logfile.write(self.loghead)
  99. self.logfile.flush()
  100. self.log_active = True
  101. def switch_log(self,val):
  102. """Switch logging on/off. val should be ONLY a boolean."""
  103. if val not in [False,True,0,1]:
  104. raise ValueError('Call switch_log ONLY with a boolean argument, '
  105. 'not with: %s' % val)
  106. label = {0:'OFF',1:'ON',False:'OFF',True:'ON'}
  107. if self.logfile is None:
  108. print("""
  109. Logging hasn't been started yet (use logstart for that).
  110. %logon/%logoff are for temporarily starting and stopping logging for a logfile
  111. which already exists. But you must first start the logging process with
  112. %logstart (optionally giving a logfile name).""")
  113. else:
  114. if self.log_active == val:
  115. print('Logging is already',label[val])
  116. else:
  117. print('Switching logging',label[val])
  118. self.log_active = not self.log_active
  119. self.log_active_out = self.log_active
  120. def logstate(self):
  121. """Print a status message about the logger."""
  122. if self.logfile is None:
  123. print('Logging has not been activated.')
  124. else:
  125. state = self.log_active and 'active' or 'temporarily suspended'
  126. print('Filename :', self.logfname)
  127. print('Mode :', self.logmode)
  128. print('Output logging :', self.log_output)
  129. print('Raw input log :', self.log_raw_input)
  130. print('Timestamping :', self.timestamp)
  131. print('State :', state)
  132. def log(self, line_mod, line_ori):
  133. """Write the sources to a log.
  134. Inputs:
  135. - line_mod: possibly modified input, such as the transformations made
  136. by input prefilters or input handlers of various kinds. This should
  137. always be valid Python.
  138. - line_ori: unmodified input line from the user. This is not
  139. necessarily valid Python.
  140. """
  141. # Write the log line, but decide which one according to the
  142. # log_raw_input flag, set when the log is started.
  143. if self.log_raw_input:
  144. self.log_write(line_ori)
  145. else:
  146. self.log_write(line_mod)
  147. def log_write(self, data, kind='input'):
  148. """Write data to the log file, if active"""
  149. #print 'data: %r' % data # dbg
  150. if self.log_active and data:
  151. write = self.logfile.write
  152. if kind=='input':
  153. if self.timestamp:
  154. write(str_to_unicode(time.strftime('# %a, %d %b %Y %H:%M:%S\n',
  155. time.localtime())))
  156. write(data)
  157. elif kind=='output' and self.log_output:
  158. odata = u'\n'.join([u'#[Out]# %s' % s
  159. for s in data.splitlines()])
  160. write(u'%s\n' % odata)
  161. self.logfile.flush()
  162. def logstop(self):
  163. """Fully stop logging and close log file.
  164. In order to start logging again, a new logstart() call needs to be
  165. made, possibly (though not necessarily) with a new filename, mode and
  166. other options."""
  167. if self.logfile is not None:
  168. self.logfile.close()
  169. self.logfile = None
  170. else:
  171. print("Logging hadn't been started.")
  172. self.log_active = False
  173. # For backwards compatibility, in case anyone was using this.
  174. close_log = logstop