##// END OF EJS Templates
Make logging unicode-aware...
Thomas Kluyver -
Show More
@@ -1,217 +1,220 b''
1 1 """Logger class for IPython's logging facilities.
2 2 """
3 3
4 4 #*****************************************************************************
5 5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
6 6 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING, distributed as part of this software.
10 10 #*****************************************************************************
11 11
12 12 #****************************************************************************
13 13 # Modules and globals
14 14
15 15 # Python standard modules
16 16 import glob
17 import io
17 18 import os
18 19 import time
19 20
21 from IPython.utils.py3compat import str_to_unicode
22
20 23 #****************************************************************************
21 24 # FIXME: This class isn't a mixin anymore, but it still needs attributes from
22 25 # ipython and does input cache management. Finish cleanup later...
23 26
24 27 class Logger(object):
25 28 """A Logfile class with different policies for file creation"""
26 29
27 def __init__(self, home_dir, logfname='Logger.log', loghead='',
30 def __init__(self, home_dir, logfname='Logger.log', loghead=u'',
28 31 logmode='over'):
29 32
30 33 # this is the full ipython instance, we need some attributes from it
31 34 # which won't exist until later. What a mess, clean up later...
32 35 self.home_dir = home_dir
33 36
34 37 self.logfname = logfname
35 38 self.loghead = loghead
36 39 self.logmode = logmode
37 40 self.logfile = None
38 41
39 42 # Whether to log raw or processed input
40 43 self.log_raw_input = False
41 44
42 45 # whether to also log output
43 46 self.log_output = False
44 47
45 48 # whether to put timestamps before each log entry
46 49 self.timestamp = False
47 50
48 51 # activity control flags
49 52 self.log_active = False
50 53
51 54 # logmode is a validated property
52 55 def _set_mode(self,mode):
53 56 if mode not in ['append','backup','global','over','rotate']:
54 57 raise ValueError,'invalid log mode %s given' % mode
55 58 self._logmode = mode
56 59
57 60 def _get_mode(self):
58 61 return self._logmode
59 62
60 63 logmode = property(_get_mode,_set_mode)
61 64
62 65 def logstart(self,logfname=None,loghead=None,logmode=None,
63 66 log_output=False,timestamp=False,log_raw_input=False):
64 67 """Generate a new log-file with a default header.
65 68
66 69 Raises RuntimeError if the log has already been started"""
67 70
68 71 if self.logfile is not None:
69 72 raise RuntimeError('Log file is already active: %s' %
70 73 self.logfname)
71 74
72 75 # The parameters can override constructor defaults
73 76 if logfname is not None: self.logfname = logfname
74 77 if loghead is not None: self.loghead = loghead
75 78 if logmode is not None: self.logmode = logmode
76 79
77 80 # Parameters not part of the constructor
78 81 self.timestamp = timestamp
79 82 self.log_output = log_output
80 83 self.log_raw_input = log_raw_input
81 84
82 85 # init depending on the log mode requested
83 86 isfile = os.path.isfile
84 87 logmode = self.logmode
85 88
86 89 if logmode == 'append':
87 self.logfile = open(self.logfname,'a')
90 self.logfile = io.open(self.logfname, 'a', encoding='utf-8')
88 91
89 92 elif logmode == 'backup':
90 93 if isfile(self.logfname):
91 94 backup_logname = self.logfname+'~'
92 95 # Manually remove any old backup, since os.rename may fail
93 96 # under Windows.
94 97 if isfile(backup_logname):
95 98 os.remove(backup_logname)
96 99 os.rename(self.logfname,backup_logname)
97 self.logfile = open(self.logfname,'w')
100 self.logfile = io.open(self.logfname, 'w', encoding='utf-8')
98 101
99 102 elif logmode == 'global':
100 103 self.logfname = os.path.join(self.home_dir,self.logfname)
101 self.logfile = open(self.logfname, 'a')
104 self.logfile = io.open(self.logfname, 'a', encoding='utf-8')
102 105
103 106 elif logmode == 'over':
104 107 if isfile(self.logfname):
105 108 os.remove(self.logfname)
106 self.logfile = open(self.logfname,'w')
109 self.logfile = io.open(self.logfname,'w', encoding='utf-8')
107 110
108 111 elif logmode == 'rotate':
109 112 if isfile(self.logfname):
110 113 if isfile(self.logfname+'.001~'):
111 114 old = glob.glob(self.logfname+'.*~')
112 115 old.sort()
113 116 old.reverse()
114 117 for f in old:
115 118 root, ext = os.path.splitext(f)
116 119 num = int(ext[1:-1])+1
117 120 os.rename(f, root+'.'+`num`.zfill(3)+'~')
118 121 os.rename(self.logfname, self.logfname+'.001~')
119 self.logfile = open(self.logfname,'w')
122 self.logfile = io.open(self.logfname, 'w', encoding='utf-8')
120 123
121 124 if logmode != 'append':
122 125 self.logfile.write(self.loghead)
123 126
124 127 self.logfile.flush()
125 128 self.log_active = True
126 129
127 130 def switch_log(self,val):
128 131 """Switch logging on/off. val should be ONLY a boolean."""
129 132
130 133 if val not in [False,True,0,1]:
131 134 raise ValueError, \
132 135 'Call switch_log ONLY with a boolean argument, not with:',val
133 136
134 137 label = {0:'OFF',1:'ON',False:'OFF',True:'ON'}
135 138
136 139 if self.logfile is None:
137 140 print """
138 141 Logging hasn't been started yet (use logstart for that).
139 142
140 143 %logon/%logoff are for temporarily starting and stopping logging for a logfile
141 144 which already exists. But you must first start the logging process with
142 145 %logstart (optionally giving a logfile name)."""
143 146
144 147 else:
145 148 if self.log_active == val:
146 149 print 'Logging is already',label[val]
147 150 else:
148 151 print 'Switching logging',label[val]
149 152 self.log_active = not self.log_active
150 153 self.log_active_out = self.log_active
151 154
152 155 def logstate(self):
153 156 """Print a status message about the logger."""
154 157 if self.logfile is None:
155 158 print 'Logging has not been activated.'
156 159 else:
157 160 state = self.log_active and 'active' or 'temporarily suspended'
158 161 print 'Filename :',self.logfname
159 162 print 'Mode :',self.logmode
160 163 print 'Output logging :',self.log_output
161 164 print 'Raw input log :',self.log_raw_input
162 165 print 'Timestamping :',self.timestamp
163 166 print 'State :',state
164 167
165 168 def log(self, line_mod, line_ori):
166 169 """Write the sources to a log.
167 170
168 171 Inputs:
169 172
170 173 - line_mod: possibly modified input, such as the transformations made
171 174 by input prefilters or input handlers of various kinds. This should
172 175 always be valid Python.
173 176
174 177 - line_ori: unmodified input line from the user. This is not
175 178 necessarily valid Python.
176 179 """
177 180
178 181 # Write the log line, but decide which one according to the
179 182 # log_raw_input flag, set when the log is started.
180 183 if self.log_raw_input:
181 184 self.log_write(line_ori)
182 185 else:
183 186 self.log_write(line_mod)
184 187
185 188 def log_write(self, data, kind='input'):
186 189 """Write data to the log file, if active"""
187 190
188 191 #print 'data: %r' % data # dbg
189 192 if self.log_active and data:
190 193 write = self.logfile.write
191 194 if kind=='input':
192 195 if self.timestamp:
193 write(time.strftime('# %a, %d %b %Y %H:%M:%S\n',
194 time.localtime()))
196 write(str_to_unicode(time.strftime('# %a, %d %b %Y %H:%M:%S\n',
197 time.localtime())))
195 198 write(data)
196 199 elif kind=='output' and self.log_output:
197 odata = '\n'.join(['#[Out]# %s' % s
200 odata = u'\n'.join([u'#[Out]# %s' % s
198 201 for s in data.splitlines()])
199 write('%s\n' % odata)
202 write(u'%s\n' % odata)
200 203 self.logfile.flush()
201 204
202 205 def logstop(self):
203 206 """Fully stop logging and close log file.
204 207
205 208 In order to start logging again, a new logstart() call needs to be
206 209 made, possibly (though not necessarily) with a new filename, mode and
207 210 other options."""
208 211
209 212 if self.logfile is not None:
210 213 self.logfile.close()
211 214 self.logfile = None
212 215 else:
213 216 print "Logging hadn't been started."
214 217 self.log_active = False
215 218
216 219 # For backwards compatibility, in case anyone was using this.
217 220 close_log = logstop
@@ -1,169 +1,170 b''
1 1 """Implementation of magic functions for IPython's own logging.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (c) 2012 The IPython Development Team.
5 5 #
6 6 # Distributed under the terms of the Modified BSD License.
7 7 #
8 8 # The full license is in the file COPYING.txt, distributed with this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 # Stdlib
16 16 import os
17 17 import sys
18 18
19 19 # Our own packages
20 20 from IPython.core.magic import Magics, magics_class, line_magic
21 21 from IPython.utils.warn import warn
22 from IPython.utils.py3compat import str_to_unicode
22 23
23 24 #-----------------------------------------------------------------------------
24 25 # Magic implementation classes
25 26 #-----------------------------------------------------------------------------
26 27
27 28 @magics_class
28 29 class LoggingMagics(Magics):
29 30 """Magics related to all logging machinery."""
30 31
31 32 @line_magic
32 33 def logstart(self, parameter_s=''):
33 34 """Start logging anywhere in a session.
34 35
35 36 %logstart [-o|-r|-t] [log_name [log_mode]]
36 37
37 38 If no name is given, it defaults to a file named 'ipython_log.py' in your
38 39 current directory, in 'rotate' mode (see below).
39 40
40 41 '%logstart name' saves to file 'name' in 'backup' mode. It saves your
41 42 history up to that point and then continues logging.
42 43
43 44 %logstart takes a second optional parameter: logging mode. This can be one
44 45 of (note that the modes are given unquoted):\\
45 46 append: well, that says it.\\
46 47 backup: rename (if exists) to name~ and start name.\\
47 48 global: single logfile in your home dir, appended to.\\
48 49 over : overwrite existing log.\\
49 50 rotate: create rotating logs name.1~, name.2~, etc.
50 51
51 52 Options:
52 53
53 54 -o: log also IPython's output. In this mode, all commands which
54 55 generate an Out[NN] prompt are recorded to the logfile, right after
55 56 their corresponding input line. The output lines are always
56 57 prepended with a '#[Out]# ' marker, so that the log remains valid
57 58 Python code.
58 59
59 60 Since this marker is always the same, filtering only the output from
60 61 a log is very easy, using for example a simple awk call::
61 62
62 63 awk -F'#\\[Out\\]# ' '{if($2) {print $2}}' ipython_log.py
63 64
64 65 -r: log 'raw' input. Normally, IPython's logs contain the processed
65 66 input, so that user lines are logged in their final form, converted
66 67 into valid Python. For example, %Exit is logged as
67 68 _ip.magic("Exit"). If the -r flag is given, all input is logged
68 69 exactly as typed, with no transformations applied.
69 70
70 71 -t: put timestamps before each input line logged (these are put in
71 72 comments)."""
72 73
73 74 opts,par = self.parse_options(parameter_s,'ort')
74 75 log_output = 'o' in opts
75 76 log_raw_input = 'r' in opts
76 77 timestamp = 't' in opts
77 78
78 79 logger = self.shell.logger
79 80
80 81 # if no args are given, the defaults set in the logger constructor by
81 82 # ipython remain valid
82 83 if par:
83 84 try:
84 85 logfname,logmode = par.split()
85 86 except:
86 87 logfname = par
87 88 logmode = 'backup'
88 89 else:
89 90 logfname = logger.logfname
90 91 logmode = logger.logmode
91 92 # put logfname into rc struct as if it had been called on the command
92 93 # line, so it ends up saved in the log header Save it in case we need
93 94 # to restore it...
94 95 old_logfile = self.shell.logfile
95 96 if logfname:
96 97 logfname = os.path.expanduser(logfname)
97 98 self.shell.logfile = logfname
98 99
99 loghead = '# IPython log file\n\n'
100 loghead = u'# IPython log file\n\n'
100 101 try:
101 102 logger.logstart(logfname, loghead, logmode, log_output, timestamp,
102 103 log_raw_input)
103 104 except:
104 105 self.shell.logfile = old_logfile
105 106 warn("Couldn't start log: %s" % sys.exc_info()[1])
106 107 else:
107 108 # log input history up to this point, optionally interleaving
108 109 # output if requested
109 110
110 111 if timestamp:
111 112 # disable timestamping for the previous history, since we've
112 113 # lost those already (no time machine here).
113 114 logger.timestamp = False
114 115
115 116 if log_raw_input:
116 117 input_hist = self.shell.history_manager.input_hist_raw
117 118 else:
118 119 input_hist = self.shell.history_manager.input_hist_parsed
119 120
120 121 if log_output:
121 122 log_write = logger.log_write
122 123 output_hist = self.shell.history_manager.output_hist
123 124 for n in range(1,len(input_hist)-1):
124 log_write(input_hist[n].rstrip() + '\n')
125 log_write(input_hist[n].rstrip() + u'\n')
125 126 if n in output_hist:
126 log_write(repr(output_hist[n]),'output')
127 log_write(str_to_unicode(repr(output_hist[n])),'output')
127 128 else:
128 logger.log_write('\n'.join(input_hist[1:]))
129 logger.log_write('\n')
129 logger.log_write(u'\n'.join(input_hist[1:]))
130 logger.log_write(u'\n')
130 131 if timestamp:
131 132 # re-enable timestamping
132 133 logger.timestamp = True
133 134
134 135 print ('Activating auto-logging. '
135 136 'Current session state plus future input saved.')
136 137 logger.logstate()
137 138
138 139 @line_magic
139 140 def logstop(self, parameter_s=''):
140 141 """Fully stop logging and close log file.
141 142
142 143 In order to start logging again, a new %logstart call needs to be made,
143 144 possibly (though not necessarily) with a new filename, mode and other
144 145 options."""
145 self.logger.logstop()
146 self.shell.logger.logstop()
146 147
147 148 @line_magic
148 149 def logoff(self, parameter_s=''):
149 150 """Temporarily stop logging.
150 151
151 152 You must have previously started logging."""
152 153 self.shell.logger.switch_log(0)
153 154
154 155 @line_magic
155 156 def logon(self, parameter_s=''):
156 157 """Restart logging.
157 158
158 159 This function is for restarting logging which you've temporarily
159 160 stopped with %logoff. For starting logging for the first time, you
160 161 must use the %logstart function, which allows you to specify an
161 162 optional log filename."""
162 163
163 164 self.shell.logger.switch_log(1)
164 165
165 166 @line_magic
166 167 def logstate(self, parameter_s=''):
167 168 """Print the status of the logging system."""
168 169
169 170 self.shell.logger.logstate()
General Comments 0
You need to be logged in to leave comments. Login now