Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/logger.py: 16%

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

116 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 

12#**************************************************************************** 

13# Modules and globals 

14 

15# Python standard modules 

16import glob 

17import io 

18import logging 

19import os 

20import time 

21 

22 

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

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

25 

26#**************************************************************************** 

27# FIXME: This class isn't a mixin anymore, but it still needs attributes from 

28# ipython and does input cache management. Finish cleanup later... 

29 

30class Logger: 

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

32 

33 def __init__(self, home_dir, logfname='Logger.log', loghead=u'', 

34 logmode='over'): 

35 

36 # this is the full ipython instance, we need some attributes from it 

37 # which won't exist until later. What a mess, clean up later... 

38 self.home_dir = home_dir 

39 

40 self.logfname = logfname 

41 self.loghead = loghead 

42 self.logmode = logmode 

43 self.logfile = None 

44 

45 # Whether to log raw or processed input 

46 self.log_raw_input = False 

47 

48 # whether to also log output 

49 self.log_output = False 

50 

51 # whether to put timestamps before each log entry 

52 self.timestamp = False 

53 

54 # activity control flags 

55 self.log_active = False 

56 

57 # logmode is a validated property 

58 def _set_mode(self,mode): 

59 if mode not in ['append','backup','global','over','rotate']: 

60 raise ValueError('invalid log mode %s given' % mode) 

61 self._logmode = mode 

62 

63 def _get_mode(self): 

64 return self._logmode 

65 

66 logmode = property(_get_mode,_set_mode) 

67 

68 def logstart(self, logfname=None, loghead=None, logmode=None, 

69 log_output=False, timestamp=False, log_raw_input=False): 

70 """Generate a new log-file with a default header. 

71 

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

73 

74 if self.logfile is not None: 

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

76 self.logfname) 

77 

78 # The parameters can override constructor defaults 

79 if logfname is not None: self.logfname = logfname 

80 if loghead is not None: self.loghead = loghead 

81 if logmode is not None: self.logmode = logmode 

82 

83 # Parameters not part of the constructor 

84 self.timestamp = timestamp 

85 self.log_output = log_output 

86 self.log_raw_input = log_raw_input 

87 

88 # init depending on the log mode requested 

89 isfile = os.path.isfile 

90 logmode = self.logmode 

91 

92 if logmode == 'append': 

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

94 

95 elif logmode == 'backup': 

96 if isfile(self.logfname): 

97 backup_logname = self.logfname+'~' 

98 # Manually remove any old backup, since os.rename may fail 

99 # under Windows. 

100 if isfile(backup_logname): 

101 os.remove(backup_logname) 

102 os.rename(self.logfname,backup_logname) 

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

104 

105 elif logmode == 'global': 

106 self.logfname = os.path.join(self.home_dir,self.logfname) 

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

108 

109 elif logmode == 'over': 

110 if isfile(self.logfname): 

111 os.remove(self.logfname) 

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

113 

114 elif logmode == 'rotate': 

115 if isfile(self.logfname): 

116 if isfile(self.logfname+'.001~'): 

117 old = glob.glob(self.logfname+'.*~') 

118 old.sort() 

119 old.reverse() 

120 for f in old: 

121 root, ext = os.path.splitext(f) 

122 num = int(ext[1:-1])+1 

123 os.rename(f, root+'.'+repr(num).zfill(3)+'~') 

124 os.rename(self.logfname, self.logfname+'.001~') 

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

126 

127 if logmode != 'append': 

128 self.logfile.write(self.loghead) 

129 

130 self.logfile.flush() 

131 self.log_active = True 

132 

133 def switch_log(self,val): 

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

135 

136 if val not in [False,True,0,1]: 

137 raise ValueError('Call switch_log ONLY with a boolean argument, ' 

138 'not with: %s' % val) 

139 

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

141 

142 if self.logfile is None: 

143 print(""" 

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

145 

146%logon/%logoff are for temporarily starting and stopping logging for a logfile 

147which already exists. But you must first start the logging process with 

148%logstart (optionally giving a logfile name).""") 

149 

150 else: 

151 if self.log_active == val: 

152 print('Logging is already',label[val]) 

153 else: 

154 print('Switching logging',label[val]) 

155 self.log_active = not self.log_active 

156 self.log_active_out = self.log_active 

157 

158 def logstate(self): 

159 """Print a status message about the logger.""" 

160 if self.logfile is None: 

161 print('Logging has not been activated.') 

162 else: 

163 state = self.log_active and 'active' or 'temporarily suspended' 

164 print('Filename :', self.logfname) 

165 print('Mode :', self.logmode) 

166 print('Output logging :', self.log_output) 

167 print('Raw input log :', self.log_raw_input) 

168 print('Timestamping :', self.timestamp) 

169 print('State :', state) 

170 

171 def log(self, line_mod, line_ori): 

172 """Write the sources to a log. 

173 

174 Inputs: 

175 

176 - line_mod: possibly modified input, such as the transformations made 

177 by input prefilters or input handlers of various kinds. This should 

178 always be valid Python. 

179 

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

181 necessarily valid Python. 

182 """ 

183 

184 # Write the log line, but decide which one according to the 

185 # log_raw_input flag, set when the log is started. 

186 if self.log_raw_input: 

187 self.log_write(line_ori) 

188 else: 

189 self.log_write(line_mod) 

190 

191 def log_write(self, data, kind='input'): 

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

193 

194 # print('data: %r' % data) # dbg 

195 if self.log_active and data: 

196 write = self.logfile.write 

197 if kind=='input': 

198 if self.timestamp: 

199 write(time.strftime('# %a, %d %b %Y %H:%M:%S\n', time.localtime())) 

200 write(data) 

201 elif kind=='output' and self.log_output: 

202 odata = u'\n'.join([u'#[Out]# %s' % s 

203 for s in data.splitlines()]) 

204 write(u'%s\n' % odata) 

205 try: 

206 self.logfile.flush() 

207 except OSError: 

208 print("Failed to flush the log file.") 

209 print( 

210 f"Please check that {self.logfname} exists and have the right permissions." 

211 ) 

212 print( 

213 "Also consider turning off the log with `%logstop` to avoid this warning." 

214 ) 

215 

216 def logstop(self): 

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

218 

219 In order to start logging again, a new logstart() call needs to be 

220 made, possibly (though not necessarily) with a new filename, mode and 

221 other options.""" 

222 

223 if self.logfile is not None: 

224 self.logfile.close() 

225 self.logfile = None 

226 else: 

227 print("Logging hadn't been started.") 

228 self.log_active = False 

229 

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

231 close_log = logstop