1"""Implementation of magic functions for IPython's own logging.
2"""
3#-----------------------------------------------------------------------------
4#  Copyright (c) 2012 The IPython Development Team.
5#
6#  Distributed under the terms of the Modified BSD License.
7#
8#  The full license is in the file COPYING.txt, distributed with this software.
9#-----------------------------------------------------------------------------
10
11#-----------------------------------------------------------------------------
12# Imports
13#-----------------------------------------------------------------------------
14
15# Stdlib
16import os
17import sys
18
19# Our own packages
20from IPython.core.magic import Magics, magics_class, line_magic
21from warnings import warn
22from IPython.utils.py3compat import str_to_unicode
23
24#-----------------------------------------------------------------------------
25# Magic implementation classes
26#-----------------------------------------------------------------------------
27
28@magics_class
29class LoggingMagics(Magics):
30    """Magics related to all logging machinery."""
31
32    @line_magic
33    def logstart(self, parameter_s=''):
34        """Start logging anywhere in a session.
35
36        %logstart [-o|-r|-t] [log_name [log_mode]]
37
38        If no name is given, it defaults to a file named 'ipython_log.py' in your
39        current directory, in 'rotate' mode (see below).
40
41        '%logstart name' saves to file 'name' in 'backup' mode.  It saves your
42        history up to that point and then continues logging.
43
44        %logstart takes a second optional parameter: logging mode. This can be one
45        of (note that the modes are given unquoted):
46
47        append
48            Keep logging at the end of any existing file.
49
50        backup
51            Rename any existing file to name~ and start name.
52
53        global
54            Append to  a single logfile in your home directory.
55
56        over
57            Overwrite any existing log.
58
59        rotate
60            Create rotating logs: name.1~, name.2~, etc.
61
62        Options:
63
64          -o
65            log also IPython's output. In this mode, all commands which
66            generate an Out[NN] prompt are recorded to the logfile, right after
67            their corresponding input line. The output lines are always
68            prepended with a '#[Out]# ' marker, so that the log remains valid
69            Python code.
70
71          Since this marker is always the same, filtering only the output from
72          a log is very easy, using for example a simple awk call::
73
74            awk -F'#\\[Out\\]# ' '{if($2) {print $2}}' ipython_log.py
75
76          -r
77            log 'raw' input.  Normally, IPython's logs contain the processed
78            input, so that user lines are logged in their final form, converted
79            into valid Python.  For example, %Exit is logged as
80            _ip.magic("Exit").  If the -r flag is given, all input is logged
81            exactly as typed, with no transformations applied.
82
83          -t
84            put timestamps before each input line logged (these are put in
85            comments).
86        """
87
88        opts,par = self.parse_options(parameter_s,'ort')
89        log_output = 'o' in opts
90        log_raw_input = 'r' in opts
91        timestamp = 't' in opts
92
93        logger = self.shell.logger
94
95        # if no args are given, the defaults set in the logger constructor by
96        # ipython remain valid
97        if par:
98            try:
99                logfname,logmode = par.split()
100            except:
101                logfname = par
102                logmode = 'backup'
103        else:
104            logfname = logger.logfname
105            logmode = logger.logmode
106        # put logfname into rc struct as if it had been called on the command
107        # line, so it ends up saved in the log header Save it in case we need
108        # to restore it...
109        old_logfile = self.shell.logfile
110        if logfname:
111            logfname = os.path.expanduser(logfname)
112        self.shell.logfile = logfname
113
114        loghead = u'# IPython log file\n\n'
115        try:
116            logger.logstart(logfname, loghead, logmode, log_output, timestamp,
117                            log_raw_input)
118        except:
119            self.shell.logfile = old_logfile
120            warn("Couldn't start log: %s" % sys.exc_info()[1])
121        else:
122            # log input history up to this point, optionally interleaving
123            # output if requested
124
125            if timestamp:
126                # disable timestamping for the previous history, since we've
127                # lost those already (no time machine here).
128                logger.timestamp = False
129
130            if log_raw_input:
131                input_hist = self.shell.history_manager.input_hist_raw
132            else:
133                input_hist = self.shell.history_manager.input_hist_parsed
134
135            if log_output:
136                log_write = logger.log_write
137                output_hist = self.shell.history_manager.output_hist
138                for n in range(1,len(input_hist)-1):
139                    log_write(input_hist[n].rstrip() + u'\n')
140                    if n in output_hist:
141                        log_write(str_to_unicode(repr(output_hist[n])),'output')
142            else:
143                logger.log_write(u'\n'.join(input_hist[1:]))
144                logger.log_write(u'\n')
145            if timestamp:
146                # re-enable timestamping
147                logger.timestamp = True
148
149            print ('Activating auto-logging. '
150                   'Current session state plus future input saved.')
151            logger.logstate()
152
153    @line_magic
154    def logstop(self, parameter_s=''):
155        """Fully stop logging and close log file.
156
157        In order to start logging again, a new %logstart call needs to be made,
158        possibly (though not necessarily) with a new filename, mode and other
159        options."""
160        self.shell.logger.logstop()
161
162    @line_magic
163    def logoff(self, parameter_s=''):
164        """Temporarily stop logging.
165
166        You must have previously started logging."""
167        self.shell.logger.switch_log(0)
168
169    @line_magic
170    def logon(self, parameter_s=''):
171        """Restart logging.
172
173        This function is for restarting logging which you've temporarily
174        stopped with %logoff. For starting logging for the first time, you
175        must use the %logstart function, which allows you to specify an
176        optional log filename."""
177
178        self.shell.logger.switch_log(1)
179
180    @line_magic
181    def logstate(self, parameter_s=''):
182        """Print the status of the logging system."""
183
184        self.shell.logger.logstate()
185