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

119 statements  

1"""Logger class for IPython's logging facilities. 

2""" 

3 

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#***************************************************************************** 

11 

12from __future__ import annotations 

13 

14#**************************************************************************** 

15# Modules and globals 

16 

17# Python standard modules 

18import glob 

19import io 

20import logging 

21import os 

22import time 

23from typing import IO 

24 

25 

26# prevent jedi/parso's debug messages pipe into interactiveshell 

27logging.getLogger("parso").setLevel(logging.WARNING) 

28 

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... 

32 

33class Logger: 

34 """A Logfile class with different policies for file creation""" 

35 

36 def __init__(self, home_dir: str, logfname: str = 'Logger.log', 

37 loghead: str = '', logmode: str = 'over') -> None: 

38 

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 

42 

43 self.logfname = logfname 

44 self.loghead = loghead 

45 self.logfile: IO[str] | None = None 

46 

47 # Whether to log raw or processed input 

48 self.log_raw_input = False 

49 

50 # whether to also log output 

51 self.log_output = False 

52 

53 # whether to put timestamps before each log entry 

54 self.timestamp = False 

55 

56 # activity control flags 

57 self.log_active = False 

58 

59 self.logmode = logmode 

60 

61 @property 

62 def logmode(self) -> str: 

63 return self._logmode 

64 

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 

70 

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. 

75 

76 Raises RuntimeError if the log has already been started""" 

77 

78 if self.logfile is not None: 

79 raise RuntimeError('Log file is already active: %s' % 

80 self.logfname) 

81 

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 

86 

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 

91 

92 # init depending on the log mode requested 

93 isfile = os.path.isfile 

94 logmode = self.logmode 

95 

96 if logmode == 'append': 

97 self.logfile = open(self.logfname, 'a', encoding='utf-8') 

98 

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') 

108 

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') 

112 

113 elif logmode == 'over': 

114 if isfile(self.logfname): 

115 os.remove(self.logfname) 

116 self.logfile = open(self.logfname,'w', encoding='utf-8') 

117 

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') 

130 

131 if logmode != 'append': 

132 self.logfile.write(self.loghead) 

133 

134 self.logfile.flush() 

135 self.log_active = True 

136 

137 def switch_log(self, val: bool) -> None: 

138 """Switch logging on/off. val should be ONLY a boolean.""" 

139 

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) 

143 

144 label = {0:'OFF',1:'ON',False:'OFF',True:'ON'} 

145 

146 if self.logfile is None: 

147 print(""" 

148Logging hasn't been started yet (use logstart for that). 

149 

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).""") 

153 

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 

161 

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) 

174 

175 def log(self, line_mod: str, line_ori: str) -> None: 

176 """Write the sources to a log. 

177 

178 Inputs: 

179 

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. 

183 

184 - line_ori: unmodified input line from the user. This is not 

185 necessarily valid Python. 

186 """ 

187 

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) 

194 

195 def log_write(self, data: str, kind: str = 'input') -> None: 

196 """Write data to the log file, if active""" 

197 

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 ) 

219 

220 def logstop(self) -> None: 

221 """Fully stop logging and close log file. 

222 

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.""" 

226 

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 

233 

234 # For backwards compatibility, in case anyone was using this. 

235 close_log = logstop