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