Merge lp:~gary/launchpad/bug838869 into lp:launchpad
- bug838869
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Gary Poster |
Approved revision: | no longer in the source branch. |
Merged at revision: | 13857 |
Proposed branch: | lp:~gary/launchpad/bug838869 |
Merge into: | lp:launchpad |
Diff against target: |
613 lines (+439/-41) 4 files modified
lib/canonical/launchpad/webapp/adapter.py (+120/-37) lib/canonical/launchpad/webapp/tests/test_statementtracer.py (+316/-0) lib/lp/services/profile/profile.py (+2/-2) lib/lp/services/profile/tests.py (+1/-2) |
To merge this branch: | bzr merge lp:~gary/launchpad/bug838869 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
j.c.sackett (community) | Approve | ||
Review via email: mp+73695@code.launchpad.net |
Commit message
[r=jcsackett][bug=838869] Add tests for Launchpad SQL logger; add a more surgical SQL logger for outside of a request than what we had before; and add the ability to conditionally request tracebacks in the SQL logger.
Description of the change
This branch does three things.
- It adds a helper for tracking queries piecemeal in tests and make harness, SQLLogger. This is different than the QueryCollector in lib/lp/
- It adds the ability to conditionally collect stacktraces, and renames the associated function from `start_
- It adds tests for the logger, which had not existed at all as far as I could find. I include tests for the functionality I added as well as existing functionality. This is bug 838869.
Lint is happy.
Reasonable QA will be to go to https:/
Thank you.
Robert Collins (lifeless) wrote : | # |
There was/is a non browser query collector in testing/. This may be
duplicative.
On 3/09/2011 4:34 AM, <email address hidden> wrote:
The proposal to merge lp:~gary/launchpad/bug838869 into lp:launchpad has
been updated.
Status: Approved => Merged
For more details, see:
https:/
--
https:/
Your team Launchpad code revie...
--
launchpad-reviews mailing list
<email address hidden>
https:/
Preview Diff
1 | === modified file 'lib/canonical/launchpad/webapp/adapter.py' |
2 | --- lib/canonical/launchpad/webapp/adapter.py 2011-08-26 13:13:20 +0000 |
3 | +++ lib/canonical/launchpad/webapp/adapter.py 2011-09-02 16:31:40 +0000 |
4 | @@ -6,13 +6,16 @@ |
5 | |
6 | __metaclass__ = type |
7 | |
8 | +from functools import partial |
9 | import logging |
10 | import os |
11 | import re |
12 | +import StringIO |
13 | import sys |
14 | import thread |
15 | import threading |
16 | from time import time |
17 | +import traceback |
18 | import warnings |
19 | |
20 | import psycopg2 |
21 | @@ -79,6 +82,7 @@ |
22 | ) |
23 | from lp.services.stacktrace import ( |
24 | extract_stack, |
25 | + extract_tb, |
26 | print_list, |
27 | ) |
28 | |
29 | @@ -91,9 +95,11 @@ |
30 | 'get_request_start_time', |
31 | 'get_request_duration', |
32 | 'get_store_name', |
33 | + 'print_queries', |
34 | 'soft_timeout_expired', |
35 | - 'start_sql_traceback_logging', |
36 | - 'stop_sql_traceback_logging', |
37 | + 'SQLLogger', |
38 | + 'start_sql_logging', |
39 | + 'stop_sql_logging', |
40 | 'StoreSelector', |
41 | ] |
42 | |
43 | @@ -202,7 +208,9 @@ |
44 | warnings.warn('clear_request_started() called outside of a request', |
45 | stacklevel=2) |
46 | _local.request_start_time = None |
47 | - _local.sql_trace = None |
48 | + _local.sql_logging = None |
49 | + _local.sql_logging_start = None |
50 | + _local.sql_logging_tracebacks_if = None |
51 | request = get_current_browser_request() |
52 | set_request_timeline(request, Timeline()) |
53 | if getattr(_local, 'commit_logger', None) is not None: |
54 | @@ -351,18 +359,75 @@ |
55 | return True |
56 | |
57 | |
58 | -def start_sql_traceback_logging(): |
59 | - """Set the sql traceback data logging for the current request.""" |
60 | - _local.sql_trace = [] |
61 | - |
62 | - |
63 | -def stop_sql_traceback_logging(): |
64 | - """Stop the sql traceback data logging and return the result.""" |
65 | - result = getattr(_local, 'sql_trace', None) |
66 | - _local.sql_trace = None |
67 | +def start_sql_logging(tracebacks_if=False): |
68 | + """Turn the sql data logging on.""" |
69 | + if getattr(_local, 'sql_logging', None) is not None: |
70 | + warnings.warn('SQL logging already started') |
71 | + return |
72 | + _local.sql_logging_tracebacks_if = tracebacks_if |
73 | + _local.sql_logging = [] |
74 | + _local.sql_logging_start = int(time() * 1000) |
75 | + |
76 | + |
77 | +def stop_sql_logging(): |
78 | + """Turn off the sql data logging and return the result.""" |
79 | + result = getattr(_local, 'sql_logging', None) |
80 | + _local.sql_logging_tracebacks_if = None |
81 | + _local.sql_logging = None |
82 | + _local.sql_logging_start = None |
83 | + if result is None: |
84 | + warnings.warn('SQL logging not started') |
85 | return result |
86 | |
87 | |
88 | +class SQLLogger: |
89 | + |
90 | + def __init__(self, tracebacks_if=False): |
91 | + self.tracebacks_if = tracebacks_if |
92 | + |
93 | + queries = None |
94 | + |
95 | + def __enter__(self): |
96 | + self.queries = None |
97 | + start_sql_logging(self.tracebacks_if) |
98 | + |
99 | + def __exit__(self, exc_type, exc_value, tb): |
100 | + self.queries = stop_sql_logging() |
101 | + |
102 | + def __str__(self): |
103 | + if self.queries is None: |
104 | + return '(no queries)' |
105 | + else: |
106 | + out = StringIO.StringIO() |
107 | + print_queries(self.queries, file=out) |
108 | + return out.getvalue() |
109 | + |
110 | + |
111 | +def print_queries(queries, file=None): |
112 | + if file is None: |
113 | + file = sys.stdout |
114 | + for query in queries: |
115 | + stack = query['stack'] |
116 | + if stack is not None: |
117 | + exception = query['exception'] |
118 | + if exception is not None: |
119 | + file.write( |
120 | + 'Error when determining whether to generate a ' |
121 | + 'stacktrace.\n') |
122 | + file.write('Traceback (most recent call last):\n') |
123 | + print_list(stack, file) |
124 | + if exception is not None: |
125 | + lines = traceback.format_exception_only(*exception) |
126 | + file.write(' '.join(lines)) |
127 | + file.write("." * 70 + "\n") |
128 | + sql = query['sql'] |
129 | + if sql is not None: |
130 | + file.write('%d-%d@%s %s\n' % sql) |
131 | + else: |
132 | + file.write('(no SQL recorded)\n') |
133 | + file.write("-" * 70 + "\n") |
134 | + |
135 | + |
136 | # ---- Prevent database access in the main thread of the app server |
137 | |
138 | class StormAccessFromMainThread(Exception): |
139 | @@ -625,23 +690,14 @@ |
140 | class LaunchpadStatementTracer: |
141 | """Storm tracer class to log executed statements.""" |
142 | |
143 | + _normalize_whitespace = partial(re.compile('\s+').sub, ' ') |
144 | + |
145 | def __init__(self): |
146 | self._debug_sql = bool(os.environ.get('LP_DEBUG_SQL')) |
147 | self._debug_sql_extra = bool(os.environ.get('LP_DEBUG_SQL_EXTRA')) |
148 | |
149 | def connection_raw_execute(self, connection, raw_cursor, |
150 | statement, params): |
151 | - sql_trace = getattr(_local, 'sql_trace', None) |
152 | - if sql_trace is not None or self._debug_sql_extra: |
153 | - # Gather data for the [start|stop]_sql_traceback_logging |
154 | - # feature, as exposed by ++profile++sqltrace, and/or for stderr, |
155 | - # as exposed by LP_DEBUG_SQL_EXTRA. |
156 | - stack = extract_stack() |
157 | - if sql_trace is not None: |
158 | - sql_trace.append(dict(stack=stack, sql=None)) |
159 | - if self._debug_sql_extra: |
160 | - print_list(stack) |
161 | - sys.stderr.write("." * 70 + "\n") |
162 | statement_to_log = statement |
163 | if params: |
164 | # There are some bind parameters so we want to insert them into |
165 | @@ -656,18 +712,43 @@ |
166 | param_strings = [repr(p) if isinstance(p, basestring) else p |
167 | for p in query_params] |
168 | statement_to_log = quoted_statement % tuple(param_strings) |
169 | + # Record traceback to log, if requested. |
170 | + print_traceback = self._debug_sql_extra |
171 | + log_sql = getattr(_local, 'sql_logging', None) |
172 | + log_traceback = False |
173 | + if log_sql is not None: |
174 | + log_sql.append(dict(stack=None, sql=None, exception=None)) |
175 | + conditional = getattr(_local, 'sql_logging_tracebacks_if', None) |
176 | + if callable(conditional): |
177 | + try: |
178 | + log_traceback = conditional( |
179 | + self._normalize_whitespace( |
180 | + statement_to_log.strip()).upper()) |
181 | + except (MemoryError, SystemExit, KeyboardInterrupt): |
182 | + raise |
183 | + except: |
184 | + exc_type, exc_value, tb = sys.exc_info() |
185 | + log_sql[-1]['exception'] = (exc_type, exc_value) |
186 | + log_sql[-1]['stack'] = extract_tb(tb) |
187 | + else: |
188 | + log_traceback = bool(conditional) |
189 | + if print_traceback or log_traceback: |
190 | + stack = extract_stack() |
191 | + if log_traceback: |
192 | + log_sql[-1]['stack'] = stack |
193 | + if print_traceback: |
194 | + print_list(stack) |
195 | + sys.stderr.write("." * 70 + "\n") |
196 | # store the last executed statement as an attribute on the current |
197 | # thread |
198 | threading.currentThread().lp_last_sql_statement = statement |
199 | request_starttime = getattr(_local, 'request_start_time', None) |
200 | if request_starttime is None: |
201 | - if (sql_trace is not None or |
202 | - self._debug_sql or |
203 | - self._debug_sql_extra): |
204 | + if print_traceback or self._debug_sql or log_sql is not None: |
205 | # Stash some information for logging at the end of the |
206 | - # request. |
207 | + # SQL execution. |
208 | connection._lp_statement_info = ( |
209 | - time(), |
210 | + int(time() * 1000), |
211 | 'SQL-%s' % connection._database.name, |
212 | statement_to_log) |
213 | return |
214 | @@ -682,24 +763,26 @@ |
215 | # action may be None if the tracer was installed after the |
216 | # statement was submitted. |
217 | action.finish() |
218 | - # Do data reporting for the [start|stop]_sql_traceback_logging |
219 | - # feature, as exposed by ++profile++sqltrace, and/or for stderr, |
220 | - # as exposed by LP_DEBUG_SQL_EXTRA and LP_DEBUG_SQL. |
221 | - sql_trace = getattr(_local, 'sql_trace', None) |
222 | - if sql_trace is not None or self._debug_sql or self._debug_sql_extra: |
223 | + log_sql = getattr(_local, 'sql_logging', None) |
224 | + if log_sql is not None or self._debug_sql or self._debug_sql_extra: |
225 | data = None |
226 | if action is not None: |
227 | data = action.logTuple() |
228 | else: |
229 | info = getattr(connection, '_lp_statement_info', None) |
230 | if info is not None: |
231 | + stop = int(time() * 1000) |
232 | start, dbname, statement = info |
233 | + logging_start = ( |
234 | + getattr(_local, 'sql_logging_start', None) or start) |
235 | # Times are in milliseconds, to mirror actions. |
236 | - duration = int((time() - start) * 1000) |
237 | - data = (0, duration, dbname, statement) |
238 | + start = start - logging_start |
239 | + stop = stop - logging_start |
240 | + data = (start, stop, dbname, statement) |
241 | + connection._lp_statement_info = None |
242 | if data is not None: |
243 | - if sql_trace and sql_trace[-1]['sql'] is None: |
244 | - sql_trace[-1]['sql'] = data |
245 | + if log_sql and log_sql[-1]['sql'] is None: |
246 | + log_sql[-1]['sql'] = data |
247 | if self._debug_sql or self._debug_sql_extra: |
248 | sys.stderr.write('%d-%d@%s %s\n' % data) |
249 | sys.stderr.write("-" * 70 + "\n") |
250 | |
251 | === added file 'lib/canonical/launchpad/webapp/tests/test_statementtracer.py' |
252 | --- lib/canonical/launchpad/webapp/tests/test_statementtracer.py 1970-01-01 00:00:00 +0000 |
253 | +++ lib/canonical/launchpad/webapp/tests/test_statementtracer.py 2011-09-02 16:31:40 +0000 |
254 | @@ -0,0 +1,316 @@ |
255 | +# Copyright 2011 Canonical Ltd. This software is licensed under the |
256 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
257 | + |
258 | +"""Tests for the LaunchpadStatementTracer.""" |
259 | + |
260 | +__metaclass__ = type |
261 | + |
262 | +from contextlib import contextmanager |
263 | +import StringIO |
264 | +import sys |
265 | + |
266 | +from lp.testing import ( |
267 | + person_logged_in, |
268 | + TestCase, |
269 | + TestCaseWithFactory, |
270 | + ) |
271 | +from lp.services.osutils import override_environ |
272 | +from lp.services.timeline.requesttimeline import get_request_timeline |
273 | +from canonical.launchpad.webapp import adapter as da |
274 | +from canonical.lazr.utils import get_current_browser_request |
275 | +from canonical.testing import DatabaseFunctionalLayer |
276 | + |
277 | + |
278 | +@contextmanager |
279 | +def stdout(): |
280 | + file = StringIO.StringIO() |
281 | + original = sys.stdout |
282 | + sys.stdout = file |
283 | + try: |
284 | + yield file |
285 | + finally: |
286 | + sys.stdout = original |
287 | + |
288 | + |
289 | +@contextmanager |
290 | +def stderr(): |
291 | + file = StringIO.StringIO() |
292 | + original = sys.stderr |
293 | + sys.stderr = file |
294 | + try: |
295 | + yield file |
296 | + finally: |
297 | + sys.stderr = original |
298 | + |
299 | + |
300 | +class StubTime: |
301 | + |
302 | + time = 1000.0 |
303 | + |
304 | + def __call__(self): |
305 | + result = self.time |
306 | + self.time += 0.001 |
307 | + return result |
308 | + |
309 | + |
310 | +class StubConnection: |
311 | + |
312 | + def __init__(self): |
313 | + self._database = type('StubDatabase', (), dict(name='stub-database')) |
314 | + |
315 | + |
316 | +class TestLoggingOutsideOfRequest(TestCase): |
317 | + |
318 | + def setUp(self): |
319 | + super(TestLoggingOutsideOfRequest, self).setUp() |
320 | + self.connection = StubConnection() |
321 | + original_time = da.time |
322 | + self.addCleanup(setattr, da, 'time', original_time) |
323 | + da.time = StubTime() |
324 | + |
325 | + def execute(self, statement=None, params=None, **environ): |
326 | + with override_environ(**environ): |
327 | + tracer = da.LaunchpadStatementTracer() |
328 | + if statement is None: |
329 | + statement = 'SELECT * FROM bar WHERE bing = 42' |
330 | + tracer.connection_raw_execute( |
331 | + self.connection, None, statement, params) |
332 | + tracer.connection_raw_execute_success( |
333 | + self.connection, None, statement, params) |
334 | + |
335 | + def test_no_logging(self): |
336 | + with stderr() as file: |
337 | + self.execute() |
338 | + self.assertEqual('', file.getvalue()) |
339 | + |
340 | + def test_stderr(self): |
341 | + with stderr() as file: |
342 | + self.execute(LP_DEBUG_SQL='1') |
343 | + self.assertEqual( |
344 | + '0-1@SQL-stub-database SELECT * FROM bar WHERE bing = 42\n' + |
345 | + "-" * 70 + "\n", |
346 | + file.getvalue()) |
347 | + |
348 | + def test_stderr_with_stacktrace(self): |
349 | + with stderr() as file: |
350 | + self.execute(LP_DEBUG_SQL_EXTRA='1') |
351 | + self.assertStartsWith( |
352 | + file.getvalue(), |
353 | + ' File "') |
354 | + self.assertEndsWith( |
355 | + file.getvalue(), |
356 | + "." * 70 + "\n" + |
357 | + '0-1@SQL-stub-database SELECT * FROM bar WHERE bing = 42\n' + |
358 | + "-" * 70 + "\n") |
359 | + |
360 | + def test_data_logging(self): |
361 | + da.start_sql_logging() |
362 | + with stderr() as file: |
363 | + self.execute() |
364 | + self.assertEqual('', file.getvalue()) |
365 | + result = da.stop_sql_logging() |
366 | + self.assertEqual(1, len(result)) |
367 | + self.assertIs(None, result[0]['stack']) |
368 | + self.assertIs(None, result[0]['exception']) |
369 | + self.assertEqual( |
370 | + (1, 2, 'SQL-stub-database', 'SELECT * FROM bar WHERE bing = 42'), |
371 | + result[0]['sql']) |
372 | + |
373 | + def test_data_logging_with_stacktrace(self): |
374 | + da.start_sql_logging(tracebacks_if=True) |
375 | + with stderr() as file: |
376 | + self.execute() |
377 | + self.assertEqual('', file.getvalue()) |
378 | + result = da.stop_sql_logging() |
379 | + self.assertEqual(1, len(result)) |
380 | + self.assertIsNot(None, result[0]['stack']) |
381 | + self.assertIs(None, result[0]['exception']) |
382 | + self.assertEqual( |
383 | + (1, 2, 'SQL-stub-database', 'SELECT * FROM bar WHERE bing = 42'), |
384 | + result[0]['sql']) |
385 | + |
386 | + def test_data_logging_with_conditional_stacktrace(self): |
387 | + # Conditions must be normalized to uppercase. |
388 | + da.start_sql_logging(tracebacks_if=lambda sql: 'KUMQUAT' in sql) |
389 | + with stderr() as file: |
390 | + self.execute() |
391 | + self.execute(statement='SELECT * FROM kumquat WHERE bing = 42') |
392 | + self.assertEqual('', file.getvalue()) |
393 | + result = da.stop_sql_logging() |
394 | + self.assertEqual(2, len(result)) |
395 | + self.assertIs(None, result[0]['stack']) |
396 | + self.assertIsNot(None, result[1]['stack']) |
397 | + |
398 | + def test_data_logging_with_conditional_stacktrace_normalized_whitespace( |
399 | + self): |
400 | + # The whitespace in the SQL is normalized |
401 | + da.start_sql_logging( |
402 | + tracebacks_if=lambda sql: 'FROM KUMQUAT WHERE' in sql) |
403 | + self.execute( |
404 | + statement='SELECT * FROM kumquat \nWHERE bing = 42') |
405 | + result = da.stop_sql_logging() |
406 | + self.assertEqual(1, len(result)) |
407 | + self.assertIsNot(None, result[0]['stack']) |
408 | + |
409 | + def test_data_logging_with_broken_conditional_stacktrace(self): |
410 | + error = ValueError('rutebega') |
411 | + |
412 | + def ow(sql): |
413 | + raise error |
414 | + da.start_sql_logging(tracebacks_if=ow) |
415 | + with stderr() as file: |
416 | + self.execute() |
417 | + self.assertEqual('', file.getvalue()) |
418 | + result = da.stop_sql_logging() |
419 | + self.assertEqual(1, len(result)) |
420 | + self.assertIsNot(None, result[0]['stack']) |
421 | + self.assertEqual((ValueError, error), result[0]['exception']) |
422 | + self.assertEqual( |
423 | + (1, 2, 'SQL-stub-database', 'SELECT * FROM bar WHERE bing = 42'), |
424 | + result[0]['sql']) |
425 | + |
426 | + def test_print_queries_with_tracebacks(self): |
427 | + da.start_sql_logging(tracebacks_if=True) |
428 | + self.execute() |
429 | + result = da.stop_sql_logging() |
430 | + with stdout() as file: |
431 | + da.print_queries(result) |
432 | + self.assertStartsWith( |
433 | + file.getvalue(), |
434 | + ' File "') |
435 | + self.assertEndsWith( |
436 | + file.getvalue(), |
437 | + "." * 70 + "\n" + |
438 | + '1-2@SQL-stub-database SELECT * FROM bar WHERE bing = 42\n' + |
439 | + "-" * 70 + "\n") |
440 | + |
441 | + def test_print_queries_without_tracebacks(self): |
442 | + da.start_sql_logging() |
443 | + self.execute() |
444 | + result = da.stop_sql_logging() |
445 | + with stdout() as file: |
446 | + da.print_queries(result) |
447 | + self.assertEqual( |
448 | + '1-2@SQL-stub-database SELECT * FROM bar WHERE bing = 42\n' + |
449 | + "-" * 70 + "\n", |
450 | + file.getvalue()) |
451 | + |
452 | + def test_print_queries_with_exceptions(self): |
453 | + def ow(sql): |
454 | + raise ValueError('rutebega') |
455 | + da.start_sql_logging(tracebacks_if=ow) |
456 | + self.execute() |
457 | + result = da.stop_sql_logging() |
458 | + with stdout() as file: |
459 | + da.print_queries(result) |
460 | + self.assertStartsWith( |
461 | + file.getvalue(), |
462 | + 'Error when determining whether to generate a stacktrace.\n' + |
463 | + 'Traceback (most recent call last):\n' + |
464 | + ' File "') |
465 | + self.assertEndsWith( |
466 | + file.getvalue(), |
467 | + "ValueError: rutebega\n" + |
468 | + "." * 70 + "\n" + |
469 | + '1-2@SQL-stub-database SELECT * FROM bar WHERE bing = 42\n' + |
470 | + "-" * 70 + "\n") |
471 | + |
472 | + def test_context_manager(self): |
473 | + logger = da.SQLLogger() |
474 | + with logger: |
475 | + self.execute() |
476 | + self.assertEqual(1, len(logger.queries)) |
477 | + self.assertIs(None, logger.queries[0]['stack']) |
478 | + self.assertIs(None, logger.queries[0]['exception']) |
479 | + self.assertEqual( |
480 | + (1, 2, 'SQL-stub-database', 'SELECT * FROM bar WHERE bing = 42'), |
481 | + logger.queries[0]['sql']) |
482 | + with stdout() as file: |
483 | + result = str(logger) |
484 | + self.assertEqual('', file.getvalue()) |
485 | + self.assertEqual( |
486 | + '1-2@SQL-stub-database SELECT * FROM bar WHERE bing = 42\n' + |
487 | + "-" * 70 + "\n", |
488 | + result) |
489 | + |
490 | + def test_context_manager_with_stacktrace(self): |
491 | + logger = da.SQLLogger(tracebacks_if=True) |
492 | + with logger: |
493 | + self.execute() |
494 | + self.assertEqual(1, len(logger.queries)) |
495 | + self.assertIsNot(None, logger.queries[0]['stack']) |
496 | + |
497 | + def test_sql_parameters(self): |
498 | + logger = da.SQLLogger() |
499 | + with logger: |
500 | + self.execute(statement='SELECT * FROM bar WHERE bing = %s', |
501 | + params=(142,)) |
502 | + self.assertEqual( |
503 | + (1, 2, 'SQL-stub-database', 'SELECT * FROM bar WHERE bing = 142'), |
504 | + logger.queries[0]['sql']) |
505 | + |
506 | + |
507 | +class TestLoggingWithinRequest(TestCaseWithFactory): |
508 | + # When called with a request, the code uses the request timeline and |
509 | + # its action objects. |
510 | + |
511 | + layer = DatabaseFunctionalLayer |
512 | + |
513 | + def setUp(self): |
514 | + super(TestLoggingWithinRequest, self).setUp() |
515 | + self.connection = StubConnection() |
516 | + self.person = self.factory.makePerson() |
517 | + da.set_request_started(1000.0) |
518 | + self.addCleanup(da.clear_request_started) |
519 | + |
520 | + def test_logger(self): |
521 | + tracer = da.LaunchpadStatementTracer() |
522 | + logger = da.SQLLogger() |
523 | + with person_logged_in(self.person): |
524 | + with logger: |
525 | + tracer.connection_raw_execute( |
526 | + self.connection, None, |
527 | + 'SELECT * FROM bar WHERE bing = 42', ()) |
528 | + timeline = get_request_timeline(get_current_browser_request()) |
529 | + action = timeline.actions[-1] |
530 | + self.assertEqual( |
531 | + 'SELECT * FROM bar WHERE bing = 42', |
532 | + action.detail) |
533 | + self.assertEqual('SQL-stub-database', action.category) |
534 | + self.assertIs(None, action.duration) |
535 | + # Now we change the detail to verify that the action is the |
536 | + # source of the final log. |
537 | + action.detail = 'SELECT * FROM surprise' |
538 | + tracer.connection_raw_execute_success( |
539 | + self.connection, None, |
540 | + 'SELECT * FROM bar WHERE bing = 42', ()) |
541 | + self.assertIsNot(None, action.duration) |
542 | + self.assertEqual( |
543 | + 'SELECT * FROM surprise', logger.queries[0]['sql'][3]) |
544 | + |
545 | + def test_stderr(self): |
546 | + with override_environ(LP_DEBUG_SQL='1'): |
547 | + tracer = da.LaunchpadStatementTracer() |
548 | + with person_logged_in(self.person): |
549 | + with stderr() as file: |
550 | + tracer.connection_raw_execute( |
551 | + self.connection, None, |
552 | + 'SELECT * FROM bar WHERE bing = 42', ()) |
553 | + timeline = get_request_timeline(get_current_browser_request()) |
554 | + action = timeline.actions[-1] |
555 | + self.assertEqual( |
556 | + 'SELECT * FROM bar WHERE bing = 42', |
557 | + action.detail) |
558 | + self.assertEqual('SQL-stub-database', action.category) |
559 | + self.assertIs(None, action.duration) |
560 | + # Now we change the detail to verify that the action is the |
561 | + # source of the final log. |
562 | + action.detail = 'SELECT * FROM surprise' |
563 | + tracer.connection_raw_execute_success( |
564 | + self.connection, None, |
565 | + 'SELECT * FROM bar WHERE bing = 42', ()) |
566 | + self.assertIsNot(None, action.duration) |
567 | + self.assertEndsWith( |
568 | + file.getvalue(), |
569 | + '@SQL-stub-database SELECT * FROM surprise\n' + |
570 | + "-" * 70 + "\n") |
571 | |
572 | === modified file 'lib/lp/services/profile/profile.py' |
573 | --- lib/lp/services/profile/profile.py 2011-08-24 17:24:38 +0000 |
574 | +++ lib/lp/services/profile/profile.py 2011-09-02 16:31:40 +0000 |
575 | @@ -213,7 +213,7 @@ |
576 | actions.add('callgrind') |
577 | if actions: |
578 | if 'sqltrace' in actions: |
579 | - da.start_sql_traceback_logging() |
580 | + da.start_sql_logging(tracebacks_if=True) |
581 | if 'show' in actions or actions.intersection(available_profilers): |
582 | _profilers.profiler = Profiler() |
583 | _profilers.profiler.start() |
584 | @@ -346,7 +346,7 @@ |
585 | del prof_stats |
586 | trace = None |
587 | if 'sqltrace' in actions: |
588 | - trace = da.stop_sql_traceback_logging() or () |
589 | + trace = da.stop_sql_logging() or () |
590 | # The trace is a list of dicts, each with the keys "sql" and |
591 | # "stack". "sql" is a tuple of start time, stop time, database |
592 | # name (with a "SQL-" prefix), and sql statement. "stack" is a |
593 | |
594 | === modified file 'lib/lp/services/profile/tests.py' |
595 | --- lib/lp/services/profile/tests.py 2011-08-22 19:06:39 +0000 |
596 | +++ lib/lp/services/profile/tests.py 2011-09-02 16:31:40 +0000 |
597 | @@ -82,7 +82,6 @@ |
598 | profile._profilers.actions = None |
599 | profile._profilers.memory_profile_start = None |
600 | profile._profilers.profiling = False |
601 | - da.stop_sql_traceback_logging() |
602 | super(TestCleanupProfiler, self).tearDown() |
603 | |
604 | |
605 | @@ -275,7 +274,7 @@ |
606 | profile.start_request(self._get_start_event('/++profile++sqltrace/')) |
607 | self.assertIs(getattr(profile._profilers, 'profiler', None), None) |
608 | self.assertEquals(profile._profilers.actions, set(('sqltrace', ))) |
609 | - self.assertEqual([], da.stop_sql_traceback_logging()) |
610 | + self.assertEqual([], da.stop_sql_logging()) |
611 | |
612 | |
613 | class BaseRequestEndHandlerTest(BaseTest): |
Gary, this looks good.