##// END OF EJS Templates
Remove all direct shell access from logger....
Fernando Perez -
Show More
@@ -1,266 +1,215 b''
1 # -*- coding: utf-8 -*-
1 """Logger class for IPython's logging facilities.
2 """
3 Logger class for IPython's logging facilities.
4 """
2 """
5
3
6 #*****************************************************************************
4 #*****************************************************************************
7 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
8 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
6 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
9 #
7 #
10 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
12 #*****************************************************************************
10 #*****************************************************************************
13
11
14 #****************************************************************************
12 #****************************************************************************
15 # Modules and globals
13 # Modules and globals
16
14
17 # Python standard modules
15 # Python standard modules
18 import glob
16 import glob
19 import os
17 import os
20 import time
18 import time
21
19
22 #****************************************************************************
20 #****************************************************************************
23 # FIXME: This class isn't a mixin anymore, but it still needs attributes from
21 # FIXME: This class isn't a mixin anymore, but it still needs attributes from
24 # ipython and does input cache management. Finish cleanup later...
22 # ipython and does input cache management. Finish cleanup later...
25
23
26 class Logger(object):
24 class Logger(object):
27 """A Logfile class with different policies for file creation"""
25 """A Logfile class with different policies for file creation"""
28
26
29 def __init__(self,shell,logfname='Logger.log',loghead='',logmode='over'):
27 def __init__(self, home_dir, logfname='Logger.log', loghead='',
30
28 logmode='over'):
31 self._i00,self._i,self._ii,self._iii = '','','',''
32
29
33 # this is the full ipython instance, we need some attributes from it
30 # this is the full ipython instance, we need some attributes from it
34 # which won't exist until later. What a mess, clean up later...
31 # which won't exist until later. What a mess, clean up later...
35 self.shell = shell
32 self.home_dir = home_dir
36
33
37 self.logfname = logfname
34 self.logfname = logfname
38 self.loghead = loghead
35 self.loghead = loghead
39 self.logmode = logmode
36 self.logmode = logmode
40 self.logfile = None
37 self.logfile = None
41
38
42 # Whether to log raw or processed input
39 # Whether to log raw or processed input
43 self.log_raw_input = False
40 self.log_raw_input = False
44
41
45 # whether to also log output
42 # whether to also log output
46 self.log_output = False
43 self.log_output = False
47
44
48 # whether to put timestamps before each log entry
45 # whether to put timestamps before each log entry
49 self.timestamp = False
46 self.timestamp = False
50
47
51 # activity control flags
48 # activity control flags
52 self.log_active = False
49 self.log_active = False
53
50
54 # logmode is a validated property
51 # logmode is a validated property
55 def _set_mode(self,mode):
52 def _set_mode(self,mode):
56 if mode not in ['append','backup','global','over','rotate']:
53 if mode not in ['append','backup','global','over','rotate']:
57 raise ValueError,'invalid log mode %s given' % mode
54 raise ValueError,'invalid log mode %s given' % mode
58 self._logmode = mode
55 self._logmode = mode
59
56
60 def _get_mode(self):
57 def _get_mode(self):
61 return self._logmode
58 return self._logmode
62
59
63 logmode = property(_get_mode,_set_mode)
60 logmode = property(_get_mode,_set_mode)
64
61
65 def logstart(self,logfname=None,loghead=None,logmode=None,
62 def logstart(self,logfname=None,loghead=None,logmode=None,
66 log_output=False,timestamp=False,log_raw_input=False):
63 log_output=False,timestamp=False,log_raw_input=False):
67 """Generate a new log-file with a default header.
64 """Generate a new log-file with a default header.
68
65
69 Raises RuntimeError if the log has already been started"""
66 Raises RuntimeError if the log has already been started"""
70
67
71 if self.logfile is not None:
68 if self.logfile is not None:
72 raise RuntimeError('Log file is already active: %s' %
69 raise RuntimeError('Log file is already active: %s' %
73 self.logfname)
70 self.logfname)
74
71
75 self.log_active = True
72 self.log_active = True
76
73
77 # The parameters can override constructor defaults
74 # The parameters can override constructor defaults
78 if logfname is not None: self.logfname = logfname
75 if logfname is not None: self.logfname = logfname
79 if loghead is not None: self.loghead = loghead
76 if loghead is not None: self.loghead = loghead
80 if logmode is not None: self.logmode = logmode
77 if logmode is not None: self.logmode = logmode
81
78
82 # Parameters not part of the constructor
79 # Parameters not part of the constructor
83 self.timestamp = timestamp
80 self.timestamp = timestamp
84 self.log_output = log_output
81 self.log_output = log_output
85 self.log_raw_input = log_raw_input
82 self.log_raw_input = log_raw_input
86
83
87 # init depending on the log mode requested
84 # init depending on the log mode requested
88 isfile = os.path.isfile
85 isfile = os.path.isfile
89 logmode = self.logmode
86 logmode = self.logmode
90
87
91 if logmode == 'append':
88 if logmode == 'append':
92 self.logfile = open(self.logfname,'a')
89 self.logfile = open(self.logfname,'a')
93
90
94 elif logmode == 'backup':
91 elif logmode == 'backup':
95 if isfile(self.logfname):
92 if isfile(self.logfname):
96 backup_logname = self.logfname+'~'
93 backup_logname = self.logfname+'~'
97 # Manually remove any old backup, since os.rename may fail
94 # Manually remove any old backup, since os.rename may fail
98 # under Windows.
95 # under Windows.
99 if isfile(backup_logname):
96 if isfile(backup_logname):
100 os.remove(backup_logname)
97 os.remove(backup_logname)
101 os.rename(self.logfname,backup_logname)
98 os.rename(self.logfname,backup_logname)
102 self.logfile = open(self.logfname,'w')
99 self.logfile = open(self.logfname,'w')
103
100
104 elif logmode == 'global':
101 elif logmode == 'global':
105 self.logfname = os.path.join(self.shell.home_dir,self.logfname)
102 self.logfname = os.path.join(self.home_dir,self.logfname)
106 self.logfile = open(self.logfname, 'a')
103 self.logfile = open(self.logfname, 'a')
107
104
108 elif logmode == 'over':
105 elif logmode == 'over':
109 if isfile(self.logfname):
106 if isfile(self.logfname):
110 os.remove(self.logfname)
107 os.remove(self.logfname)
111 self.logfile = open(self.logfname,'w')
108 self.logfile = open(self.logfname,'w')
112
109
113 elif logmode == 'rotate':
110 elif logmode == 'rotate':
114 if isfile(self.logfname):
111 if isfile(self.logfname):
115 if isfile(self.logfname+'.001~'):
112 if isfile(self.logfname+'.001~'):
116 old = glob.glob(self.logfname+'.*~')
113 old = glob.glob(self.logfname+'.*~')
117 old.sort()
114 old.sort()
118 old.reverse()
115 old.reverse()
119 for f in old:
116 for f in old:
120 root, ext = os.path.splitext(f)
117 root, ext = os.path.splitext(f)
121 num = int(ext[1:-1])+1
118 num = int(ext[1:-1])+1
122 os.rename(f, root+'.'+`num`.zfill(3)+'~')
119 os.rename(f, root+'.'+`num`.zfill(3)+'~')
123 os.rename(self.logfname, self.logfname+'.001~')
120 os.rename(self.logfname, self.logfname+'.001~')
124 self.logfile = open(self.logfname,'w')
121 self.logfile = open(self.logfname,'w')
125
122
126 if logmode != 'append':
123 if logmode != 'append':
127 self.logfile.write(self.loghead)
124 self.logfile.write(self.loghead)
128
125
129 self.logfile.flush()
126 self.logfile.flush()
130
127
131 def switch_log(self,val):
128 def switch_log(self,val):
132 """Switch logging on/off. val should be ONLY a boolean."""
129 """Switch logging on/off. val should be ONLY a boolean."""
133
130
134 if val not in [False,True,0,1]:
131 if val not in [False,True,0,1]:
135 raise ValueError, \
132 raise ValueError, \
136 'Call switch_log ONLY with a boolean argument, not with:',val
133 'Call switch_log ONLY with a boolean argument, not with:',val
137
134
138 label = {0:'OFF',1:'ON',False:'OFF',True:'ON'}
135 label = {0:'OFF',1:'ON',False:'OFF',True:'ON'}
139
136
140 if self.logfile is None:
137 if self.logfile is None:
141 print """
138 print """
142 Logging hasn't been started yet (use logstart for that).
139 Logging hasn't been started yet (use logstart for that).
143
140
144 %logon/%logoff are for temporarily starting and stopping logging for a logfile
141 %logon/%logoff are for temporarily starting and stopping logging for a logfile
145 which already exists. But you must first start the logging process with
142 which already exists. But you must first start the logging process with
146 %logstart (optionally giving a logfile name)."""
143 %logstart (optionally giving a logfile name)."""
147
144
148 else:
145 else:
149 if self.log_active == val:
146 if self.log_active == val:
150 print 'Logging is already',label[val]
147 print 'Logging is already',label[val]
151 else:
148 else:
152 print 'Switching logging',label[val]
149 print 'Switching logging',label[val]
153 self.log_active = not self.log_active
150 self.log_active = not self.log_active
154 self.log_active_out = self.log_active
151 self.log_active_out = self.log_active
155
152
156 def logstate(self):
153 def logstate(self):
157 """Print a status message about the logger."""
154 """Print a status message about the logger."""
158 if self.logfile is None:
155 if self.logfile is None:
159 print 'Logging has not been activated.'
156 print 'Logging has not been activated.'
160 else:
157 else:
161 state = self.log_active and 'active' or 'temporarily suspended'
158 state = self.log_active and 'active' or 'temporarily suspended'
162 print 'Filename :',self.logfname
159 print 'Filename :',self.logfname
163 print 'Mode :',self.logmode
160 print 'Mode :',self.logmode
164 print 'Output logging :',self.log_output
161 print 'Output logging :',self.log_output
165 print 'Raw input log :',self.log_raw_input
162 print 'Raw input log :',self.log_raw_input
166 print 'Timestamping :',self.timestamp
163 print 'Timestamping :',self.timestamp
167 print 'State :',state
164 print 'State :',state
168
165
169 def log(self,line_ori,line_mod,continuation=None):
166 def log(self, line_mod, line_ori):
170 """Write the line to a log and create input cache variables _i*.
167 """Write the sources to a log.
171
168
172 Inputs:
169 Inputs:
173
170
174 - line_ori: unmodified input line from the user. This is not
175 necessarily valid Python.
176
177 - line_mod: possibly modified input, such as the transformations made
171 - line_mod: possibly modified input, such as the transformations made
178 by input prefilters or input handlers of various kinds. This should
172 by input prefilters or input handlers of various kinds. This should
179 always be valid Python.
173 always be valid Python.
180
174
181 - continuation: if True, indicates this is part of multi-line input."""
175 - line_ori: unmodified input line from the user. This is not
182
176 necessarily valid Python.
183 # update the auto _i tables
177 """
184 #print '***logging line',line_mod # dbg
185 #print '***cache_count', self.shell.displayhook.prompt_count # dbg
186 try:
187 input_hist = self.shell.user_ns['_ih']
188 except:
189 #print 'userns:',self.shell.user_ns.keys() # dbg
190 return
191
192 out_cache = self.shell.displayhook
193
194 # add blank lines if the input cache fell out of sync.
195 if out_cache.do_full_cache and \
196 out_cache.prompt_count +1 > len(input_hist):
197 pass
198 #input_hist.extend(['\n'] * (out_cache.prompt_count - len(input_hist)))
199
200 if not continuation and line_mod:
201 self._iii = self._ii
202 self._ii = self._i
203 self._i = self._i00
204 # put back the final \n of every input line
205 self._i00 = line_mod+'\n'
206 #print 'Logging input:<%s>' % line_mod # dbg
207 #input_hist.append(self._i00)
208 #print '---[%s]' % (len(input_hist)-1,) # dbg
209
210 # hackish access to top-level namespace to create _i1,_i2... dynamically
211 to_main = {'_i':self._i,'_ii':self._ii,'_iii':self._iii}
212 if self.shell.displayhook.do_full_cache:
213 in_num = self.shell.displayhook.prompt_count
214
215 # but if the opposite is true (a macro can produce multiple inputs
216 # with no output display called), then bring the output counter in
217 # sync:
218 ## last_num = len(input_hist)-1
219 ## if in_num != last_num:
220 ## pass # dbg
221 ## #in_num = self.shell.execution_count = last_num
222
223 new_i = '_i%s' % in_num
224 if continuation:
225 self._i00 = '%s%s\n' % (self.shell.user_ns[new_i],line_mod)
226 #input_hist[in_num] = self._i00
227 to_main[new_i] = self._i00
228 self.shell.user_ns.update(to_main)
229
178
230 # Write the log line, but decide which one according to the
179 # Write the log line, but decide which one according to the
231 # log_raw_input flag, set when the log is started.
180 # log_raw_input flag, set when the log is started.
232 if self.log_raw_input:
181 if self.log_raw_input:
233 self.log_write(line_ori)
182 self.log_write(line_ori)
234 else:
183 else:
235 self.log_write(line_mod)
184 self.log_write(line_mod)
236
185
237 def log_write(self,data,kind='input'):
186 def log_write(self, data, kind='input'):
238 """Write data to the log file, if active"""
187 """Write data to the log file, if active"""
239
188
240 #print 'data: %r' % data # dbg
189 #print 'data: %r' % data # dbg
241 if self.log_active and data:
190 if self.log_active and data:
242 write = self.logfile.write
191 write = self.logfile.write
243 if kind=='input':
192 if kind=='input':
244 if self.timestamp:
193 if self.timestamp:
245 write(time.strftime('# %a, %d %b %Y %H:%M:%S\n',
194 write(time.strftime('# %a, %d %b %Y %H:%M:%S\n',
246 time.localtime()))
195 time.localtime()))
247 write('%s\n' % data)
196 write(data)
248 elif kind=='output' and self.log_output:
197 elif kind=='output' and self.log_output:
249 odata = '\n'.join(['#[Out]# %s' % s
198 odata = '\n'.join(['#[Out]# %s' % s
250 for s in data.split('\n')])
199 for s in data.splitlines()])
251 write('%s\n' % odata)
200 write('%s\n' % odata)
252 self.logfile.flush()
201 self.logfile.flush()
253
202
254 def logstop(self):
203 def logstop(self):
255 """Fully stop logging and close log file.
204 """Fully stop logging and close log file.
256
205
257 In order to start logging again, a new logstart() call needs to be
206 In order to start logging again, a new logstart() call needs to be
258 made, possibly (though not necessarily) with a new filename, mode and
207 made, possibly (though not necessarily) with a new filename, mode and
259 other options."""
208 other options."""
260
209
261 self.logfile.close()
210 self.logfile.close()
262 self.logfile = None
211 self.logfile = None
263 self.log_active = False
212 self.log_active = False
264
213
265 # For backwards compatibility, in case anyone was using this.
214 # For backwards compatibility, in case anyone was using this.
266 close_log = logstop
215 close_log = logstop
General Comments 0
You need to be logged in to leave comments. Login now