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