logger.py 8.2 KB

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