Merge lp:~canonical-isd-hackers/canonical-identity-provider/log-request into lp:canonical-identity-provider/release

Proposed by David Owen
Status: Merged
Approved by: Simon Davy
Approved revision: no longer in the source branch.
Merged at revision: 245
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/log-request
Merge into: lp:canonical-identity-provider/release
Diff against target: 273 lines (+111/-103)
3 files modified
identityprovider/middleware/exception.py (+3/-96)
identityprovider/middleware/util.py (+101/-0)
identityprovider/tests/test_middleware.py (+7/-7)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/log-request
Reviewer Review Type Date Requested Status
Canonical ISD hackers Pending
Review via email: mp+85969@code.launchpad.net

Commit message

Extracted log_request and helpers from process_exception into new util module.

Description of the change

process_exception customized the behavior of ExceptionReporter by monkey-patching two method references. This patch instead overrides the customized method through inheritance, and no monkey-patching is needed.

Other than that, this simply moves code into the new util module and slightly renames some things.

To post a comment you must log in.
Revision history for this message
Simon Davy (bloodearnest) wrote :

LGTM

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/middleware/exception.py'
2--- identityprovider/middleware/exception.py 2011-07-06 18:09:03 +0000
3+++ identityprovider/middleware/exception.py 2011-12-15 22:40:29 +0000
4@@ -1,18 +1,10 @@
5-# Copyright 2010 Canonical Ltd. This software is licensed under the
6+# Copyright 2011 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9-import logging
10-import re
11 import sys
12 import traceback
13
14-from django.conf import settings
15-from django.db import connection
16-from django.views import debug
17-from django.views.debug import ExceptionReporter
18-
19-debug.HIDDEN_SETTINGS = re.compile(
20- 'SECRET|PASSWORD|PROFANITIES_LIST|PRIVATE|secret|password|private')
21+from .util import log_request
22
23
24 class ConsoleExceptionMiddleware(object):
25@@ -22,90 +14,5 @@
26
27 class LogExceptionMiddleware(object):
28
29- def get_reporter(self, request, *exc_info):
30- safe = self.sanitize_request(request)
31- reporter = ExceptionReporter(safe, *exc_info)
32- reporter.get_traceback_frames = self.get_traceback_frames
33- self._get_lines_from_file = reporter._get_lines_from_file
34- return reporter
35-
36 def process_exception(self, request, exception):
37- """Log the traceback, so that it can be kept in the oops file"""
38- reporter = self.get_reporter(request, *sys.exc_info())
39- template_debug = settings.TEMPLATE_DEBUG
40- settings.TEMPLATE_DEBUG = True
41- try:
42- logging.exception('Unhandled exception in application')
43- logging.warn(reporter.get_traceback_html())
44- logging.warn(traceback.format_exc())
45- for query in connection.queries:
46- logging.warn("time: %(time)s sql: %(sql)s" % query)
47- finally:
48- settings.TEMPLATE_DEBUG = template_debug
49-
50- def sanitize_request(self, request):
51- """Remove sensitive from the request before it is displayed"""
52- dup_post = request.POST.copy()
53- for key in request.POST.iterkeys():
54- if debug.HIDDEN_SETTINGS.search(key):
55- dup_post[key] = '********'
56- request.POST = dup_post
57- dup_get = request.GET.copy()
58- for key in request.GET.iterkeys():
59- if debug.HIDDEN_SETTINGS.search(key):
60- dup_get[key] = '********'
61- request.GET = dup_get
62- return request
63-
64- def sanitize_vars(self, items):
65- sanitized = []
66- for item in items:
67- if debug.HIDDEN_SETTINGS.search(item[0]):
68- sanitized.append((item[0], '********'))
69- else:
70- sanitized.append(item)
71- return sanitized
72-
73- # This function was taken from the django project.
74- # Please see the license file in the third-party/django directory.
75- def get_traceback_frames(self):
76- frames = []
77- tb = sys.exc_info()[2]
78- while tb is not None:
79- # support for __traceback_hide__ which is used by a few libraries
80- # to hide internal frames.
81- if tb.tb_frame.f_locals.get('__traceback_hide__'):
82- tb = tb.tb_next
83- continue
84- filename = tb.tb_frame.f_code.co_filename
85- function = tb.tb_frame.f_code.co_name
86- lineno = tb.tb_lineno - 1
87- loader = tb.tb_frame.f_globals.get('__loader__')
88- module_name = tb.tb_frame.f_globals.get('__name__')
89- pre_context_lineno, pre_context, context_line, post_context = \
90- self._get_lines_from_file(filename, lineno, 7, loader,
91- module_name)
92- if pre_context_lineno is not None:
93- frames.append({
94- 'tb': tb,
95- 'filename': filename,
96- 'function': function,
97- 'lineno': lineno + 1,
98- 'vars': self.sanitize_vars(tb.tb_frame.f_locals.items()),
99- 'id': id(tb),
100- 'pre_context': pre_context,
101- 'context_line': context_line,
102- 'post_context': post_context,
103- 'pre_context_lineno': pre_context_lineno + 1,
104- })
105- tb = tb.tb_next
106-
107- if not frames:
108- frames = [{
109- 'filename': '<unknown>',
110- 'function': '?',
111- 'lineno': '?',
112- 'context_line': '???',
113- }]
114-
115- return frames
116+ log_request(request)
117
118=== added file 'identityprovider/middleware/util.py'
119--- identityprovider/middleware/util.py 1970-01-01 00:00:00 +0000
120+++ identityprovider/middleware/util.py 2011-12-15 22:40:29 +0000
121@@ -0,0 +1,101 @@
122+# Copyright 2011 Canonical Ltd. This software is licensed under the
123+# GNU Affero General Public License version 3 (see the file LICENSE).
124+
125+import logging
126+import re
127+import sys
128+import traceback
129+
130+from django.conf import settings
131+from django.db import connection
132+from django.views import debug
133+
134+debug.HIDDEN_SETTINGS = re.compile(
135+ 'SECRET|PASSWORD|PROFANITIES_LIST|PRIVATE|secret|password|private')
136+
137+
138+def _sanitize_dict(dirty):
139+ clean = dirty.copy()
140+ for key in dirty.iterkeys():
141+ if debug.HIDDEN_SETTINGS.search(key):
142+ clean[key] = '********'
143+ return clean
144+
145+def _sanitize_request(request):
146+ """Remove sensitive information from the request before it is
147+ displayed."""
148+ request.GET = _sanitize_dict(request.GET)
149+ request.POST = _sanitize_dict(request.POST)
150+ return request
151+
152+def _sanitize_vars(items):
153+ sanitized = []
154+ for item in items:
155+ if debug.HIDDEN_SETTINGS.search(item[0]):
156+ sanitized.append((item[0], '********'))
157+ else:
158+ sanitized.append(item)
159+ return sanitized
160+
161+
162+class _ExceptionReporter(debug.ExceptionReporter):
163+
164+ # This function was taken from the django project.
165+ # Please see the license file in the third-party/django directory.
166+ def get_traceback_frames(self):
167+ frames = []
168+ tb = sys.exc_info()[2]
169+ while tb is not None:
170+ # support for __traceback_hide__ which is used by a few libraries
171+ # to hide internal frames.
172+ if tb.tb_frame.f_locals.get('__traceback_hide__'):
173+ tb = tb.tb_next
174+ continue
175+ filename = tb.tb_frame.f_code.co_filename
176+ function = tb.tb_frame.f_code.co_name
177+ lineno = tb.tb_lineno - 1
178+ loader = tb.tb_frame.f_globals.get('__loader__')
179+ module_name = tb.tb_frame.f_globals.get('__name__')
180+ pre_context_lineno, pre_context, context_line, post_context = \
181+ self._get_lines_from_file(filename, lineno, 7, loader,
182+ module_name)
183+ if pre_context_lineno is not None:
184+ frames.append({
185+ 'tb': tb,
186+ 'filename': filename,
187+ 'function': function,
188+ 'lineno': lineno + 1,
189+ 'vars': _sanitize_vars(tb.tb_frame.f_locals.items()),
190+ 'id': id(tb),
191+ 'pre_context': pre_context,
192+ 'context_line': context_line,
193+ 'post_context': post_context,
194+ 'pre_context_lineno': pre_context_lineno + 1,
195+ })
196+ tb = tb.tb_next
197+
198+ if not frames:
199+ frames = [{
200+ 'filename': '<unknown>',
201+ 'function': '?',
202+ 'lineno': '?',
203+ 'context_line': '???',
204+ }]
205+
206+ return frames
207+
208+
209+def log_request(request):
210+ """Log the request, so that it can be kept in the oops file"""
211+ request = _sanitize_request(request)
212+ reporter = _ExceptionReporter(request, *sys.exc_info())
213+ template_debug = settings.TEMPLATE_DEBUG
214+ settings.TEMPLATE_DEBUG = True
215+ try:
216+ logging.exception('Unhandled exception in application')
217+ logging.warn(reporter.get_traceback_html())
218+ logging.warn(traceback.format_exc())
219+ for query in connection.queries:
220+ logging.warn("time: %(time)s sql: %(sql)s" % query)
221+ finally:
222+ settings.TEMPLATE_DEBUG = template_debug
223
224=== modified file 'identityprovider/tests/test_middleware.py'
225--- identityprovider/tests/test_middleware.py 2011-08-31 13:37:21 +0000
226+++ identityprovider/tests/test_middleware.py 2011-12-15 22:40:29 +0000
227@@ -13,7 +13,7 @@
228
229 from identityprovider.tests.utils import MockRequest
230 from identityprovider.models import Account, EmailAddress, OpenIDRPConfig
231-from identityprovider.middleware.exception import LogExceptionMiddleware
232+from identityprovider.middleware import exception, util as middleware_util
233 from identityprovider.middleware.useraccount import (
234 UserAccountConversionMiddleware)
235
236@@ -107,7 +107,7 @@
237 request.GET['get_private'] = self.GET_PRIVATE
238 request.GET['get_public'] = self.GET_PUBLIC
239 self.request = request
240- self.middleware = LogExceptionMiddleware()
241+ self.middleware = exception.LogExceptionMiddleware()
242 settings.PRIVATE_SETTING = self.PRIVATE_SETTING
243 settings.PUBLIC_SETTING = self.PUBLIC_SETTING
244
245@@ -115,8 +115,8 @@
246 try:
247 x = 2 / 0
248 except:
249- reporter = self.middleware.get_reporter(self.request,
250- *sys.exc_info())
251+ reporter = middleware_util._ExceptionReporter(self.request,
252+ *sys.exc_info())
253 html = reporter.get_traceback_html()
254 self.assertFalse(self.PRIVATE_SETTING in html)
255 self.assertTrue(self.PUBLIC_SETTING in html)
256@@ -126,14 +126,14 @@
257 FRAME_PASSWORD = self.FRAME_VAR
258 public_variable = 2 / 0
259 except:
260- reporter = self.middleware.get_reporter(self.request,
261- *sys.exc_info())
262+ reporter = middleware_util._ExceptionReporter(self.request,
263+ *sys.exc_info())
264 html = reporter.get_traceback_html()
265 self.assertFalse(self.FRAME_VAR in html)
266 self.assertTrue('public_variable' in html)
267
268 def test_request_sanitization(self):
269- req = self.middleware.sanitize_request(self.request)
270+ req = middleware_util._sanitize_request(self.request)
271 # Make sure sensitive data is no longer in the request
272 self.assertFalse(self.POST_PASSWORD in req.POST.values())
273 self.assertFalse(self.POST_SECRET in req.POST.values())