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