Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/logger.py: 18%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Logger class for IPython's logging facilities.
2"""
4#*****************************************************************************
5# Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
6# Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
7#
8# Distributed under the terms of the BSD License. The full license is in
9# the file COPYING, distributed as part of this software.
10#*****************************************************************************
12from __future__ import annotations
14#****************************************************************************
15# Modules and globals
17# Python standard modules
18import glob
19import io
20import logging
21import os
22import time
23from typing import IO
26# prevent jedi/parso's debug messages pipe into interactiveshell
27logging.getLogger("parso").setLevel(logging.WARNING)
29#****************************************************************************
30# FIXME: This class isn't a mixin anymore, but it still needs attributes from
31# ipython and does input cache management. Finish cleanup later...
33class Logger:
34 """A Logfile class with different policies for file creation"""
36 def __init__(self, home_dir: str, logfname: str = 'Logger.log',
37 loghead: str = '', logmode: str = 'over') -> None:
39 # this is the full ipython instance, we need some attributes from it
40 # which won't exist until later. What a mess, clean up later...
41 self.home_dir = home_dir
43 self.logfname = logfname
44 self.loghead = loghead
45 self.logfile: IO[str] | None = None
47 # Whether to log raw or processed input
48 self.log_raw_input = False
50 # whether to also log output
51 self.log_output = False
53 # whether to put timestamps before each log entry
54 self.timestamp = False
56 # activity control flags
57 self.log_active = False
59 self.logmode = logmode
61 @property
62 def logmode(self) -> str:
63 return self._logmode
65 @logmode.setter
66 def logmode(self, mode: str) -> None:
67 if mode not in ['append', 'backup', 'global', 'over', 'rotate']:
68 raise ValueError('invalid log mode %s given' % mode)
69 self._logmode = mode
71 def logstart(self, logfname: str | None = None, loghead: str | None = None,
72 logmode: str | None = None, log_output: bool = False,
73 timestamp: bool = False, log_raw_input: bool = False) -> None:
74 """Generate a new log-file with a default header.
76 Raises RuntimeError if the log has already been started"""
78 if self.logfile is not None:
79 raise RuntimeError('Log file is already active: %s' %
80 self.logfname)
82 # The parameters can override constructor defaults
83 if logfname is not None: self.logfname = logfname
84 if loghead is not None: self.loghead = loghead
85 if logmode is not None: self.logmode = logmode
87 # Parameters not part of the constructor
88 self.timestamp = timestamp
89 self.log_output = log_output
90 self.log_raw_input = log_raw_input
92 # init depending on the log mode requested
93 isfile = os.path.isfile
94 logmode = self.logmode
96 if logmode == 'append':
97 self.logfile = open(self.logfname, 'a', encoding='utf-8')
99 elif logmode == 'backup':
100 if isfile(self.logfname):
101 backup_logname = self.logfname+'~'
102 # Manually remove any old backup, since os.rename may fail
103 # under Windows.
104 if isfile(backup_logname):
105 os.remove(backup_logname)
106 os.rename(self.logfname,backup_logname)
107 self.logfile = open(self.logfname, 'w', encoding='utf-8')
109 elif logmode == 'global':
110 self.logfname = os.path.join(self.home_dir,self.logfname)
111 self.logfile = open(self.logfname, 'a', encoding='utf-8')
113 elif logmode == 'over':
114 if isfile(self.logfname):
115 os.remove(self.logfname)
116 self.logfile = open(self.logfname,'w', encoding='utf-8')
118 elif logmode == 'rotate':
119 if isfile(self.logfname):
120 if isfile(self.logfname+'.001~'):
121 old = glob.glob(self.logfname+'.*~')
122 old.sort()
123 old.reverse()
124 for f in old:
125 root, ext = os.path.splitext(f)
126 num = int(ext[1:-1])+1
127 os.rename(f, root+'.'+repr(num).zfill(3)+'~')
128 os.rename(self.logfname, self.logfname+'.001~')
129 self.logfile = open(self.logfname, 'w', encoding='utf-8')
131 if logmode != 'append':
132 self.logfile.write(self.loghead)
134 self.logfile.flush()
135 self.log_active = True
137 def switch_log(self, val: bool) -> None:
138 """Switch logging on/off. val should be ONLY a boolean."""
140 if val not in [False,True,0,1]:
141 raise ValueError('Call switch_log ONLY with a boolean argument, '
142 'not with: %s' % val)
144 label = {0:'OFF',1:'ON',False:'OFF',True:'ON'}
146 if self.logfile is None:
147 print("""
148Logging hasn't been started yet (use logstart for that).
150%logon/%logoff are for temporarily starting and stopping logging for a logfile
151which already exists. But you must first start the logging process with
152%logstart (optionally giving a logfile name).""")
154 else:
155 if self.log_active == val:
156 print('Logging is already',label[val])
157 else:
158 print('Switching logging',label[val])
159 self.log_active = not self.log_active
160 self.log_active_out = self.log_active
162 def logstate(self) -> None:
163 """Print a status message about the logger."""
164 if self.logfile is None:
165 print('Logging has not been activated.')
166 else:
167 state = self.log_active and 'active' or 'temporarily suspended'
168 print('Filename :', self.logfname)
169 print('Mode :', self.logmode)
170 print('Output logging :', self.log_output)
171 print('Raw input log :', self.log_raw_input)
172 print('Timestamping :', self.timestamp)
173 print('State :', state)
175 def log(self, line_mod: str, line_ori: str) -> None:
176 """Write the sources to a log.
178 Inputs:
180 - line_mod: possibly modified input, such as the transformations made
181 by input prefilters or input handlers of various kinds. This should
182 always be valid Python.
184 - line_ori: unmodified input line from the user. This is not
185 necessarily valid Python.
186 """
188 # Write the log line, but decide which one according to the
189 # log_raw_input flag, set when the log is started.
190 if self.log_raw_input:
191 self.log_write(line_ori)
192 else:
193 self.log_write(line_mod)
195 def log_write(self, data: str, kind: str = 'input') -> None:
196 """Write data to the log file, if active"""
198 # print('data: %r' % data) # dbg
199 if self.log_active and data:
200 write = self.logfile.write
201 if kind=='input':
202 if self.timestamp:
203 write(time.strftime('# %a, %d %b %Y %H:%M:%S\n', time.localtime()))
204 write(data)
205 elif kind=='output' and self.log_output:
206 odata = '\n'.join(['#[Out]# %s' % s
207 for s in data.splitlines()])
208 write('%s\n' % odata)
209 try:
210 self.logfile.flush()
211 except OSError:
212 print("Failed to flush the log file.")
213 print(
214 f"Please check that {self.logfname} exists and have the right permissions."
215 )
216 print(
217 "Also consider turning off the log with `%logstop` to avoid this warning."
218 )
220 def logstop(self) -> None:
221 """Fully stop logging and close log file.
223 In order to start logging again, a new logstart() call needs to be
224 made, possibly (though not necessarily) with a new filename, mode and
225 other options."""
227 if self.logfile is not None:
228 self.logfile.close()
229 self.logfile = None
230 else:
231 print("Logging hadn't been started.")
232 self.log_active = False
234 # For backwards compatibility, in case anyone was using this.
235 close_log = logstop