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