Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/utils/log.py: 35%

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

100 statements  

1import logging 

2import logging.config # needed when logging_config doesn't start with logging.config 

3from copy import copy 

4 

5from django.conf import settings 

6from django.core import mail 

7from django.core.mail import get_connection 

8from django.core.management.color import color_style 

9from django.utils.module_loading import import_string 

10 

11request_logger = logging.getLogger("django.request") 

12 

13# Default logging for Django. This sends an email to the site admins on every 

14# HTTP 500 error. Depending on DEBUG, all other log records are either sent to 

15# the console (DEBUG=True) or discarded (DEBUG=False) by means of the 

16# require_debug_true filter. This configuration is quoted in 

17# docs/ref/logging.txt; please amend it there if edited here. 

18DEFAULT_LOGGING = { 

19 "version": 1, 

20 "disable_existing_loggers": False, 

21 "filters": { 

22 "require_debug_false": { 

23 "()": "django.utils.log.RequireDebugFalse", 

24 }, 

25 "require_debug_true": { 

26 "()": "django.utils.log.RequireDebugTrue", 

27 }, 

28 }, 

29 "formatters": { 

30 "django.server": { 

31 "()": "django.utils.log.ServerFormatter", 

32 "format": "[{server_time}] {message}", 

33 "style": "{", 

34 } 

35 }, 

36 "handlers": { 

37 "console": { 

38 "level": "INFO", 

39 "filters": ["require_debug_true"], 

40 "class": "logging.StreamHandler", 

41 }, 

42 "django.server": { 

43 "level": "INFO", 

44 "class": "logging.StreamHandler", 

45 "formatter": "django.server", 

46 }, 

47 "mail_admins": { 

48 "level": "ERROR", 

49 "filters": ["require_debug_false"], 

50 "class": "django.utils.log.AdminEmailHandler", 

51 }, 

52 }, 

53 "loggers": { 

54 "django": { 

55 "handlers": ["console", "mail_admins"], 

56 "level": "INFO", 

57 }, 

58 "django.server": { 

59 "handlers": ["django.server"], 

60 "level": "INFO", 

61 "propagate": False, 

62 }, 

63 }, 

64} 

65 

66 

67def configure_logging(logging_config, logging_settings): 

68 if logging_config: 

69 # First find the logging configuration function ... 

70 logging_config_func = import_string(logging_config) 

71 

72 logging.config.dictConfig(DEFAULT_LOGGING) 

73 

74 # ... then invoke it with the logging settings 

75 if logging_settings: 

76 logging_config_func(logging_settings) 

77 

78 

79class AdminEmailHandler(logging.Handler): 

80 """An exception log handler that emails log entries to site admins. 

81 

82 If the request is passed as the first argument to the log record, 

83 request data will be provided in the email report. 

84 """ 

85 

86 def __init__(self, include_html=False, email_backend=None, reporter_class=None): 

87 super().__init__() 

88 self.include_html = include_html 

89 self.email_backend = email_backend 

90 self.reporter_class = import_string( 

91 reporter_class or settings.DEFAULT_EXCEPTION_REPORTER 

92 ) 

93 

94 def emit(self, record): 

95 # Early return when no email will be sent. 

96 if ( 

97 not settings.ADMINS 

98 # Method not overridden. 

99 and self.send_mail.__func__ is AdminEmailHandler.send_mail 

100 ): 

101 return 

102 try: 

103 request = record.request 

104 subject = "%s (%s IP): %s" % ( 

105 record.levelname, 

106 ( 

107 "internal" 

108 if request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS 

109 else "EXTERNAL" 

110 ), 

111 record.getMessage(), 

112 ) 

113 except Exception: 

114 subject = "%s: %s" % (record.levelname, record.getMessage()) 

115 request = None 

116 subject = self.format_subject(subject) 

117 

118 # Since we add a nicely formatted traceback on our own, create a copy 

119 # of the log record without the exception data. 

120 no_exc_record = copy(record) 

121 no_exc_record.exc_info = None 

122 no_exc_record.exc_text = None 

123 

124 if record.exc_info: 

125 exc_info = record.exc_info 

126 else: 

127 exc_info = (None, record.getMessage(), None) 

128 

129 reporter = self.reporter_class(request, is_email=True, *exc_info) 

130 message = "%s\n\n%s" % ( 

131 self.format(no_exc_record), 

132 reporter.get_traceback_text(), 

133 ) 

134 html_message = reporter.get_traceback_html() if self.include_html else None 

135 self.send_mail(subject, message, fail_silently=True, html_message=html_message) 

136 

137 def send_mail(self, subject, message, *args, **kwargs): 

138 mail.mail_admins( 

139 subject, message, *args, connection=self.connection(), **kwargs 

140 ) 

141 

142 def connection(self): 

143 return get_connection(backend=self.email_backend, fail_silently=True) 

144 

145 def format_subject(self, subject): 

146 """ 

147 Escape CR and LF characters. 

148 """ 

149 return subject.replace("\n", "\\n").replace("\r", "\\r") 

150 

151 

152class CallbackFilter(logging.Filter): 

153 """ 

154 A logging filter that checks the return value of a given callable (which 

155 takes the record-to-be-logged as its only parameter) to decide whether to 

156 log a record. 

157 """ 

158 

159 def __init__(self, callback): 

160 self.callback = callback 

161 

162 def filter(self, record): 

163 if self.callback(record): 

164 return 1 

165 return 0 

166 

167 

168class RequireDebugFalse(logging.Filter): 

169 def filter(self, record): 

170 return not settings.DEBUG 

171 

172 

173class RequireDebugTrue(logging.Filter): 

174 def filter(self, record): 

175 return settings.DEBUG 

176 

177 

178class ServerFormatter(logging.Formatter): 

179 default_time_format = "%d/%b/%Y %H:%M:%S" 

180 

181 def __init__(self, *args, **kwargs): 

182 self.style = color_style() 

183 super().__init__(*args, **kwargs) 

184 

185 def format(self, record): 

186 msg = record.msg 

187 status_code = getattr(record, "status_code", None) 

188 

189 if status_code: 

190 if 200 <= status_code < 300: 

191 # Put 2XX first, since it should be the common case 

192 msg = self.style.HTTP_SUCCESS(msg) 

193 elif 100 <= status_code < 200: 

194 msg = self.style.HTTP_INFO(msg) 

195 elif status_code == 304: 

196 msg = self.style.HTTP_NOT_MODIFIED(msg) 

197 elif 300 <= status_code < 400: 

198 msg = self.style.HTTP_REDIRECT(msg) 

199 elif status_code == 404: 

200 msg = self.style.HTTP_NOT_FOUND(msg) 

201 elif 400 <= status_code < 500: 

202 msg = self.style.HTTP_BAD_REQUEST(msg) 

203 else: 

204 # Any 5XX, or any other status code 

205 msg = self.style.HTTP_SERVER_ERROR(msg) 

206 

207 if self.uses_server_time() and not hasattr(record, "server_time"): 

208 record.server_time = self.formatTime(record, self.datefmt) 

209 

210 record.msg = msg 

211 return super().format(record) 

212 

213 def uses_server_time(self): 

214 return self._fmt.find("{server_time}") >= 0 

215 

216 

217def log_response( 

218 message, 

219 *args, 

220 response=None, 

221 request=None, 

222 logger=request_logger, 

223 level=None, 

224 exception=None, 

225): 

226 """ 

227 Log errors based on HttpResponse status. 

228 

229 Log 5xx responses as errors and 4xx responses as warnings (unless a level 

230 is given as a keyword argument). The HttpResponse status_code and the 

231 request are passed to the logger's extra parameter. 

232 """ 

233 # Check if the response has already been logged. Multiple requests to log 

234 # the same response can be received in some cases, e.g., when the 

235 # response is the result of an exception and is logged when the exception 

236 # is caught, to record the exception. 

237 if getattr(response, "_has_been_logged", False): 

238 return 

239 

240 if level is None: 

241 if response.status_code >= 500: 

242 level = "error" 

243 elif response.status_code >= 400: 

244 level = "warning" 

245 else: 

246 level = "info" 

247 

248 getattr(logger, level)( 

249 message, 

250 *args, 

251 extra={ 

252 "status_code": response.status_code, 

253 "request": request, 

254 }, 

255 exc_info=exception, 

256 ) 

257 response._has_been_logged = True