From 6578f0b8ea3b26022bb5161a40a77c6b15de21ae Mon Sep 17 00:00:00 2001 From: Matthew J Morrison Date: Wed, 8 Dec 2010 08:42:57 -0600 Subject: Added clean_params method to DatabaseStatTracker to scrub non-unicode data for displaying on the sql panel Signed-off-by: Rob Hudson --- debug_toolbar/panels/sql.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'debug_toolbar') diff --git a/debug_toolbar/panels/sql.py b/debug_toolbar/panels/sql.py index e1e9bdf..299da66 100644 --- a/debug_toolbar/panels/sql.py +++ b/debug_toolbar/panels/sql.py @@ -12,7 +12,7 @@ from django.views.debug import linebreak_iter from django.template import Node from django.template.loader import render_to_string from django.utils import simplejson -from django.utils.encoding import force_unicode +from django.utils.encoding import force_unicode, DjangoUnicodeDecodeError from django.utils.hashcompat import sha_constructor from django.utils.translation import ugettext_lazy as _ @@ -85,12 +85,24 @@ class DatabaseStatTracker(util.CursorDebugWrapper): Replacement for CursorDebugWrapper which stores additional information in `connection.queries`. """ + def clean_params(self, params): + clean_params = () + for x in params: + try: + force_unicode(x, strings_only=True) + except DjangoUnicodeDecodeError: + clean_params += ("", ) + else: + clean_params += (x, ) + return clean_params + def execute(self, sql, params=()): start = datetime.now() try: return self.cursor.execute(sql, params) finally: stop = datetime.now() + params = self.clean_params(params) duration = ms_from_timedelta(stop - start) stacktrace = tidy_stacktrace(traceback.extract_stack()) _params = '' -- cgit v1.2.3 From 1d90d8e19c8f7a06a8f3e61535f4f83c12cb58c6 Mon Sep 17 00:00:00 2001 From: Vincent Driessen Date: Sun, 9 Jan 2011 10:06:25 -0800 Subject: Added support for LogBook. Thanks to Vincent Driessen for the idea and patch. Signed-off-by: Rob Hudson --- debug_toolbar/panels/logger.py | 78 ++++++++++++++++------ .../templates/debug_toolbar/panels/logger.html | 2 + 2 files changed, 60 insertions(+), 20 deletions(-) (limited to 'debug_toolbar') diff --git a/debug_toolbar/panels/logger.py b/debug_toolbar/panels/logger.py index 620102e..0ddbfb1 100644 --- a/debug_toolbar/panels/logger.py +++ b/debug_toolbar/panels/logger.py @@ -8,16 +8,16 @@ from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from debug_toolbar.panels import DebugPanel -class ThreadTrackingHandler(logging.Handler): + +class LogCollector(object): def __init__(self): if threading is None: raise NotImplementedError("threading module is not available, \ the logging panel cannot be used without it") - logging.Handler.__init__(self) self.records = {} # a dictionary that maps threads to log records - def emit(self, record): - self.get_records().append(record) + def add_record(self, record, thread=None): + self.get_records(thread).append(record) def get_records(self, thread=None): """ @@ -36,20 +36,67 @@ class ThreadTrackingHandler(logging.Handler): if thread in self.records: del self.records[thread] -handler = ThreadTrackingHandler() + +class ThreadTrackingHandler(logging.Handler): + def __init__(self, collector): + logging.Handler.__init__(self) + self.collector = collector + + def emit(self, record): + record = { + 'message': record.getMessage(), + 'time': datetime.datetime.fromtimestamp(record.created), + 'level': record.levelname, + 'file': record.pathname, + 'line': record.lineno, + 'channel': record.name, + } + self.collector.add_record(record) + + +collector = LogCollector() +logging_handler = ThreadTrackingHandler(collector) logging.root.setLevel(logging.NOTSET) -logging.root.addHandler(handler) +logging.root.addHandler(logging_handler) # register with logging + +try: + import logbook + logbook_supported = True +except ImportError: + # logbook support is optional, so fail silently + logbook_supported = False + +if logbook_supported: + class LogbookThreadTrackingHandler(logbook.handlers.Handler): + def __init__(self, collector): + logbook.handlers.Handler.__init__(self, bubble=True) + self.collector = collector + + def emit(self, record): + record = { + 'message': record.message, + 'time': record.time, + 'level': record.level_name, + 'file': record.filename, + 'line': record.lineno, + 'channel': record.channel, + } + self.collector.add_record(record) + + + logbook_handler = LogbookThreadTrackingHandler(collector) + logbook_handler.push_application() # register with logbook class LoggingPanel(DebugPanel): name = 'Logging' has_content = True def process_request(self, request): - handler.clear_records() + collector.clear_records() def get_and_delete(self): - records = handler.get_records() - handler.clear_records() + records = collector.get_records() + collector.clear_records() return records def nav_title(self): @@ -57,7 +104,7 @@ class LoggingPanel(DebugPanel): def nav_subtitle(self): # FIXME l10n: use ngettext - return "%s message%s" % (len(handler.get_records()), (len(handler.get_records()) == 1) and '' or 's') + return "%s message%s" % (len(collector.get_records()), (len(collector.get_records()) == 1) and '' or 's') def title(self): return _('Log Messages') @@ -66,16 +113,7 @@ class LoggingPanel(DebugPanel): return '' def content(self): - records = [] - for record in self.get_and_delete(): - records.append({ - 'message': record.getMessage(), - 'time': datetime.datetime.fromtimestamp(record.created), - 'level': record.levelname, - 'file': record.pathname, - 'line': record.lineno, - }) - + records = self.get_and_delete() context = self.context.copy() context.update({'records': records}) diff --git a/debug_toolbar/templates/debug_toolbar/panels/logger.html b/debug_toolbar/templates/debug_toolbar/panels/logger.html index 5e8b652..c41749e 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/logger.html +++ b/debug_toolbar/templates/debug_toolbar/panels/logger.html @@ -5,6 +5,7 @@ {% trans "Level" %} {% trans "Time" %} + {% trans "Channel" %} {% trans "Message" %} {% trans "Location" %} @@ -14,6 +15,7 @@ {{ record.level }} {{ record.time|date:"h:i:s m/d/Y" }} + {{ record.channel|default:"-" }} {{ record.message }} {{ record.file }}:{{ record.line }} -- cgit v1.2.3 From a2edb76e8dc900ab5f147e062ceee5ac3ac35a18 Mon Sep 17 00:00:00 2001 From: postal2600 Date: Thu, 25 Nov 2010 01:44:46 -0800 Subject: Added support for executemany. Fixes issue #120 Signed-off-by: Rob Hudson --- debug_toolbar/panels/sql.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'debug_toolbar') diff --git a/debug_toolbar/panels/sql.py b/debug_toolbar/panels/sql.py index 299da66..a3eff49 100644 --- a/debug_toolbar/panels/sql.py +++ b/debug_toolbar/panels/sql.py @@ -125,6 +125,49 @@ class DatabaseStatTracker(util.CursorDebugWrapper): pass del cur_frame + # We keep `sql` to maintain backwards compatibility + self.db.queries.append({ + 'sql': self.db.ops.last_executed_query(self.cursor, sql, params), + 'duration': duration, + 'raw_sql': sql, + 'params': _params, + 'hash': sha_constructor(settings.SECRET_KEY + sql + _params).hexdigest(), + 'stacktrace': stacktrace, + 'start_time': start, + 'stop_time': stop, + 'is_slow': (duration > SQL_WARNING_THRESHOLD), + 'is_select': sql.lower().strip().startswith('select'), + 'template_info': template_info, + }) + + def executemany(self, sql, params=()): + start = datetime.now() + try: + return self.cursor.executemany(sql, params) + finally: + stop = datetime.now() + duration = ms_from_timedelta(stop - start) + stacktrace = tidy_stacktrace(traceback.extract_stack()) + _params = '' + try: + _params = simplejson.dumps([force_unicode(x, strings_only=True) for x in params]) + except TypeError: + pass # object not JSON serializable + + template_info = None + cur_frame = sys._getframe().f_back + try: + while cur_frame is not None: + if cur_frame.f_code.co_name == 'render': + node = cur_frame.f_locals['self'] + if isinstance(node, Node): + template_info = get_template_info(node.source) + break + cur_frame = cur_frame.f_back + except: + pass + del cur_frame + # We keep `sql` to maintain backwards compatibility self.db.queries.append({ 'sql': self.db.ops.last_executed_query(self.cursor, sql, params), -- cgit v1.2.3 From 1efd6e652e35aa32339f2d5d6d8772a172646c84 Mon Sep 17 00:00:00 2001 From: Chris Adams Date: Thu, 3 Feb 2011 10:22:24 -0500 Subject: HTML validation: avoid injecting