├── .gitignore ├── AUTHORS ├── LICENSE ├── MANIFEST.in ├── README.rst ├── debug_logging ├── __init__.py ├── admin.py ├── formatters.py ├── handlers.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── log_urls.py ├── middleware.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_add_test_run_model.py │ ├── 0003_move_project_name_hostname_revision.py │ ├── 0004_remove_project_name_hostname_revision.py │ ├── 0005_rename_picklefields.py │ ├── 0006_auto__change_test_run_end.py │ ├── 0007_auto__add_field_testrun_name__add_field_testrun_description.py │ ├── 0008_auto__add_field_testrun_total_requests.py │ └── __init__.py ├── models.py ├── panels │ ├── __init__.py │ ├── cache.py │ ├── identity.py │ ├── revision.py │ ├── settings_vars.py │ ├── sql.py │ └── timer.py ├── settings.py ├── static │ └── debug_logging │ │ ├── css │ │ ├── basic.css │ │ ├── rtd.css │ │ └── style.css │ │ ├── img │ │ └── search.png │ │ └── js │ │ ├── expandable.js │ │ ├── highlights.js │ │ ├── lib │ │ └── jquery-1.5.1.min.js │ │ └── test_run.js ├── templates │ └── debug_logging │ │ ├── _inline_record.html │ │ ├── base.html │ │ ├── index.html │ │ ├── record_detail.html │ │ └── run_detail.html ├── urls.py ├── utils.py └── views.py ├── docs ├── Makefile ├── conf.py ├── index.rst ├── install.rst ├── running.rst ├── screenshots │ ├── debug_logging.png │ ├── debug_logging_2.png │ └── debug_logging_3.png └── settings.rst ├── requirements.pip └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.DS_Store 3 | *~ 4 | django_debug_logging.egg-info 5 | dist 6 | _build 7 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | The Django Debug Logging project was originally written by Brandon Konkle 2 | for a Lincoln Loop project. 3 | 4 | Other contributors include: 5 | 6 | Yann Malet 7 | Dmitry Chaplinsky 8 | 9 | Thank you for your contributions! 10 | 11 | The Django Debug Toolbar was originally created by Rob Hudson 12 | in August 2008. Please see the project page for more information about the 13 | people that made that project, and this one, possible. 14 | https://github.com/django-debug-toolbar/django-debug-toolbar 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Lincoln Loop and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Lincoln Loop nor the names of this project's 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include LICENSE 3 | include README.rst 4 | recursive-include debug_logging/static * 5 | recursive-include debug_logging/templates * 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Django Debug Logging 3 | ==================== 4 | 5 | Django Debug Logging is a "plugin" for the `Django Debug Toolbar`_ that allows 6 | users to log the debug toolbar statistics to the database during a site crawl. 7 | This allows users to create performance testing plans to exercise the site, and 8 | then review and aggregate the results afterwards to identify performance 9 | problems. 10 | 11 | It also provides a basic UI for browsing the details that have been logged to 12 | the database and reviewing aggregated information about test runs. The UI 13 | borrows a lot from the custom Sphinx theme by the Read the Docs team, and the 14 | Sentry project from Disqus. 15 | 16 | The overall goal is to use this tool to monitor performance statistics over 17 | time, so that you can see trends and spikes in the number of queries, cache 18 | misses, cpu time, etc., and identify where in the app the problems are coming 19 | from. It is not intended as a load testing tool, so features like concurrency 20 | and warmup periods will not be part of the initial focus. 21 | 22 | The docs can be found on `Read the Docs`_ 23 | 24 | Screenshots 25 | ----------- 26 | 27 | The main Debug Logging frontend view: 28 | 29 | .. image:: https://github.com/lincolnloop/django-debug-logging/raw/develop/docs/screenshots/debug_logging.png 30 | :width: 640px 31 | :height: 341px 32 | :scale: 50% 33 | :alt: Debug Logging main view 34 | :target: https://github.com/lincolnloop/django-debug-logging/raw/develop/docs/screenshots/debug_logging.png 35 | 36 | A test run: 37 | 38 | .. image:: https://github.com/lincolnloop/django-debug-logging/raw/develop/docs/screenshots/debug_logging_2.png 39 | :width: 640px 40 | :height: 422px 41 | :scale: 50% 42 | :alt: Debug Logging aggregated stats 43 | :target: https://github.com/lincolnloop/django-debug-logging/raw/develop/docs/screenshots/debug_logging_2.png 44 | 45 | A log record: 46 | 47 | .. image:: https://github.com/lincolnloop/django-debug-logging/raw/develop/docs/screenshots/debug_logging_3.png 48 | :width: 640px 49 | :height: 410px 50 | :scale: 50% 51 | :alt: Debug Logging detail view 52 | :target: https://github.com/lincolnloop/django-debug-logging/raw/develop/docs/screenshots/debug_logging_3.png 53 | 54 | To Do 55 | ----- 56 | 57 | We welcome contributions! Here are some of our main priorities for continued 58 | development: 59 | 60 | * Add a --repeat option to the log_urls command so that the urls can be run 61 | through multiple times. 62 | 63 | * Write more complex performance tests that use TestCase classes and log each 64 | request from the Django test client. 65 | 66 | * Graph the aggregated stats of the runs. 67 | 68 | * Take more inspiration from Sentry and group hits on the same urls within the 69 | same run together, showing aggregated and individual stats. 70 | 71 | .. _Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar 72 | .. _Read the Docs: http://readthedocs.org/projects/django-debug-logging/ 73 | -------------------------------------------------------------------------------- /debug_logging/__init__.py: -------------------------------------------------------------------------------- 1 | VERSION = (0, 5, 0, "a", 1) # following PEP 386 2 | DEV_N = 1 # for PyPi releases, set this to None 3 | 4 | 5 | def get_version(short=False): 6 | version = "%s.%s" % (VERSION[0], VERSION[1]) 7 | if short: 8 | return version 9 | if VERSION[2]: 10 | version = "%s.%s" % (version, VERSION[2]) 11 | if VERSION[3] != "f": 12 | version = "%s%s%s" % (version, VERSION[3], VERSION[4]) 13 | if DEV_N: 14 | version = "%s.dev%s" % (version, DEV_N) 15 | return version 16 | 17 | __version__ = get_version() 18 | -------------------------------------------------------------------------------- /debug_logging/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from debug_logging.models import TestRun, DebugLogRecord 3 | 4 | 5 | class TestRunAdmin(admin.ModelAdmin): 6 | pass 7 | admin.site.register(TestRun, TestRunAdmin) 8 | 9 | 10 | class DebugLogRecordAdmin(admin.ModelAdmin): 11 | pass 12 | admin.site.register(DebugLogRecord, DebugLogRecordAdmin) 13 | -------------------------------------------------------------------------------- /debug_logging/formatters.py: -------------------------------------------------------------------------------- 1 | from logging import Formatter 2 | 3 | from django.template import Context, Template 4 | 5 | 6 | class DjangoTemplatedFormatter(Formatter): 7 | 8 | def __init__(self, fmt=None, datefmt=None): 9 | """ 10 | Initialize the formatter either with the specified format string, or a 11 | default as described above. Allow for specialized date formatting with 12 | the optional datefmt argument (if omitted, you get the ISO8601 format). 13 | """ 14 | self._fmt = fmt or "{{ message }}" 15 | self.datefmt = datefmt 16 | 17 | def format(self, record): 18 | """ 19 | Format the specified record as text. 20 | 21 | The record's attribute dictionary is used as the context and the format 22 | provided on init is used as the template. Before formatting the 23 | dictionary, a couple of preparatory steps are carried out. The message 24 | attribute of the record is computed using LogRecord.getMessage(). If 25 | the formatting string contains "%(asctime)", formatTime() is called to 26 | format the event time. If there is exception information, it is 27 | formatted using formatException() and appended to the message. 28 | """ 29 | record.message = record.getMessage() 30 | if "{{ asctime }}" in self._fmt: 31 | record.asctime = self.formatTime(record, self.datefmt) 32 | t = Template(self._fmt) 33 | s = t.render(Context(record.__dict__)) 34 | if record.exc_info: 35 | # Cache the traceback text to avoid converting it multiple times 36 | # (it's constant anyway) 37 | if not record.exc_text: 38 | record.exc_text = self.formatException(record.exc_info) 39 | if record.exc_text: 40 | if s[-1:] != "\n": 41 | s = s + "\n" 42 | s = s + record.exc_text 43 | return s 44 | -------------------------------------------------------------------------------- /debug_logging/handlers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from debug_logging.models import TestRun, DebugLogRecord 4 | 5 | 6 | class DBHandler(logging.Handler): 7 | 8 | def emit(self, record): 9 | if type(record.msg) is dict: 10 | # Pull the project name, hostname, and revision out of the record 11 | filters = {} 12 | for key in ('project_name', 'hostname', 'revision'): 13 | if record.msg.has_key(key): 14 | filters[key] = record.msg.pop(key) 15 | 16 | # Find the open test run for this project 17 | try: 18 | test_run = TestRun.objects.get(end__isnull=True, **filters) 19 | except TestRun.DoesNotExist: 20 | # Don't log this request if there isn't an open TestRun 21 | return 22 | record.msg['test_run'] = test_run 23 | 24 | instance = DebugLogRecord(**record.msg) 25 | instance.save() 26 | -------------------------------------------------------------------------------- /debug_logging/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lincolnloop/django-debug-logging/dc2f60b5abaf5f344de45a791f3a3473873ae304/debug_logging/management/__init__.py -------------------------------------------------------------------------------- /debug_logging/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lincolnloop/django-debug-logging/dc2f60b5abaf5f344de45a791f3a3473873ae304/debug_logging/management/commands/__init__.py -------------------------------------------------------------------------------- /debug_logging/management/commands/log_urls.py: -------------------------------------------------------------------------------- 1 | from __future__ import with_statement 2 | import sys 3 | from datetime import datetime 4 | from optparse import make_option 5 | 6 | from django.test.client import Client 7 | from django.core.management.base import BaseCommand, CommandError 8 | from django.contrib.sitemaps import Sitemap 9 | 10 | from debug_logging.settings import LOGGING_CONFIG 11 | from debug_logging.handlers import DBHandler 12 | from debug_logging.utils import import_from_string 13 | 14 | 15 | class Command(BaseCommand): 16 | help = 'Hit a list of urls in sequence so that the requests will be logged' 17 | args = "url_list [url_list ...]" 18 | 19 | option_list = BaseCommand.option_list + ( 20 | make_option('-s', '--manual-start', 21 | action='store_true', 22 | dest='manual_start', 23 | help='Manually start a TestRun without actually logging any urls.' 24 | ), 25 | make_option('-e', '--manual-end', 26 | action='store_true', 27 | dest='manual_end', 28 | help='End a TestRun that was started manually.' 29 | ), 30 | make_option('-n', '--name', 31 | action='store', 32 | dest='name', 33 | metavar='NAME', 34 | help='Add a name to the test run.' 35 | ), 36 | make_option('', '--sitemap', 37 | action='store', 38 | dest='sitemap', 39 | metavar='SITEMAP', 40 | help='Load urls from a django sitemap object or dict of sitemaps.' 41 | ), 42 | make_option('-d', '--description', 43 | action='store', 44 | dest='description', 45 | metavar='DESC', 46 | help='Add a description to the test run.' 47 | ), 48 | make_option('-u', '--username', 49 | action='store', 50 | dest='username', 51 | metavar='USERNAME', 52 | help='Run the test authenticated with the USERNAME provided.' 53 | ), 54 | make_option('-p', '--password', 55 | action='store', 56 | dest='password', 57 | metavar='PASSWORD', 58 | help='Run the test authenticated with the PASSWORD provided.' 59 | ), 60 | ) 61 | 62 | def status_update(self, msg): 63 | if not self.quiet: 64 | print msg 65 | 66 | def status_ticker(self): 67 | if not self.quiet: 68 | sys.stdout.write('.') 69 | sys.stdout.flush() 70 | 71 | def handle(self, *url_lists, **options): 72 | from django.conf import settings 73 | from debug_logging.models import TestRun 74 | from debug_logging.utils import (get_project_name, get_hostname, 75 | get_revision) 76 | 77 | verbosity = int(options.get('verbosity', 1)) 78 | self.quiet = verbosity < 1 79 | self.verbose = verbosity > 1 80 | 81 | # Dtermine if the DBHandler is used 82 | if True in [isinstance(handler, DBHandler) for handler in 83 | LOGGING_CONFIG["LOGGING_HANDLERS"]]: 84 | self.has_dbhandler = True 85 | else: 86 | self.has_dbhandler = False 87 | 88 | # Check for a username without a password, or vice versa 89 | if options['username'] and not options['password']: 90 | raise CommandError('If a username is provided, a password must ' 91 | 'also be provided.') 92 | if options['password'] and not options['username']: 93 | raise CommandError('If a password is provided, a username must ' 94 | 'also be provided.') 95 | 96 | # Create a TestRun object to track this run 97 | filters = {} 98 | panels = settings.DEBUG_TOOLBAR_PANELS 99 | if 'debug_logging.panels.identity.IdentityLoggingPanel' in panels: 100 | filters['project_name'] = get_project_name() 101 | filters['hostname'] = get_hostname() 102 | if 'debug_logging.panels.revision.RevisionLoggingPanel' in panels: 103 | filters['revision'] = get_revision() 104 | 105 | if self.has_dbhandler: 106 | # Check to see if there is already a TestRun object open 107 | existing_runs = TestRun.objects.filter(end__isnull=True, **filters) 108 | if existing_runs: 109 | if options['manual_start']: 110 | # If the --manual-start option was specified, error out 111 | # because there is already an open TestRun 112 | raise CommandError('There is already an open TestRun.') 113 | 114 | # Otherwise, close it so that we can open a new one 115 | for existing_run in existing_runs: 116 | existing_run.end = datetime.now() 117 | existing_run.save() 118 | 119 | if options['manual_end']: 120 | # If the --manual-end option was specified, we can now exit 121 | self.status_update('The TestRun was successfully closed.') 122 | return 123 | if options['manual_end']: 124 | # The --manual-end option was specified, but there was no 125 | # existing run to close. 126 | raise CommandError('There is no open TestRun to end.') 127 | 128 | filters['start'] = datetime.now() 129 | test_run = TestRun(**filters) 130 | 131 | if options['name']: 132 | test_run.name = options['name'] 133 | if options['description']: 134 | test_run.description = options['description'] 135 | 136 | test_run.save() 137 | 138 | if options['manual_start']: 139 | # The TestRun was successfully created 140 | self.status_update('A new TestRun was successfully opened.') 141 | return 142 | 143 | urls = [] 144 | for url_list in url_lists: 145 | with open(url_list) as f: 146 | urls.extend([l.strip() for l in f.readlines() 147 | if not l.startswith('#')]) 148 | 149 | if options['sitemap']: 150 | sitemaps = import_from_string(options['sitemap']) 151 | 152 | if isinstance(sitemaps, dict): 153 | for sitemap in sitemaps.values(): 154 | urls.extend(map(sitemap.location, sitemap.items())) 155 | elif isinstance(sitemaps, Sitemap): 156 | urls.extend(map(sitemaps.location, sitemaps.items())) 157 | else: 158 | raise CommandError( 159 | 'Sitemaps should be a Sitemap object or a dict, got %s ' 160 | 'instead' % type(sitemaps) 161 | ) 162 | 163 | self.status_update('Beginning debug logging run...') 164 | 165 | client = Client() 166 | 167 | if options['username'] and options['password']: 168 | client.login(username=options['username'], 169 | password=options['password']) 170 | 171 | for url in urls: 172 | try: 173 | response = client.get(url, DJANGO_DEBUG_LOGGING=True) 174 | except KeyboardInterrupt as e: 175 | if self.has_dbhandler: 176 | # Close out the log entry 177 | test_run.end = datetime.now() 178 | test_run.save() 179 | 180 | raise CommandError('Debug logging run cancelled.') 181 | except Exception as e: 182 | if self.verbose: 183 | self.status_update('\nSkipped %s because of error: %s' 184 | % (url, e)) 185 | continue 186 | if response and response.status_code == 200: 187 | self.status_ticker() 188 | else: 189 | if self.verbose: 190 | try: 191 | self.status_update('\nURL %s responded with code %s' 192 | % (url, response.status_code)) 193 | except NameError as e: 194 | self.status_update('\nSkipped %s because of error: %s' 195 | % (url, e)) 196 | 197 | if self.has_dbhandler: 198 | # Close out the log entry 199 | test_run.end = datetime.now() 200 | test_run.save() 201 | 202 | self.status_update('done!\n') 203 | -------------------------------------------------------------------------------- /debug_logging/middleware.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from django.conf import settings 4 | from django.core.urlresolvers import reverse, NoReverseMatch 5 | 6 | from debug_toolbar.toolbar.loader import DebugToolbar 7 | from debug_toolbar.middleware import DebugToolbarMiddleware 8 | from debug_logging.settings import LOGGING_CONFIG 9 | 10 | logger = logging.getLogger('debug.logger') 11 | for HandlerClass in LOGGING_CONFIG["LOGGING_HANDLERS"]: 12 | logger.addHandler(HandlerClass) 13 | 14 | 15 | class DebugLoggingMiddleware(DebugToolbarMiddleware): 16 | """ 17 | Extends the Debug Toolbar middleware with some extras for logging stats. 18 | """ 19 | 20 | def _logging_enabled(self, request): 21 | return request.META.get('DJANGO_DEBUG_LOGGING', False) 22 | 23 | def _show_toolbar(self, request): 24 | if self._logging_enabled(request): 25 | # If logging is enabled, don't show the toolbar 26 | return False 27 | return super(DebugLoggingMiddleware, self)._show_toolbar(request) 28 | 29 | def process_request(self, request): 30 | if self._logging_enabled(request): 31 | request.debug_logging = LOGGING_CONFIG 32 | request.debug_logging['ENABLED'] = True 33 | response = super(DebugLoggingMiddleware, self).process_request(request) 34 | 35 | if self._logging_enabled(request): 36 | # If the debug-logging frontend is in use, add it to the blacklist 37 | blacklist = request.debug_logging['BLACKLIST'] 38 | try: 39 | debug_logging_prefix = reverse('debug_logging_index') 40 | blacklist.append(debug_logging_prefix) 41 | except NoReverseMatch: 42 | pass 43 | 44 | # Don't log requests to urls in the blacklist 45 | for blacklist_url in blacklist: 46 | if request.path.startswith(blacklist_url): 47 | return response 48 | 49 | # Add an attribute to the request to track stats, and log the 50 | # request path 51 | request.debug_logging_stats = {'request_path': request.path} 52 | 53 | self.debug_toolbars[request] = DebugToolbar(request) 54 | for panel in self.debug_toolbars[request].panels: 55 | panel.process_request(request) 56 | 57 | return response 58 | 59 | def process_response(self, request, response): 60 | response = super(DebugLoggingMiddleware, self).process_response( 61 | request, response) 62 | 63 | if response.status_code == 200: 64 | if self._logging_enabled(request) and \ 65 | hasattr(request, 'debug_logging_stats'): 66 | # If logging is enabled, log the stats to the selected handler 67 | logger.debug(request.debug_logging_stats) 68 | 69 | return response 70 | -------------------------------------------------------------------------------- /debug_logging/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding model 'DebugLogRecord' 12 | db.create_table('debug_logging_debuglogrecord', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), 15 | ('project_name', self.gf('django.db.models.fields.CharField')(max_length=255)), 16 | ('hostname', self.gf('django.db.models.fields.CharField')(max_length=255)), 17 | ('request_path', self.gf('django.db.models.fields.CharField')(max_length=255)), 18 | ('revision', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), 19 | ('settings_pickled', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), 20 | ('timer_utime', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 21 | ('timer_stime', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 22 | ('timer_cputime', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 23 | ('timer_total', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 24 | ('timer_vcsw', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 25 | ('timer_ivcsw', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 26 | ('sql_num_queries', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 27 | ('sql_time', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 28 | ('sql_queries_pickled', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), 29 | ('cache_num_calls', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 30 | ('cache_time', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 31 | ('cache_hits', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 32 | ('cache_misses', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 33 | ('cache_sets', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 34 | ('cache_gets', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 35 | ('cache_get_many', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 36 | ('cache_deletes', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 37 | ('cache_calls_pickled', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), 38 | )) 39 | db.send_create_signal('debug_logging', ['DebugLogRecord']) 40 | 41 | 42 | def backwards(self, orm): 43 | 44 | # Deleting model 'DebugLogRecord' 45 | db.delete_table('debug_logging_debuglogrecord') 46 | 47 | 48 | models = { 49 | 'debug_logging.debuglogrecord': { 50 | 'Meta': {'object_name': 'DebugLogRecord'}, 51 | 'cache_calls_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 52 | 'cache_deletes': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 53 | 'cache_get_many': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 54 | 'cache_gets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 55 | 'cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 56 | 'cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 57 | 'cache_num_calls': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 58 | 'cache_sets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 59 | 'cache_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 60 | 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 61 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 62 | 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 63 | 'request_path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 64 | 'revision': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), 65 | 'settings_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 66 | 'sql_num_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 67 | 'sql_queries_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 68 | 'sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 69 | 'timer_cputime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 70 | 'timer_ivcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 71 | 'timer_stime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 72 | 'timer_total': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 73 | 'timer_utime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 74 | 'timer_vcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 75 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 76 | } 77 | } 78 | 79 | complete_apps = ['debug_logging'] 80 | -------------------------------------------------------------------------------- /debug_logging/migrations/0002_add_test_run_model.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding model 'TestRun' 12 | db.create_table('debug_logging_testrun', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('start', self.gf('django.db.models.fields.DateTimeField')()), 15 | ('end', self.gf('django.db.models.fields.DateTimeField')()), 16 | ('project_name', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), 17 | ('hostname', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), 18 | ('revision', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True)), 19 | ('avg_time', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 20 | ('total_time', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 21 | ('avg_cpu_time', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 22 | ('total_cpu_time', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 23 | ('avg_sql_time', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 24 | ('total_sql_time', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 25 | ('avg_sql_queries', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 26 | ('total_sql_queries', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 27 | ('max_sql_queries', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 28 | ('avg_cache_hits', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 29 | ('total_cache_hits', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 30 | ('avg_cache_misses', self.gf('django.db.models.fields.FloatField')(null=True, blank=True)), 31 | ('total_cache_misses', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)), 32 | )) 33 | db.send_create_signal('debug_logging', ['TestRun']) 34 | 35 | # Adding field 'DebugLogRecord.test_run' 36 | db.add_column('debug_logging_debuglogrecord', 'test_run', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['debug_logging.TestRun'], null=True, blank=True), keep_default=False) 37 | 38 | # Changing field 'DebugLogRecord.hostname' 39 | db.alter_column('debug_logging_debuglogrecord', 'hostname', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)) 40 | 41 | # Changing field 'DebugLogRecord.project_name' 42 | db.alter_column('debug_logging_debuglogrecord', 'project_name', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)) 43 | 44 | 45 | def backwards(self, orm): 46 | 47 | raise RuntimeError("Cannot reverse this migration. 'DebugLogRecord.hostname' and 'DebugLogRecord.project_name' were made nullable.") 48 | 49 | 50 | models = { 51 | 'debug_logging.debuglogrecord': { 52 | 'Meta': {'object_name': 'DebugLogRecord'}, 53 | 'cache_calls_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 54 | 'cache_deletes': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 55 | 'cache_get_many': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 56 | 'cache_gets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 57 | 'cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 58 | 'cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 59 | 'cache_num_calls': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 60 | 'cache_sets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 61 | 'cache_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 62 | 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 63 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 64 | 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 65 | 'request_path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 66 | 'revision': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), 67 | 'settings_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 68 | 'sql_num_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 69 | 'sql_queries_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 70 | 'sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 71 | 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['debug_logging.TestRun']", 'null': 'True', 'blank': 'True'}), 72 | 'timer_cputime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 73 | 'timer_ivcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 74 | 'timer_stime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 75 | 'timer_total': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 76 | 'timer_utime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 77 | 'timer_vcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 78 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 79 | }, 80 | 'debug_logging.testrun': { 81 | 'Meta': {'object_name': 'TestRun'}, 82 | 'avg_cache_hits': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 83 | 'avg_cache_misses': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 84 | 'avg_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 85 | 'avg_sql_queries': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 86 | 'avg_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 87 | 'avg_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 88 | 'end': ('django.db.models.fields.DateTimeField', [], {}), 89 | 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 90 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 91 | 'max_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 92 | 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 93 | 'revision': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), 94 | 'start': ('django.db.models.fields.DateTimeField', [], {}), 95 | 'total_cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 96 | 'total_cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 97 | 'total_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 98 | 'total_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 99 | 'total_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 100 | 'total_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) 101 | } 102 | } 103 | 104 | complete_apps = ['debug_logging'] 105 | -------------------------------------------------------------------------------- /debug_logging/migrations/0003_move_project_name_hostname_revision.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import DataMigration 5 | from django.db import models 6 | 7 | class Migration(DataMigration): 8 | 9 | def forwards(self, orm): 10 | """ 11 | This is just a very basic data migration that groups all existing log 12 | records under one TestRun. 13 | """ 14 | from django.db.models import Max, Min 15 | 16 | records = orm.DebugLogRecord.objects.all()[:1] 17 | if records: 18 | hostname = records[0].hostname 19 | project_name = records[0].project_name 20 | revision = records[0].revision 21 | else: 22 | # There are no records 23 | return 24 | 25 | times = orm.DebugLogRecord.objects.aggregate(Max('timestamp'), Min('timestamp')) 26 | start = times['timestamp__min'] 27 | end = times['timestamp__max'] 28 | 29 | test_run = orm.TestRun.objects.create( 30 | start=start, 31 | end=end, 32 | project_name=project_name, 33 | hostname=hostname, 34 | revision=revision, 35 | ) 36 | 37 | for record in orm.DebugLogRecord.objects.all(): 38 | record.test_run = test_run 39 | record.save() 40 | 41 | 42 | def backwards(self, orm): 43 | raise RuntimeError("Cannot reverse this migration.") 44 | 45 | 46 | models = { 47 | 'debug_logging.debuglogrecord': { 48 | 'Meta': {'object_name': 'DebugLogRecord'}, 49 | 'cache_calls_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 50 | 'cache_deletes': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 51 | 'cache_get_many': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 52 | 'cache_gets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 53 | 'cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 54 | 'cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 55 | 'cache_num_calls': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 56 | 'cache_sets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 57 | 'cache_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 58 | 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 59 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 60 | 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 61 | 'request_path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 62 | 'revision': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), 63 | 'settings_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 64 | 'sql_num_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 65 | 'sql_queries_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 66 | 'sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 67 | 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['debug_logging.TestRun']", 'null': 'True', 'blank': 'True'}), 68 | 'timer_cputime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 69 | 'timer_ivcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 70 | 'timer_stime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 71 | 'timer_total': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 72 | 'timer_utime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 73 | 'timer_vcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 74 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 75 | }, 76 | 'debug_logging.testrun': { 77 | 'Meta': {'object_name': 'TestRun'}, 78 | 'avg_cache_hits': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 79 | 'avg_cache_misses': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 80 | 'avg_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 81 | 'avg_sql_queries': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 82 | 'avg_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 83 | 'avg_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 84 | 'end': ('django.db.models.fields.DateTimeField', [], {}), 85 | 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 86 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 87 | 'max_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 88 | 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 89 | 'revision': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), 90 | 'start': ('django.db.models.fields.DateTimeField', [], {}), 91 | 'total_cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 92 | 'total_cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 93 | 'total_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 94 | 'total_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 95 | 'total_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 96 | 'total_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) 97 | } 98 | } 99 | 100 | complete_apps = ['debug_logging'] 101 | -------------------------------------------------------------------------------- /debug_logging/migrations/0004_remove_project_name_hostname_revision.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Deleting field 'DebugLogRecord.hostname' 12 | db.delete_column('debug_logging_debuglogrecord', 'hostname') 13 | 14 | # Deleting field 'DebugLogRecord.revision' 15 | db.delete_column('debug_logging_debuglogrecord', 'revision') 16 | 17 | # Deleting field 'DebugLogRecord.project_name' 18 | db.delete_column('debug_logging_debuglogrecord', 'project_name') 19 | 20 | # Changing field 'DebugLogRecord.test_run' 21 | db.alter_column('debug_logging_debuglogrecord', 'test_run_id', self.gf('django.db.models.fields.related.ForeignKey')(default=1, to=orm['debug_logging.TestRun'])) 22 | 23 | 24 | def backwards(self, orm): 25 | 26 | # Adding field 'DebugLogRecord.hostname' 27 | db.add_column('debug_logging_debuglogrecord', 'hostname', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) 28 | 29 | # Adding field 'DebugLogRecord.revision' 30 | db.add_column('debug_logging_debuglogrecord', 'revision', self.gf('django.db.models.fields.CharField')(max_length=40, null=True, blank=True), keep_default=False) 31 | 32 | # Adding field 'DebugLogRecord.project_name' 33 | db.add_column('debug_logging_debuglogrecord', 'project_name', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) 34 | 35 | # Changing field 'DebugLogRecord.test_run' 36 | db.alter_column('debug_logging_debuglogrecord', 'test_run_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['debug_logging.TestRun'], null=True)) 37 | 38 | 39 | models = { 40 | 'debug_logging.debuglogrecord': { 41 | 'Meta': {'object_name': 'DebugLogRecord'}, 42 | 'cache_calls_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 43 | 'cache_deletes': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 44 | 'cache_get_many': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 45 | 'cache_gets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 46 | 'cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 47 | 'cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 48 | 'cache_num_calls': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 49 | 'cache_sets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 50 | 'cache_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 51 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 52 | 'request_path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 53 | 'settings_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 54 | 'sql_num_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 55 | 'sql_queries_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 56 | 'sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 57 | 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['debug_logging.TestRun']"}), 58 | 'timer_cputime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 59 | 'timer_ivcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 60 | 'timer_stime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 61 | 'timer_total': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 62 | 'timer_utime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 63 | 'timer_vcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 64 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 65 | }, 66 | 'debug_logging.testrun': { 67 | 'Meta': {'object_name': 'TestRun'}, 68 | 'avg_cache_hits': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 69 | 'avg_cache_misses': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 70 | 'avg_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 71 | 'avg_sql_queries': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 72 | 'avg_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 73 | 'avg_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 74 | 'end': ('django.db.models.fields.DateTimeField', [], {}), 75 | 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 76 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 77 | 'max_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 78 | 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 79 | 'revision': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), 80 | 'start': ('django.db.models.fields.DateTimeField', [], {}), 81 | 'total_cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 82 | 'total_cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 83 | 'total_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 84 | 'total_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 85 | 'total_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 86 | 'total_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) 87 | } 88 | } 89 | 90 | complete_apps = ['debug_logging'] 91 | -------------------------------------------------------------------------------- /debug_logging/migrations/0005_rename_picklefields.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | # Rename the columns that will be converted to picklefields 11 | db.rename_column('debug_logging_debuglogrecord', 'settings_pickled', 'settings') 12 | db.rename_column('debug_logging_debuglogrecord', 'sql_queries_pickled', 'sql_queries') 13 | db.rename_column('debug_logging_debuglogrecord', 'cache_calls_pickled', 'cache_calls') 14 | 15 | 16 | def backwards(self, orm): 17 | # Rename the columns that were converted to picklefields 18 | db.rename_column('debug_logging_debuglogrecord', 'settings', 'settings_pickled') 19 | db.rename_column('debug_logging_debuglogrecord', 'sql_queries', 'sql_queries_pickled') 20 | db.rename_column('debug_logging_debuglogrecord', 'cache_calls', 'cache_calls_pickled') 21 | 22 | 23 | models = { 24 | 'debug_logging.debuglogrecord': { 25 | 'Meta': {'object_name': 'DebugLogRecord'}, 26 | 'cache_calls_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 27 | 'cache_deletes': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 28 | 'cache_get_many': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 29 | 'cache_gets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 30 | 'cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 31 | 'cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 32 | 'cache_num_calls': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 33 | 'cache_sets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 34 | 'cache_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 35 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 36 | 'request_path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 37 | 'settings_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 38 | 'sql_num_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 39 | 'sql_queries_pickled': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 40 | 'sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 41 | 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['debug_logging.TestRun']"}), 42 | 'timer_cputime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 43 | 'timer_ivcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 44 | 'timer_stime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 45 | 'timer_total': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 46 | 'timer_utime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 47 | 'timer_vcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 48 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 49 | }, 50 | 'debug_logging.testrun': { 51 | 'Meta': {'object_name': 'TestRun'}, 52 | 'avg_cache_hits': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 53 | 'avg_cache_misses': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 54 | 'avg_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 55 | 'avg_sql_queries': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 56 | 'avg_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 57 | 'avg_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 58 | 'end': ('django.db.models.fields.DateTimeField', [], {}), 59 | 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 60 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 61 | 'max_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 62 | 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 63 | 'revision': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), 64 | 'start': ('django.db.models.fields.DateTimeField', [], {}), 65 | 'total_cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 66 | 'total_cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 67 | 'total_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 68 | 'total_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 69 | 'total_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 70 | 'total_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) 71 | } 72 | } 73 | 74 | complete_apps = ['debug_logging'] 75 | -------------------------------------------------------------------------------- /debug_logging/migrations/0006_auto__change_test_run_end.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Changing field 'TestRun.end' 12 | db.alter_column('debug_logging_testrun', 'end', self.gf('django.db.models.fields.DateTimeField')(null=True)) 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # User chose to not deal with backwards NULL issues for 'TestRun.end' 18 | raise RuntimeError("Cannot reverse this migration. 'TestRun.end' and its values cannot be restored.") 19 | 20 | 21 | models = { 22 | 'debug_logging.debuglogrecord': { 23 | 'Meta': {'object_name': 'DebugLogRecord'}, 24 | 'cache_calls': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), 25 | 'cache_deletes': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 26 | 'cache_get_many': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 27 | 'cache_gets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 28 | 'cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 29 | 'cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 30 | 'cache_num_calls': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 31 | 'cache_sets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 32 | 'cache_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 33 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 34 | 'request_path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 35 | 'settings': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), 36 | 'sql_num_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 37 | 'sql_queries': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), 38 | 'sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 39 | 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['debug_logging.TestRun']"}), 40 | 'timer_cputime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 41 | 'timer_ivcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 42 | 'timer_stime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 43 | 'timer_total': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 44 | 'timer_utime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 45 | 'timer_vcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 46 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 47 | }, 48 | 'debug_logging.testrun': { 49 | 'Meta': {'object_name': 'TestRun'}, 50 | 'avg_cache_hits': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 51 | 'avg_cache_misses': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 52 | 'avg_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 53 | 'avg_sql_queries': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 54 | 'avg_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 55 | 'avg_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 56 | 'end': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 57 | 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 58 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 59 | 'max_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 60 | 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 61 | 'revision': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), 62 | 'start': ('django.db.models.fields.DateTimeField', [], {}), 63 | 'total_cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 64 | 'total_cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 65 | 'total_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 66 | 'total_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 67 | 'total_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 68 | 'total_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) 69 | } 70 | } 71 | 72 | complete_apps = ['debug_logging'] 73 | -------------------------------------------------------------------------------- /debug_logging/migrations/0007_auto__add_field_testrun_name__add_field_testrun_description.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding field 'TestRun.name' 12 | db.add_column('debug_logging_testrun', 'name', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True), keep_default=False) 13 | 14 | # Adding field 'TestRun.description' 15 | db.add_column('debug_logging_testrun', 'description', self.gf('django.db.models.fields.TextField')(null=True, blank=True), keep_default=False) 16 | 17 | 18 | def backwards(self, orm): 19 | 20 | # Deleting field 'TestRun.name' 21 | db.delete_column('debug_logging_testrun', 'name') 22 | 23 | # Deleting field 'TestRun.description' 24 | db.delete_column('debug_logging_testrun', 'description') 25 | 26 | 27 | models = { 28 | 'debug_logging.debuglogrecord': { 29 | 'Meta': {'object_name': 'DebugLogRecord'}, 30 | 'cache_calls': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), 31 | 'cache_deletes': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 32 | 'cache_get_many': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 33 | 'cache_gets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 34 | 'cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 35 | 'cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 36 | 'cache_num_calls': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 37 | 'cache_sets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 38 | 'cache_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 39 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 40 | 'request_path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 41 | 'settings': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), 42 | 'sql_num_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 43 | 'sql_queries': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), 44 | 'sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 45 | 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['debug_logging.TestRun']"}), 46 | 'timer_cputime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 47 | 'timer_ivcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 48 | 'timer_stime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 49 | 'timer_total': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 50 | 'timer_utime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 51 | 'timer_vcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 52 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 53 | }, 54 | 'debug_logging.testrun': { 55 | 'Meta': {'object_name': 'TestRun'}, 56 | 'avg_cache_hits': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 57 | 'avg_cache_misses': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 58 | 'avg_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 59 | 'avg_sql_queries': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 60 | 'avg_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 61 | 'avg_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 62 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 63 | 'end': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 64 | 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 65 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 66 | 'max_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 67 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 68 | 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 69 | 'revision': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), 70 | 'start': ('django.db.models.fields.DateTimeField', [], {}), 71 | 'total_cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 72 | 'total_cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 73 | 'total_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 74 | 'total_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 75 | 'total_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 76 | 'total_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) 77 | } 78 | } 79 | 80 | complete_apps = ['debug_logging'] 81 | -------------------------------------------------------------------------------- /debug_logging/migrations/0008_auto__add_field_testrun_total_requests.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | import datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | class Migration(SchemaMigration): 8 | 9 | def forwards(self, orm): 10 | 11 | # Adding field 'TestRun.total_requests' 12 | db.add_column('debug_logging_testrun', 'total_requests', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True), keep_default=False) 13 | 14 | 15 | def backwards(self, orm): 16 | 17 | # Deleting field 'TestRun.total_requests' 18 | db.delete_column('debug_logging_testrun', 'total_requests') 19 | 20 | 21 | models = { 22 | 'debug_logging.debuglogrecord': { 23 | 'Meta': {'object_name': 'DebugLogRecord'}, 24 | 'cache_calls': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), 25 | 'cache_deletes': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 26 | 'cache_get_many': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 27 | 'cache_gets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 28 | 'cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 29 | 'cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 30 | 'cache_num_calls': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 31 | 'cache_sets': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 32 | 'cache_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 33 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 34 | 'request_path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 35 | 'settings': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), 36 | 'sql_num_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 37 | 'sql_queries': ('picklefield.fields.PickledObjectField', [], {'null': 'True', 'blank': 'True'}), 38 | 'sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 39 | 'test_run': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'records'", 'to': "orm['debug_logging.TestRun']"}), 40 | 'timer_cputime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 41 | 'timer_ivcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 42 | 'timer_stime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 43 | 'timer_total': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 44 | 'timer_utime': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 45 | 'timer_vcsw': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 46 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}) 47 | }, 48 | 'debug_logging.testrun': { 49 | 'Meta': {'object_name': 'TestRun'}, 50 | 'avg_cache_hits': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 51 | 'avg_cache_misses': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 52 | 'avg_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 53 | 'avg_sql_queries': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 54 | 'avg_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 55 | 'avg_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 56 | 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), 57 | 'end': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 58 | 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 59 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 60 | 'max_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 61 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 62 | 'project_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 63 | 'revision': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), 64 | 'start': ('django.db.models.fields.DateTimeField', [], {}), 65 | 'total_cache_hits': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 66 | 'total_cache_misses': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 67 | 'total_cpu_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 68 | 'total_requests': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 69 | 'total_sql_queries': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), 70 | 'total_sql_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}), 71 | 'total_time': ('django.db.models.fields.FloatField', [], {'null': 'True', 'blank': 'True'}) 72 | } 73 | } 74 | 75 | complete_apps = ['debug_logging'] 76 | -------------------------------------------------------------------------------- /debug_logging/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lincolnloop/django-debug-logging/dc2f60b5abaf5f344de45a791f3a3473873ae304/debug_logging/migrations/__init__.py -------------------------------------------------------------------------------- /debug_logging/models.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.db import models 3 | from django.template.defaultfilters import date as date_filter 4 | 5 | from picklefield.fields import PickledObjectField 6 | 7 | 8 | class TestRun(models.Model): 9 | """Captures overall statistics about a single test run.""" 10 | start = models.DateTimeField() 11 | end = models.DateTimeField(blank=True, null=True) 12 | 13 | name = models.CharField(max_length=255, blank=True, null=True) 14 | description = models.TextField(blank=True, null=True) 15 | 16 | project_name = models.CharField(max_length=255, blank=True, null=True) 17 | hostname = models.CharField(max_length=255, blank=True, null=True) 18 | revision = models.CharField(max_length=40, blank=True, null=True) 19 | 20 | # Some of these fields aren't used yet, since they are not represented in 21 | # the UI. Once they are added to the UI, they'll be added to the 22 | # set_aggregates method below. 23 | total_requests = models.IntegerField(blank=True, null=True) 24 | avg_time = models.FloatField(blank=True, null=True) 25 | total_time = models.FloatField(blank=True, null=True) 26 | avg_cpu_time = models.FloatField(blank=True, null=True) 27 | total_cpu_time = models.FloatField(blank=True, null=True) 28 | 29 | avg_sql_time = models.FloatField(blank=True, null=True) 30 | total_sql_time = models.FloatField(blank=True, null=True) 31 | avg_sql_queries = models.FloatField(blank=True, null=True) 32 | total_sql_queries = models.IntegerField(blank=True, null=True) 33 | max_sql_queries = models.IntegerField(blank=True, null=True) 34 | 35 | avg_cache_hits = models.FloatField(blank=True, null=True) 36 | total_cache_hits = models.IntegerField(blank=True, null=True) 37 | avg_cache_misses = models.FloatField(blank=True, null=True) 38 | total_cache_misses = models.IntegerField(blank=True, null=True) 39 | 40 | def __unicode__(self): 41 | date_format = 'n/j/Y g:i a' 42 | if self.name: 43 | return '%s (%s)' % (name, date_filter(self.start, date_format)) 44 | return date_filter(self.start, date_format) 45 | 46 | def get_absolute_url(self): 47 | return reverse('debug_logging_run_detail', args=[self.id]) 48 | 49 | def set_aggregates(self, force=False): 50 | """ 51 | Sets any aggregates that haven't been generated yet, or recalculates 52 | them if the force option is indicated. 53 | """ 54 | aggregates = {} 55 | 56 | if not self.avg_time or force: 57 | aggregates["avg_time"] = models.Avg('timer_total') 58 | if not self.avg_cpu_time or force: 59 | aggregates["avg_cpu_time"] = models.Avg('timer_cputime') 60 | if not self.avg_sql_time or force: 61 | aggregates["avg_sql_time"] = models.Avg('sql_time') 62 | if not self.avg_sql_queries or force: 63 | aggregates["avg_sql_queries"] = models.Avg('sql_num_queries') 64 | if not self.total_sql_queries or force: 65 | aggregates["total_sql_queries"] = models.Sum('sql_num_queries') 66 | if not self.max_sql_queries or force: 67 | aggregates["max_sql_queries"] = models.Max('sql_num_queries') 68 | if not self.total_requests or force: 69 | aggregates["total_requests"] = models.Count('pk') 70 | 71 | if aggregates: 72 | aggregated = self.records.aggregate(**aggregates) 73 | 74 | for key, value in aggregated.items(): 75 | setattr(self, key, value) 76 | 77 | 78 | class DebugLogRecord(models.Model): 79 | """Captures statistics for individual requests.""" 80 | timestamp = models.DateTimeField(auto_now_add=True) 81 | test_run = models.ForeignKey(TestRun, related_name='records') 82 | 83 | request_path = models.CharField(max_length=255) 84 | settings = PickledObjectField(compress=True, blank=True, null=True) 85 | 86 | # Timer stats 87 | timer_utime = models.FloatField(blank=True, null=True) 88 | timer_stime = models.FloatField(blank=True, null=True) 89 | timer_cputime = models.FloatField(blank=True, null=True) 90 | timer_total = models.FloatField(blank=True, null=True) 91 | timer_vcsw = models.IntegerField(blank=True, null=True) 92 | timer_ivcsw = models.IntegerField(blank=True, null=True) 93 | 94 | # Sql stats 95 | sql_num_queries = models.IntegerField(blank=True, null=True) 96 | sql_time = models.FloatField(blank=True, null=True) 97 | sql_queries = PickledObjectField(compress=True, blank=True, null=True) 98 | 99 | # Cache stats 100 | cache_num_calls = models.IntegerField(blank=True, null=True) 101 | cache_time = models.FloatField(blank=True, null=True) 102 | cache_hits = models.IntegerField(blank=True, null=True) 103 | cache_misses = models.IntegerField(blank=True, null=True) 104 | cache_sets = models.IntegerField(blank=True, null=True) 105 | cache_gets = models.IntegerField(blank=True, null=True) 106 | cache_get_many = models.IntegerField(blank=True, null=True) 107 | cache_deletes = models.IntegerField(blank=True, null=True) 108 | cache_calls = PickledObjectField(compress=True, blank=True, null=True) 109 | 110 | def __unicode__(self): 111 | return u'DebugLogRecord from %s' % self.timestamp 112 | -------------------------------------------------------------------------------- /debug_logging/panels/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lincolnloop/django-debug-logging/dc2f60b5abaf5f344de45a791f3a3473873ae304/debug_logging/panels/__init__.py -------------------------------------------------------------------------------- /debug_logging/panels/cache.py: -------------------------------------------------------------------------------- 1 | from debug_toolbar.panels.cache import CacheDebugPanel, CacheStatTracker 2 | 3 | from debug_logging.settings import LOGGING_CONFIG 4 | 5 | 6 | class CacheLoggingPanel(CacheDebugPanel): 7 | """Extends the Cache debug panel to enable logging.""" 8 | 9 | def process_response(self, request, response): 10 | super(CacheLoggingPanel, self).process_response(request, response) 11 | if getattr(request, 'debug_logging', {}).get('ENABLED', False): 12 | # Logging is enabled, so log the cache data 13 | 14 | stats = {} 15 | 16 | stats['cache_num_calls'] = len(self.cache.calls) 17 | stats['cache_time'] = self.cache.total_time 18 | stats['cache_hits'] = self.cache.hits 19 | stats['cache_misses'] = self.cache.misses 20 | stats['cache_sets'] = self.cache.sets 21 | stats['cache_gets'] = self.cache.gets 22 | stats['cache_get_many'] = self.cache.get_many 23 | stats['cache_deletes'] = self.cache.deletes 24 | 25 | if LOGGING_CONFIG['CACHE_EXTRA']: 26 | stats['cache_calls'] = self.cache.calls 27 | 28 | request.debug_logging_stats.update(stats) 29 | -------------------------------------------------------------------------------- /debug_logging/panels/identity.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import ugettext_lazy as _ 2 | 3 | from debug_logging.utils import get_project_name, get_hostname 4 | from debug_toolbar.panels import DebugPanel 5 | 6 | 7 | class IdentityLoggingPanel(DebugPanel): 8 | """ 9 | A panel to display the current site name and hostname, to identify the 10 | current environment for logging. 11 | """ 12 | name = 'Identity' 13 | has_content = False 14 | 15 | def nav_title(self): 16 | return _('Identity') 17 | 18 | def nav_subtitle(self): 19 | project_name, hostname = self.identify() 20 | if project_name and hostname: 21 | return '%s on %s' % (project_name, hostname) 22 | 23 | def process_response(self, request, response): 24 | if getattr(request, 'debug_logging', {}).get('ENABLED', False): 25 | project_name, hostname = self.identify() 26 | # Logging is enabled, so log the revision 27 | request.debug_logging_stats.update({ 28 | 'project_name': project_name, 29 | 'hostname': hostname, 30 | }) 31 | 32 | def identify(self): 33 | return get_project_name(), get_hostname() 34 | -------------------------------------------------------------------------------- /debug_logging/panels/revision.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import ugettext_lazy as _ 2 | from debug_logging.utils import get_revision 3 | from debug_toolbar.panels import DebugPanel 4 | 5 | 6 | class RevisionLoggingPanel(DebugPanel): 7 | """ 8 | A panel to display the current source code revision. Currently only 9 | supports git. 10 | """ 11 | name = 'Revision' 12 | has_content = False 13 | 14 | def nav_title(self): 15 | return _('Revision') 16 | 17 | def nav_subtitle(self): 18 | return self.get_revision() or 'Revision unavailable' 19 | 20 | def process_response(self, request, response): 21 | if getattr(request, 'debug_logging', {}).get('ENABLED', False): 22 | # Logging is enabled, so log the revision 23 | request.debug_logging_stats.update({ 24 | 'revision': self.get_revision() 25 | }) 26 | 27 | def get_revision(self): 28 | return get_revision() 29 | -------------------------------------------------------------------------------- /debug_logging/panels/settings_vars.py: -------------------------------------------------------------------------------- 1 | from django.views.debug import get_safe_settings 2 | from debug_toolbar.panels.settings_vars import SettingsVarsDebugPanel 3 | 4 | 5 | class SettingsVarsLoggingPanel(SettingsVarsDebugPanel): 6 | """Extends the Settings debug panel to enable logging.""" 7 | 8 | def process_response(self, request, response): 9 | super(SettingsVarsLoggingPanel, self).process_response(request, response) 10 | if getattr(request, 'debug_logging', {}).get('ENABLED', False): 11 | # Logging is enabled, so log the settings 12 | 13 | safe_settings = get_safe_settings() 14 | log_settings = {} 15 | for k, v in safe_settings.items(): 16 | if request.debug_logging['LOGGED_SETTINGS_RE'].search(k): 17 | log_settings[k] = v 18 | 19 | request.debug_logging_stats['settings'] = log_settings 20 | -------------------------------------------------------------------------------- /debug_logging/panels/sql.py: -------------------------------------------------------------------------------- 1 | from django.db.backends import BaseDatabaseWrapper 2 | from debug_toolbar.panels.sql import SQLDebugPanel 3 | from debug_toolbar.middleware import DebugToolbarMiddleware 4 | from debug_toolbar.utils.tracking import replace_call 5 | 6 | 7 | # Warning, ugly hackery ahead. Place an alias to the logging class in the 8 | # panels dict. 9 | @replace_call(BaseDatabaseWrapper.cursor) 10 | def cursor(func, self): 11 | djdt = DebugToolbarMiddleware.get_current() 12 | if djdt: 13 | djdt._panels[SQLDebugPanel] = djdt.get_panel(SQLLoggingPanel) 14 | return func(self) 15 | 16 | 17 | class SQLLoggingPanel(SQLDebugPanel): 18 | """Extends the SQL debug panel to enable logging.""" 19 | 20 | def process_response(self, request, response): 21 | super(SQLLoggingPanel, self).process_response(request, response) 22 | if getattr(request, 'debug_logging', {}).get('ENABLED', False): 23 | # Call the nav_subtitle method so that the query data is captured 24 | self.nav_subtitle() 25 | 26 | for alias, query in self._queries: 27 | query['alias'] = alias 28 | 29 | stats = {} 30 | 31 | queries = [q for a, q in self._queries] 32 | 33 | if request.debug_logging['SQL_EXTRA']: 34 | stats['sql_queries'] = queries 35 | 36 | stats['sql_time'] = self._sql_time 37 | stats['sql_num_queries'] = len(queries) 38 | request.debug_logging_stats.update(stats) 39 | -------------------------------------------------------------------------------- /debug_logging/panels/timer.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from debug_toolbar.panels.timer import TimerDebugPanel 3 | 4 | 5 | class TimerLoggingPanel(TimerDebugPanel): 6 | """Extends the Timer debug panel to enable logging.""" 7 | 8 | def get_stats(self): 9 | """ 10 | Taken from the beginning of TimerDebugPanel's 'content' method. 11 | """ 12 | utime = 1000 * self._elapsed_ru('ru_utime') 13 | stime = 1000 * self._elapsed_ru('ru_stime') 14 | vcsw = self._elapsed_ru('ru_nvcsw') 15 | ivcsw = self._elapsed_ru('ru_nivcsw') 16 | minflt = self._elapsed_ru('ru_minflt') 17 | majflt = self._elapsed_ru('ru_majflt') 18 | 19 | return utime, stime, vcsw, ivcsw, minflt, majflt 20 | 21 | def process_response(self, request, response): 22 | super(TimerLoggingPanel, self).process_response(request, response) 23 | 24 | if getattr(request, 'debug_logging', {}).get('ENABLED', False): 25 | utime, stime, vcsw, ivcsw, minflt, majflt = self.get_stats() 26 | stats = { 27 | 'timer_utime': utime, 28 | 'timer_stime': stime, 29 | 'timer_cputime': (utime + stime), 30 | 'timer_total': self.total_time, 31 | 'timer_vcsw': vcsw, 32 | 'timer_ivcsw': ivcsw, 33 | } 34 | request.debug_logging_stats.update(stats) 35 | -------------------------------------------------------------------------------- /debug_logging/settings.py: -------------------------------------------------------------------------------- 1 | import re 2 | from logging import Handler 3 | 4 | from django.conf import settings 5 | from django.utils.importlib import import_module 6 | 7 | from debug_logging.utils import import_from_string 8 | 9 | 10 | DEFAULT_LOGGED_SETTINGS = [ 11 | 'CACHE_BACKEND', 'CACHE_MIDDLEWARE_KEY_PREFIX', 'CACHE_MIDDLEWARE_SECONDS', 12 | 'DATABASES', 'DEBUG', 'DEBUG_LOGGING_CONFIG', 'DEBUG_TOOLBAR_CONFIG', 13 | 'DEBUG_TOOLBAR_PANELS', 'INSTALLED_APPS', 'INTERNAL_IPS', 14 | 'MIDDLEWARE_CLASSES', 'TEMPLATE_CONTEXT_PROCESSORS', 'TEMPLATE_DEBUG', 15 | 'USE_I18N', 'USE_L10N' 16 | ] 17 | 18 | 19 | DEFAULT_CONFIG = { 20 | 'SQL_EXTRA': False, 21 | 'CACHE_EXTRA': False, 22 | 'BLACKLIST': [], 23 | 'LOGGING_HANDLERS': ('debug_logging.handlers.DBHandler',), 24 | 'LOGGED_SETTINGS': DEFAULT_LOGGED_SETTINGS, 25 | } 26 | 27 | # Cache of the logging config. 28 | _logging_config = None 29 | 30 | 31 | def _get_logging_config(): 32 | """ 33 | Extend the default config with the values provided in settings.py, and then 34 | conduct some post-processing. 35 | """ 36 | global _logging_config 37 | if _logging_config is None: 38 | _logging_config = dict(DEFAULT_CONFIG, 39 | **getattr(settings, 'DEBUG_LOGGING_CONFIG', {})) 40 | 41 | # Instantiate the handlers 42 | handlers = [] 43 | for handler in _logging_config['LOGGING_HANDLERS']: 44 | if isinstance(handler, Handler): 45 | handlers.append(handler()) 46 | elif isinstance(handler, basestring): 47 | handlers.append(import_from_string(handler)()) 48 | _logging_config['LOGGING_HANDLERS'] = handlers 49 | 50 | # Compile a regex for logged settings 51 | _logging_config['LOGGED_SETTINGS_RE'] = re.compile( 52 | '|'.join(_logging_config['LOGGED_SETTINGS']) 53 | ) 54 | 55 | return _logging_config 56 | 57 | 58 | LOGGING_CONFIG = _get_logging_config() 59 | -------------------------------------------------------------------------------- /debug_logging/static/debug_logging/css/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | } 56 | 57 | div.sphinxsidebar ul { 58 | list-style: none; 59 | } 60 | 61 | div.sphinxsidebar ul ul, 62 | div.sphinxsidebar ul.want-points { 63 | margin-left: 20px; 64 | list-style: square; 65 | } 66 | 67 | div.sphinxsidebar ul ul { 68 | margin-top: 0; 69 | margin-bottom: 0; 70 | } 71 | 72 | div.sphinxsidebar form { 73 | margin-top: 10px; 74 | } 75 | 76 | div.sphinxsidebar input { 77 | border: 1px solid #98dbcc; 78 | font-family: sans-serif; 79 | font-size: 1em; 80 | } 81 | 82 | img { 83 | border: 0; 84 | } 85 | 86 | /* -- search page ----------------------------------------------------------- */ 87 | 88 | ul.search { 89 | margin: 10px 0 0 20px; 90 | padding: 0; 91 | } 92 | 93 | ul.search li { 94 | padding: 5px 0 5px 20px; 95 | background-image: url(file.png); 96 | background-repeat: no-repeat; 97 | background-position: 0 7px; 98 | } 99 | 100 | ul.search li a { 101 | font-weight: bold; 102 | } 103 | 104 | ul.search li div.context { 105 | color: #888; 106 | margin: 2px 0 0 30px; 107 | text-align: left; 108 | } 109 | 110 | ul.keywordmatches li.goodmatch a { 111 | font-weight: bold; 112 | } 113 | 114 | /* -- index page ------------------------------------------------------------ */ 115 | 116 | table.contentstable { 117 | width: 90%; 118 | } 119 | 120 | table.contentstable p.biglink { 121 | line-height: 150%; 122 | } 123 | 124 | a.biglink { 125 | font-size: 1.3em; 126 | } 127 | 128 | span.linkdescr { 129 | font-style: italic; 130 | padding-top: 5px; 131 | font-size: 90%; 132 | } 133 | 134 | /* -- general index --------------------------------------------------------- */ 135 | 136 | table.indextable { 137 | width: 100%; 138 | } 139 | 140 | table.indextable td { 141 | text-align: left; 142 | vertical-align: top; 143 | } 144 | 145 | table.indextable dl, table.indextable dd { 146 | margin-top: 0; 147 | margin-bottom: 0; 148 | } 149 | 150 | table.indextable tr.pcap { 151 | height: 10px; 152 | } 153 | 154 | table.indextable tr.cap { 155 | margin-top: 10px; 156 | background-color: #f2f2f2; 157 | } 158 | 159 | img.toggler { 160 | margin-right: 3px; 161 | margin-top: 3px; 162 | cursor: pointer; 163 | } 164 | 165 | div.modindex-jumpbox { 166 | border-top: 1px solid #ddd; 167 | border-bottom: 1px solid #ddd; 168 | margin: 1em 0 1em 0; 169 | padding: 0.4em; 170 | } 171 | 172 | div.genindex-jumpbox { 173 | border-top: 1px solid #ddd; 174 | border-bottom: 1px solid #ddd; 175 | margin: 1em 0 1em 0; 176 | padding: 0.4em; 177 | } 178 | 179 | /* -- general body styles --------------------------------------------------- */ 180 | 181 | a.headerlink { 182 | visibility: hidden; 183 | } 184 | 185 | h1:hover > a.headerlink, 186 | h2:hover > a.headerlink, 187 | h3:hover > a.headerlink, 188 | h4:hover > a.headerlink, 189 | h5:hover > a.headerlink, 190 | h6:hover > a.headerlink, 191 | dt:hover > a.headerlink { 192 | visibility: visible; 193 | } 194 | 195 | div.body p.caption { 196 | text-align: inherit; 197 | } 198 | 199 | div.body td { 200 | text-align: left; 201 | } 202 | 203 | .field-list ul { 204 | padding-left: 1em; 205 | } 206 | 207 | .first { 208 | margin-top: 0 !important; 209 | } 210 | 211 | p.rubric { 212 | margin-top: 30px; 213 | font-weight: bold; 214 | } 215 | 216 | img.align-left, .figure.align-left, object.align-left { 217 | clear: left; 218 | float: left; 219 | margin-right: 1em; 220 | } 221 | 222 | img.align-right, .figure.align-right, object.align-right { 223 | clear: right; 224 | float: right; 225 | margin-left: 1em; 226 | } 227 | 228 | img.align-center, .figure.align-center, object.align-center { 229 | display: block; 230 | margin-left: auto; 231 | margin-right: auto; 232 | } 233 | 234 | .align-left { 235 | text-align: left; 236 | } 237 | 238 | .align-center { 239 | clear: both; 240 | text-align: center; 241 | } 242 | 243 | .align-right { 244 | text-align: right; 245 | } 246 | 247 | /* -- sidebars -------------------------------------------------------------- */ 248 | 249 | div.sidebar { 250 | margin: 0 0 0.5em 1em; 251 | border: 1px solid #ddb; 252 | padding: 7px 7px 0 7px; 253 | background-color: #ffe; 254 | width: 40%; 255 | float: right; 256 | } 257 | 258 | p.sidebar-title { 259 | font-weight: bold; 260 | } 261 | 262 | /* -- topics ---------------------------------------------------------------- */ 263 | 264 | div.topic { 265 | border: 1px solid #ccc; 266 | padding: 7px 7px 0 7px; 267 | margin: 10px 0 10px 0; 268 | } 269 | 270 | p.topic-title { 271 | font-size: 1.1em; 272 | font-weight: bold; 273 | margin-top: 10px; 274 | } 275 | 276 | /* -- admonitions ----------------------------------------------------------- */ 277 | 278 | div.admonition { 279 | margin-top: 10px; 280 | margin-bottom: 10px; 281 | padding: 7px; 282 | } 283 | 284 | div.admonition dt { 285 | font-weight: bold; 286 | } 287 | 288 | div.admonition dl { 289 | margin-bottom: 0; 290 | } 291 | 292 | p.admonition-title { 293 | margin: 0px 10px 5px 0px; 294 | font-weight: bold; 295 | } 296 | 297 | div.body p.centered { 298 | text-align: center; 299 | margin-top: 25px; 300 | } 301 | 302 | /* -- tables ---------------------------------------------------------------- */ 303 | 304 | table.docutils { 305 | border: 0; 306 | border-collapse: collapse; 307 | } 308 | 309 | table.docutils td, table.docutils th { 310 | padding: 1px 8px 1px 5px; 311 | border-top: 0; 312 | border-left: 0; 313 | border-right: 0; 314 | border-bottom: 1px solid #aaa; 315 | } 316 | 317 | table.field-list td, table.field-list th { 318 | border: 0 !important; 319 | } 320 | 321 | table.footnote td, table.footnote th { 322 | border: 0 !important; 323 | } 324 | 325 | th { 326 | text-align: left; 327 | padding-right: 5px; 328 | } 329 | 330 | table.citation { 331 | border-left: solid 1px gray; 332 | margin-left: 1px; 333 | } 334 | 335 | table.citation td { 336 | border-bottom: none; 337 | } 338 | 339 | /* -- other body styles ----------------------------------------------------- */ 340 | 341 | ol.arabic { 342 | list-style: decimal; 343 | } 344 | 345 | ol.loweralpha { 346 | list-style: lower-alpha; 347 | } 348 | 349 | ol.upperalpha { 350 | list-style: upper-alpha; 351 | } 352 | 353 | ol.lowerroman { 354 | list-style: lower-roman; 355 | } 356 | 357 | ol.upperroman { 358 | list-style: upper-roman; 359 | } 360 | 361 | dl { 362 | margin-bottom: 15px; 363 | } 364 | 365 | dd p { 366 | margin-top: 0px; 367 | } 368 | 369 | dd ul, dd table { 370 | margin-bottom: 10px; 371 | } 372 | 373 | dd { 374 | margin-top: 3px; 375 | margin-bottom: 10px; 376 | margin-left: 30px; 377 | } 378 | 379 | dt:target, .highlighted { 380 | background-color: #fbe54e; 381 | } 382 | 383 | dl.glossary dt { 384 | font-weight: bold; 385 | font-size: 1.1em; 386 | } 387 | 388 | .field-list ul { 389 | margin: 0; 390 | padding-left: 1em; 391 | } 392 | 393 | .field-list p { 394 | margin: 0; 395 | } 396 | 397 | .refcount { 398 | color: #060; 399 | } 400 | 401 | .optional { 402 | font-size: 1.3em; 403 | } 404 | 405 | .versionmodified { 406 | font-style: italic; 407 | } 408 | 409 | .system-message { 410 | background-color: #fda; 411 | padding: 5px; 412 | border: 3px solid red; 413 | } 414 | 415 | .footnote:target { 416 | background-color: #ffa; 417 | } 418 | 419 | .line-block { 420 | display: block; 421 | margin-top: 1em; 422 | margin-bottom: 1em; 423 | } 424 | 425 | .line-block .line-block { 426 | margin-top: 0; 427 | margin-bottom: 0; 428 | margin-left: 1.5em; 429 | } 430 | 431 | .guilabel, .menuselection { 432 | font-family: sans-serif; 433 | } 434 | 435 | .accelerator { 436 | text-decoration: underline; 437 | } 438 | 439 | .classifier { 440 | font-style: oblique; 441 | } 442 | 443 | /* -- code displays --------------------------------------------------------- */ 444 | 445 | pre { 446 | overflow: auto; 447 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 448 | } 449 | 450 | td.linenos pre { 451 | padding: 5px 0px; 452 | border: 0; 453 | background-color: transparent; 454 | color: #aaa; 455 | } 456 | 457 | table.highlighttable { 458 | margin-left: 0.5em; 459 | } 460 | 461 | table.highlighttable td { 462 | padding: 0 0.5em 0 0.5em; 463 | } 464 | 465 | tt.descname { 466 | background-color: transparent; 467 | font-weight: bold; 468 | font-size: 1.2em; 469 | } 470 | 471 | tt.descclassname { 472 | background-color: transparent; 473 | } 474 | 475 | tt.xref, a tt { 476 | background-color: transparent; 477 | font-weight: bold; 478 | } 479 | 480 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { 481 | background-color: transparent; 482 | } 483 | 484 | .viewcode-link { 485 | float: right; 486 | } 487 | 488 | .viewcode-back { 489 | float: right; 490 | font-family: sans-serif; 491 | } 492 | 493 | div.viewcode-block:target { 494 | margin: -1px -10px; 495 | padding: 0 10px; 496 | } 497 | 498 | /* -- math display ---------------------------------------------------------- */ 499 | 500 | img.math { 501 | vertical-align: middle; 502 | } 503 | 504 | div.body div.math p { 505 | text-align: center; 506 | } 507 | 508 | span.eqno { 509 | float: right; 510 | } 511 | 512 | /* -- printout stylesheet --------------------------------------------------- */ 513 | 514 | @media print { 515 | div.document, 516 | div.documentwrapper, 517 | div.bodywrapper { 518 | margin: 0 !important; 519 | width: 100%; 520 | } 521 | 522 | div.sphinxsidebar, 523 | div.related, 524 | div.footer, 525 | #top-link { 526 | display: none; 527 | } 528 | } 529 | -------------------------------------------------------------------------------- /debug_logging/static/debug_logging/css/rtd.css: -------------------------------------------------------------------------------- 1 | /* 2 | * rtd.css 3 | * ~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- sphinxdoc theme. Originally created by 6 | * Armin Ronacher for Werkzeug. 7 | * 8 | * Customized for ReadTheDocs by Eric Pierce & Eric Holscher 9 | * 10 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. 11 | * :license: BSD, see LICENSE for details. 12 | * 13 | */ 14 | 15 | @import url("basic.css"); 16 | 17 | /* PAGE LAYOUT -------------------------------------------------------------- */ 18 | 19 | body { 20 | font: 100%/1.5 "ff-meta-web-pro-1","ff-meta-web-pro-2",Arial,"Helvetica Neue",sans-serif; 21 | text-align: center; 22 | color: black; 23 | background-color: #465158; 24 | padding: 0; 25 | margin: 0; 26 | } 27 | 28 | div.document { 29 | text-align: left; 30 | background-color: #e8ecef; 31 | } 32 | 33 | div.bodywrapper { 34 | background-color: #ffffff; 35 | border-left: 1px solid #ccc; 36 | border-bottom: 1px solid #ccc; 37 | margin: 0 0 0 16em; 38 | } 39 | 40 | div.body { 41 | margin: 0; 42 | padding: 0.5em 1.3em; 43 | max-width: 55em; 44 | min-width: 20em; 45 | } 46 | 47 | div.related { 48 | font-size: 1em; 49 | background-color: #465158; 50 | } 51 | 52 | div.documentwrapper { 53 | float: left; 54 | width: 100%; 55 | background-color: #e8ecef; 56 | } 57 | 58 | 59 | /* HEADINGS --------------------------------------------------------------- */ 60 | 61 | h1 { 62 | margin: 0; 63 | padding: 0.7em 0 0.3em 0; 64 | font-size: 1.5em; 65 | line-height: 1.15; 66 | color: #111; 67 | clear: both; 68 | } 69 | 70 | h2 { 71 | margin: 2em 0 0.2em 0; 72 | font-size: 1.35em; 73 | padding: 0; 74 | color: #465158; 75 | } 76 | 77 | h3 { 78 | margin: 1em 0 -0.3em 0; 79 | font-size: 1.2em; 80 | color: #6c818f; 81 | } 82 | 83 | div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { 84 | color: black; 85 | } 86 | 87 | h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { 88 | display: none; 89 | margin: 0 0 0 0.3em; 90 | padding: 0 0.2em 0 0.2em; 91 | color: #aaa !important; 92 | } 93 | 94 | h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, 95 | h5:hover a.anchor, h6:hover a.anchor { 96 | display: inline; 97 | } 98 | 99 | h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, 100 | h5 a.anchor:hover, h6 a.anchor:hover { 101 | color: #777; 102 | background-color: #eee; 103 | } 104 | 105 | 106 | /* LINKS ------------------------------------------------------------------ */ 107 | 108 | /* Normal links get a pseudo-underline */ 109 | a { 110 | color: #444; 111 | text-decoration: none; 112 | border-bottom: 1px solid #ccc; 113 | } 114 | 115 | /* Links in sidebar, TOC, index trees and tables have no underline */ 116 | .sphinxsidebar a, 117 | .toctree-wrapper a, 118 | .indextable a, 119 | #indices-and-tables a { 120 | color: #444; 121 | text-decoration: none; 122 | border-bottom: none; 123 | } 124 | 125 | /* Most links get an underline-effect when hovered */ 126 | a:hover, 127 | div.toctree-wrapper a:hover, 128 | .indextable a:hover, 129 | #indices-and-tables a:hover { 130 | color: #111; 131 | text-decoration: none; 132 | border-bottom: 1px solid #111; 133 | } 134 | 135 | /* Footer links */ 136 | div.footer a { 137 | color: #86989B; 138 | text-decoration: none; 139 | border: none; 140 | } 141 | div.footer a:hover { 142 | color: #a6b8bb; 143 | text-decoration: underline; 144 | border: none; 145 | } 146 | 147 | /* Permalink anchor (subtle grey with a red hover) */ 148 | div.body a.headerlink { 149 | color: #ccc; 150 | font-size: 1em; 151 | margin-left: 6px; 152 | padding: 0 4px 0 4px; 153 | text-decoration: none; 154 | border: none; 155 | } 156 | div.body a.headerlink:hover { 157 | color: #c60f0f; 158 | border: none; 159 | } 160 | 161 | 162 | /* NAVIGATION BAR --------------------------------------------------------- */ 163 | 164 | div.related ul { 165 | height: 2.5em; 166 | } 167 | 168 | div.related ul li { 169 | margin: 0; 170 | padding: 0.65em 0; 171 | float: left; 172 | display: block; 173 | color: white; /* For the >> separators */ 174 | font-size: 0.8em; 175 | } 176 | 177 | div.related ul li.right { 178 | float: right; 179 | margin-right: 5px; 180 | color: transparent; /* Hide the | separators */ 181 | } 182 | 183 | /* "Breadcrumb" links in nav bar */ 184 | div.related ul li a { 185 | order: none; 186 | background-color: inherit; 187 | font-weight: bold; 188 | margin: 6px 0 6px 4px; 189 | line-height: 1.75em; 190 | color: #ffffff; 191 | padding: 0.4em 0.8em; 192 | border: none; 193 | border-radius: 3px; 194 | } 195 | /* previous / next / modules / index links look more like buttons */ 196 | div.related ul li.right a { 197 | margin: 0.375em 0; 198 | background-color: #697983; 199 | text-shadow: 0 1px rgba(0, 0, 0, 0.5); 200 | border-radius: 3px; 201 | -webkit-border-radius: 3px; 202 | -moz-border-radius: 3px; 203 | } 204 | /* All navbar links light up as buttons when hovered */ 205 | div.related ul li a:hover { 206 | background-color: #8ca1af; 207 | color: #ffffff; 208 | text-decoration: none; 209 | border-radius: 3px; 210 | -webkit-border-radius: 3px; 211 | -moz-border-radius: 3px; 212 | } 213 | /* Take extra precautions for tt within links */ 214 | a tt, 215 | div.related ul li a tt { 216 | background: inherit !important; 217 | color: inherit !important; 218 | } 219 | 220 | 221 | /* SIDEBAR ---------------------------------------------------------------- */ 222 | 223 | div.sphinxsidebarwrapper { 224 | padding: 0; 225 | } 226 | 227 | div.sphinxsidebar { 228 | margin: 0; 229 | margin-left: -100%; 230 | float: left; 231 | top: 3em; 232 | left: 0; 233 | padding: 0 1em; 234 | width: 14em; 235 | font-size: 1em; 236 | text-align: left; 237 | background-color: #e8ecef; 238 | } 239 | 240 | div.sphinxsidebar img { 241 | max-width: 12em; 242 | } 243 | 244 | div.sphinxsidebar h3, div.sphinxsidebar h4 { 245 | margin: 1.2em 0 0.3em 0; 246 | font-size: 1em; 247 | padding: 0; 248 | color: #222222; 249 | font-family: "ff-meta-web-pro-1", "ff-meta-web-pro-2", "Arial", "Helvetica Neue", sans-serif; 250 | } 251 | 252 | div.sphinxsidebar h3 a { 253 | color: #444444; 254 | } 255 | 256 | div.sphinxsidebar ul, 257 | div.sphinxsidebar p { 258 | margin-top: 0; 259 | padding-left: 0; 260 | line-height: 130%; 261 | background-color: #e8ecef; 262 | } 263 | 264 | /* No bullets for nested lists, but a little extra indentation */ 265 | div.sphinxsidebar ul ul { 266 | list-style-type: none; 267 | margin-left: 1.5em; 268 | padding: 0; 269 | } 270 | 271 | /* A little top/bottom padding to prevent adjacent links' borders 272 | * from overlapping each other */ 273 | div.sphinxsidebar ul li { 274 | padding: 1px 0; 275 | } 276 | 277 | /* A little left-padding to make these align with the ULs */ 278 | div.sphinxsidebar p.topless { 279 | padding-left: 0 0 0 1em; 280 | } 281 | 282 | /* Make these into hidden one-liners */ 283 | div.sphinxsidebar ul li, 284 | div.sphinxsidebar p.topless { 285 | white-space: nowrap; 286 | overflow: hidden; 287 | } 288 | /* ...which become visible when hovered */ 289 | div.sphinxsidebar ul li:hover, 290 | div.sphinxsidebar p.topless:hover { 291 | overflow: visible; 292 | } 293 | 294 | /* Search text box and "Go" button */ 295 | #searchbox { 296 | margin-top: 2em; 297 | margin-bottom: 1em; 298 | background: #ddd; 299 | padding: 0.5em; 300 | border-radius: 6px; 301 | -moz-border-radius: 6px; 302 | -webkit-border-radius: 6px; 303 | } 304 | #searchbox h3 { 305 | margin-top: 0; 306 | } 307 | 308 | /* Make search box and button abut and have a border */ 309 | input, 310 | div.sphinxsidebar input { 311 | border: 1px solid #999; 312 | float: left; 313 | } 314 | 315 | /* Search textbox */ 316 | input[type="text"] { 317 | margin: 0; 318 | padding: 0 3px; 319 | height: 20px; 320 | width: 144px; 321 | border-top-left-radius: 3px; 322 | border-bottom-left-radius: 3px; 323 | -moz-border-radius-topleft: 3px; 324 | -moz-border-radius-bottomleft: 3px; 325 | -webkit-border-top-left-radius: 3px; 326 | -webkit-border-bottom-left-radius: 3px; 327 | } 328 | /* Search button */ 329 | input[type="submit"] { 330 | margin: 0 0 0 -1px; /* -1px prevents a double-border with textbox */ 331 | height: 22px; 332 | color: #444; 333 | background-color: #e8ecef; 334 | padding: 1px 4px; 335 | font-weight: bold; 336 | border-top-right-radius: 3px; 337 | border-bottom-right-radius: 3px; 338 | -moz-border-radius-topright: 3px; 339 | -moz-border-radius-bottomright: 3px; 340 | -webkit-border-top-right-radius: 3px; 341 | -webkit-border-bottom-right-radius: 3px; 342 | } 343 | input[type="submit"]:hover { 344 | color: #ffffff; 345 | background-color: #8ecc4c; 346 | } 347 | 348 | div.sphinxsidebar p.searchtip { 349 | clear: both; 350 | padding: 0.5em 0 0 0; 351 | background: #ddd; 352 | color: #666; 353 | font-size: 0.9em; 354 | } 355 | 356 | /* Sidebar links are unusual */ 357 | div.sphinxsidebar li a, 358 | div.sphinxsidebar p a { 359 | background: #e8ecef; /* In case links overlap main content */ 360 | border-radius: 3px; 361 | -moz-border-radius: 3px; 362 | -webkit-border-radius: 3px; 363 | border: 1px solid transparent; /* To prevent things jumping around on hover */ 364 | padding: 0 5px 0 5px; 365 | } 366 | div.sphinxsidebar li a:hover, 367 | div.sphinxsidebar p a:hover { 368 | color: #111; 369 | text-decoration: none; 370 | border: 1px solid #888; 371 | } 372 | 373 | /* Tweak any link appearing in a heading */ 374 | div.sphinxsidebar h3 a { 375 | } 376 | 377 | 378 | 379 | 380 | /* OTHER STUFF ------------------------------------------------------------ */ 381 | 382 | cite, code, tt { 383 | font-family: 'Consolas', 'Deja Vu Sans Mono', 384 | 'Bitstream Vera Sans Mono', monospace; 385 | font-size: 0.95em; 386 | letter-spacing: 0.01em; 387 | } 388 | 389 | tt { 390 | background-color: #f2f2f2; 391 | color: #444; 392 | } 393 | 394 | tt.descname, tt.descclassname, tt.xref { 395 | border: 0; 396 | } 397 | 398 | hr { 399 | border: 1px solid #abc; 400 | margin: 2em; 401 | } 402 | 403 | 404 | pre, #_fontwidthtest { 405 | font-family: 'Consolas', 'Deja Vu Sans Mono', 406 | 'Bitstream Vera Sans Mono', monospace; 407 | margin: 1em 2em; 408 | font-size: 0.95em; 409 | letter-spacing: 0.015em; 410 | line-height: 120%; 411 | padding: 0.5em; 412 | border: 1px solid #ccc; 413 | background-color: #eee; 414 | border-radius: 6px; 415 | -moz-border-radius: 6px; 416 | -webkit-border-radius: 6px; 417 | } 418 | 419 | pre a { 420 | color: inherit; 421 | text-decoration: underline; 422 | } 423 | 424 | td.linenos pre { 425 | margin: 1em 0em; 426 | } 427 | 428 | td.code pre { 429 | margin: 1em 0em; 430 | } 431 | 432 | div.quotebar { 433 | background-color: #f8f8f8; 434 | max-width: 250px; 435 | float: right; 436 | padding: 2px 7px; 437 | border: 1px solid #ccc; 438 | } 439 | 440 | div.topic { 441 | background-color: #f8f8f8; 442 | } 443 | 444 | table { 445 | border-collapse: collapse; 446 | margin: 0 -0.5em 0 -0.5em; 447 | } 448 | 449 | table td, table th { 450 | padding: 0.2em 0.5em 0.2em 0.5em; 451 | } 452 | 453 | 454 | /* ADMONITIONS AND WARNINGS ------------------------------------------------- */ 455 | 456 | /* Shared by admonitions, warnings and sidebars */ 457 | div.admonition, 458 | div.warning, 459 | div.sidebar { 460 | font-size: 0.9em; 461 | margin: 2em; 462 | padding: 0; 463 | /* 464 | border-radius: 6px; 465 | -moz-border-radius: 6px; 466 | -webkit-border-radius: 6px; 467 | */ 468 | } 469 | div.admonition p, 470 | div.warning p, 471 | div.sidebar p { 472 | margin: 0.5em 1em 0.5em 1em; 473 | padding: 0; 474 | } 475 | div.admonition pre, 476 | div.warning pre, 477 | div.sidebar pre { 478 | margin: 0.4em 1em 0.4em 1em; 479 | } 480 | div.admonition p.admonition-title, 481 | div.warning p.admonition-title, 482 | div.sidebar p.sidebar-title { 483 | margin: 0; 484 | padding: 0.1em 0 0.1em 0.5em; 485 | color: white; 486 | font-weight: bold; 487 | font-size: 1.1em; 488 | text-shadow: 0 1px rgba(0, 0, 0, 0.5); 489 | } 490 | div.admonition ul, div.admonition ol, 491 | div.warning ul, div.warning ol, 492 | div.sidebar ul, div.sidebar ol { 493 | margin: 0.1em 0.5em 0.5em 3em; 494 | padding: 0; 495 | } 496 | 497 | 498 | /* Admonitions and sidebars only */ 499 | div.admonition, div.sidebar { 500 | border: 1px solid #609060; 501 | background-color: #e9ffe9; 502 | } 503 | div.admonition p.admonition-title, 504 | div.sidebar p.sidebar-title { 505 | background-color: #70A070; 506 | border-bottom: 1px solid #609060; 507 | } 508 | 509 | 510 | /* Warnings only */ 511 | div.warning { 512 | border: 1px solid #900000; 513 | background-color: #ffe9e9; 514 | } 515 | div.warning p.admonition-title { 516 | background-color: #b04040; 517 | border-bottom: 1px solid #900000; 518 | } 519 | 520 | 521 | /* Sidebars only */ 522 | div.sidebar { 523 | max-width: 200px; 524 | } 525 | 526 | 527 | 528 | div.versioninfo { 529 | margin: 1em 0 0 0; 530 | border: 1px solid #ccc; 531 | background-color: #DDEAF0; 532 | padding: 8px; 533 | line-height: 1.3em; 534 | font-size: 0.9em; 535 | } 536 | 537 | .viewcode-back { 538 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 539 | 'Verdana', sans-serif; 540 | } 541 | 542 | div.viewcode-block:target { 543 | background-color: #f4debf; 544 | border-top: 1px solid #ac9; 545 | border-bottom: 1px solid #ac9; 546 | } 547 | 548 | dl { 549 | margin: 1em 0 2.5em 0; 550 | } 551 | 552 | /* Highlight target when you click an internal link */ 553 | dt:target { 554 | background: #ffe080; 555 | } 556 | /* Don't highlight whole divs */ 557 | div.highlight { 558 | background: transparent; 559 | } 560 | /* But do highlight spans (so search results can be highlighted) */ 561 | span.highlight { 562 | background: #ffe080; 563 | } 564 | 565 | div.footer { 566 | background-color: #465158; 567 | color: #eeeeee; 568 | padding: 0 2em 2em 2em; 569 | clear: both; 570 | font-size: 0.8em; 571 | text-align: center; 572 | } 573 | 574 | p { 575 | margin: 0.8em 0 0.5em 0; 576 | } 577 | 578 | .section p img { 579 | margin: 1em 2em; 580 | } 581 | -------------------------------------------------------------------------------- /debug_logging/static/debug_logging/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * style.css 3 | * ~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2011 by Lincoln Loop, see AUTHORS. 6 | * :license: BSD, see LICENSE for details. 7 | * 8 | */ 9 | 10 | @import url("rtd.css"); 11 | 12 | .button { 13 | cursor: pointer; 14 | background-color: #697983; 15 | font-size: 0.8em; 16 | font-weight: bold; 17 | line-height: 1.75em; 18 | color: #fff; 19 | margin: 0; 20 | padding: 0.4em 0.8em; 21 | border: none; 22 | border-radius: 3px; 23 | -webkit-border-radius: 3px; 24 | -moz-border-radius: 3px; 25 | text-shadow: 0 1px rgba(0, 0, 0, 0.5); 26 | } 27 | 28 | .button:hover { 29 | background-color: #8ca1af; 30 | color: #fff; 31 | text-decoration: none; 32 | border-bottom: none; 33 | } 34 | 35 | .button tt { 36 | background: inherit !important; 37 | color: inherit !important; 38 | } 39 | 40 | .footer { 41 | margin-top: 1em; 42 | } 43 | 44 | /* INDEX ------------------------------------------------------------------ */ 45 | 46 | #test-run-summaries th { 47 | font-size: .8em; 48 | } 49 | 50 | #test-run-summaries th.select-all { 51 | font-size: 1em; 52 | } 53 | 54 | #runs-form input[type=submit] { 55 | float: none; 56 | margin: 10px 1px; 57 | } 58 | 59 | /* LOG RUNS --------------------------------------------------------------- */ 60 | 61 | .expandlink { 62 | margin-right: 1em; 63 | } 64 | 65 | #thresholds input { 66 | float: none; 67 | } 68 | 69 | /* --- CSS taken (and somewhat modified) from Django-Sentry --- */ 70 | 71 | /* messages generic */ 72 | .count { background: #000; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; padding: 0 5px; margin-right: 3px; line-height: 19px; color: #fff; display: inline-block; } 73 | .count-digits-4 { font-size: 0.8em; } 74 | .count-digits-5 { font-size: 0.7em; } 75 | 76 | /* message list */ 77 | .messages { 78 | list-style: none; 79 | margin-left: 0; 80 | -webkit-padding-start: 0; 81 | } 82 | .messages li { list-style: none; } 83 | .messages .no-messages { 84 | font-weight: bold; 85 | } 86 | .messages .count { 87 | /* width: 28px;*/ 88 | vertical-align: top; 89 | text-align: center; 90 | -webkit-border-radius: 5px; 91 | -moz-border-radius: 5px; 92 | border-radius: 5px; 93 | padding: 4px; 94 | line-height: 19px; 95 | color: #fff; 96 | display: inline-block; 97 | margin-right: 5px; 98 | } 99 | .messages .count a { text-decoration: none; } 100 | .messages h3 { 101 | vertical-align: middle; 102 | font-weight: bold; 103 | line-height: 24px; 104 | display: inline-block; 105 | margin: 0; 106 | max-width: 80%; 107 | } 108 | .messages h3 a { text-decoration: none; } 109 | .messages .message { 110 | color: #666; 111 | margin-top: 8px; 112 | font-size: 0.9em; 113 | margin-bottom: 0; 114 | overflow: hidden; 115 | white-space: nowrap; 116 | } 117 | 118 | .messages .tag { display: inline-block; padding: 2px 5px; background: #eaeaea; margin-right: 5px;} 119 | .messages .tag.warn { background: #CF070F; color: white;} 120 | .messages .traceback { display: none; } 121 | .messages li { 122 | padding: 8px; 123 | position: relative; 124 | overflow: hidden; 125 | margin-bottom: 1em; 126 | -webkit-border-radius: 5px; 127 | -moz-border-radius: 5px; 128 | border-radius: 5px; 129 | } 130 | .messages .row1 { background: #f9f9f9; } 131 | .messages .hidden { 132 | position: absolute; 133 | right: 15px; 134 | top: 21px; 135 | visibility: hidden;; 136 | } 137 | .messages li:hover .hidden { 138 | visibility: visible; 139 | } 140 | 141 | .messages .row_link { 142 | top: 0; 143 | left: 0; 144 | width: 100%; 145 | height: 100%; 146 | position: absolute; 147 | } 148 | 149 | .messages .last_seen { 150 | color: #bbb; 151 | font-size: 0.9em; 152 | margin-left: 5px; 153 | white-space: nowrap; 154 | vertical-align: middle; 155 | display: inline-block; 156 | } 157 | .messages .status { margin-left: 5px; text-transform: uppercase; font-size: 0.9em; } 158 | .messages .status-0 { display: none; } 159 | .messages .status-1 { color: red; } 160 | .messages li:hover, .messages .active { 161 | background: #eaeaea; 162 | } 163 | .messages li:hover .tag { background: #fff; } 164 | .messages li:hover .tag.warn { background: #F10710; } 165 | .messages li:hover h3 a { 166 | color: #0099dc; 167 | } 168 | .messages li:hover .last_seen, .messages .active .last_seen { color: #777; } 169 | 170 | .priority-veryhigh .count { 171 | background: #e22e2e; 172 | -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.2); 173 | -moz-box-shadow: 0 1px 2px rgba(0,0,0,.2); 174 | box-shadow: 0 1px 2px rgba(0,0,0,.2); 175 | background:-moz-linear-gradient(center top , #ED1C24, #AA1317) repeat scroll 0 0 transparent; 176 | background:-webkit-gradient(linear, 0% 0%, 0% 100%, from(#ED1C24), to(#AA1317)); 177 | } 178 | .priority-high .count { 179 | background: #ff5400; 180 | background:-moz-linear-gradient(center top , #F16C7C, #BF404F) repeat scroll 0 0 transparent; 181 | background:-webkit-gradient(linear, 0% 0%, 0% 100%, from(#F16C7C), to(#BF404F)); 182 | } 183 | .priority-medium .count { 184 | background: #ff7800; 185 | background:-moz-linear-gradient(center top , #FAA51A, #F47A20) repeat scroll 0 0 transparent; 186 | background:-webkit-gradient(linear, 0% 0%, 0% 100%, from(#FAA51A), to(#F47A20)); 187 | } 188 | .priority-low .count, 189 | .priority-verylow .count { 190 | background: #ffb400; 191 | background:-moz-linear-gradient(center top , #efd57c, #e0ae00) repeat scroll 0 0 transparent; 192 | background:-webkit-gradient(linear, 0% 0%, 0% 100%, from(#efd57c), to(#e0ae00)); 193 | } 194 | 195 | /* --- End CSS taken from Django-Sentry --- */ -------------------------------------------------------------------------------- /debug_logging/static/debug_logging/img/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lincolnloop/django-debug-logging/dc2f60b5abaf5f344de45a791f3a3473873ae304/debug_logging/static/debug_logging/img/search.png -------------------------------------------------------------------------------- /debug_logging/static/debug_logging/js/expandable.js: -------------------------------------------------------------------------------- 1 | /* Handles expandable elements */ 2 | 3 | $(function() { 4 | $('.expandable').each(function() { 5 | $(this).hide(); 6 | $('Expand').insertBefore(this).click(function() { 7 | $(this).next('.expandable').fadeToggle('fast'); 8 | if ($(this).text() == 'Expand') { 9 | $(this).text('Hide'); 10 | } else { 11 | $(this).text('Expand'); 12 | } 13 | }); 14 | }); 15 | }); -------------------------------------------------------------------------------- /debug_logging/static/debug_logging/js/highlights.js: -------------------------------------------------------------------------------- 1 | /* Handles highlights */ 2 | 3 | $(function() { 4 | function highlight_results() { 5 | time = parseFloat($('#time-threshold').val()); 6 | queries = parseInt($('#sql-threshold').val()); 7 | 8 | $('.messages .row1').each(function() { 9 | if (parseFloat($('.response-time', this).text()) > time) { 10 | $(this).removeClass('priority-low'); 11 | $(this).addClass('priority-veryhigh'); 12 | } else { 13 | $(this).removeClass('priority-veryhigh'); 14 | $(this).addClass('priority-low'); 15 | } 16 | if (parseInt($('.num-queries', this).text()) > queries) { 17 | $('.num-queries', this).parent().addClass('warn'); 18 | } else { 19 | $('.num-queries', this).parent().removeClass('warn'); 20 | } 21 | }); 22 | } 23 | 24 | $('#time-threshold').keyup(function() { 25 | clearTimeout(this.highlightTimer) 26 | this.highlightTimer = setTimeout(highlight_results, 250); 27 | }); 28 | $('#sql-threshold').keyup(function() { 29 | clearTimeout(this.highlightTimer) 30 | this.highlightTimer = setTimeout(highlight_results, 250); 31 | }); 32 | 33 | highlight_results(); 34 | }); 35 | -------------------------------------------------------------------------------- /debug_logging/static/debug_logging/js/test_run.js: -------------------------------------------------------------------------------- 1 | /* Handles test run controls */ 2 | 3 | $(function() { 4 | $('#start-run').click(function() { 5 | 6 | }); 7 | 8 | $(".select-all input").click(function(e) { 9 | var el = $(this); 10 | el.closest('table').find("input[name=run_id]").attr("checked", el.attr("checked")); 11 | }); 12 | 13 | $("#runs-form").submit(function(e) { 14 | return window.confirm("Do you really want to delete all selected runs? This action cannot be undone"); 15 | }); 16 | }); -------------------------------------------------------------------------------- /debug_logging/templates/debug_logging/_inline_record.html: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | 4 | {{ record.timer_total|floatformat:"-2" }} 5 | 6 | ms 7 | 8 |

    9 | {% if not record_class %} 10 | 11 | {{ record.request_path}} 12 | 13 | {% else %} 14 | {{ record.request_path}} 15 | {% endif %} 16 |

    17 | 18 | {{ record.timestamp|date:"M dS, g:i:s a" }} 19 | 20 |

    21 | 22 | CPU user {{ record.timer_utime|floatformat:"-2" }} ms / system {{ record.timer_stime|floatformat:"-2" }} ms 23 | 24 | 25 | 26 | {{ record.sql_num_queries }} 27 | 28 | SQL queries in {{ record.sql_time|floatformat:"-2" }} ms 29 | 30 | 31 | {{ record.cache_hits|default:"no" }} cache hits / {{ record.cache_misses|default:"no" }} misses in {{ record.cache_time|floatformat:"-2" }} ms 32 | 33 |

    34 | {% if not record_class %} 35 | 36 | {% endif %} 37 |
  • 38 | -------------------------------------------------------------------------------- /debug_logging/templates/debug_logging/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% load static %} 4 | 5 | 6 | 7 | 8 | {% block title %}{% endblock %} | Django Debug Logging 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 |
    24 |
    25 |
    26 |
    27 | {% block body %}{% endblock %} 28 |
    29 |
    30 |
    31 |
    32 |
    33 | {% block sidebar %}{% endblock %} 34 |
    35 |
    36 |
    37 |
    38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | {% block extra_js %}{% endblock %} 50 | 51 | 52 | -------------------------------------------------------------------------------- /debug_logging/templates/debug_logging/index.html: -------------------------------------------------------------------------------- 1 | {% extends "debug_logging/base.html" %} 2 | {% load static %} 3 | 4 | {% block title %}Home{% endblock %} 5 | 6 | {% block sidebar %} 7 |

    Test Runs

    8 |
      9 | {% for run in all_test_runs %} 10 |
    • {{ run }}
    • 11 | {% empty %} 12 |
    • (none currently available)
    • 13 | {% endfor %} 14 |
    15 | {% endblock %} 16 | 17 | {% block body %} 18 |

    Django Debug Logging

    19 |

    Select a test run below to review the results.

    20 |

    Test Runs

    21 |
    22 | {% csrf_token %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% for run in all_test_runs %} 37 | 38 | {{ run.set_aggregates|default:"" }} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {% empty %} 48 | 49 | {% endfor %} 50 | 51 |
    Name/DateTotal RequestsAvg. TimeAvg. Sql TimeAvg. QueriesTotal Queries
    {{ run }}{{ run.total_requests }}{{ run.avg_time|floatformat:"-2" }}{{ run.avg_sql_time|floatformat:"-2" }}{{ run.avg_sql_queries|floatformat:"-2" }}{{ run.total_sql_queries }}
    There are no test runs available to review.
    52 | 53 |
    54 | {% endblock %} 55 | 56 | {% block extra_js %} 57 | 58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /debug_logging/templates/debug_logging/record_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "debug_logging/base.html" %} 2 | 3 | {% block title %}Log Record: {{ record.request_path }}{% endblock %} 4 | 5 | {% block breadcrumbs %} 6 |
  • »Test Run: {{ test_run }}
  • 7 |
  • »Log Record: {{ record.request_path }}
  • 8 | {% endblock %} 9 | 10 | {% block sidebar %} 11 |

    Project

    12 |

    {{ record.test_run.project_name|default:"(Not recorded)" }}

    13 |

    Hostname

    14 |

    {{ record.test_run.hostname|default:"(Not recorded)" }} 15 |

    Revision

    16 |

    {{ record.test_run.revision|default:"(Not recorded)" }} 17 | {% endblock %} 18 | 19 | {% block body %} 20 |

      21 | {% with "active" as record_class %} 22 | {% include "debug_logging/_inline_record.html" %} 23 | {% endwith %} 24 |
    25 | 26 | 27 | 28 | 29 | 41 | 42 |
    REQUEST PATH{{ record.request_path }}
    SETTINGS 30 | 31 | 32 | 33 | 34 | 35 | {% for var in record.settings.items|dictsort:"0" %} 36 | 37 | {% endfor %} 38 | 39 | 40 |
    43 |

    Timer Stats

    44 | 45 | 46 | 47 | 48 | 49 | 50 |
    USER CPU TIME{{ record.timer_utime|floatformat:"-2" }} ms
    SYSTEM CPU TIME{{ record.timer_stime|floatformat:"-2" }} ms
    TOTAL CPU TIME{{ record.timer_cputime|floatformat:"-2" }} ms
    ELAPSED TIME{{ record.timer_total|floatformat:"-2" }} ms
    CONTEXT SWITCHES{{ record.timer_vcsw }} voluntary, {{ record.timer_ivcsw }} involuntary
    51 |

    SQL Stats

    52 | 53 | 54 | 55 | 56 | 71 | 72 |
    QUERIES{{ record.sql_num_queries }}
    TIME{{ record.sql_time|floatformat:"-2" }} ms
    INDIVIDUAL QUERIES 57 | 58 | 59 | 60 | 61 | 62 | {% for query in record.sql_queries %} 63 | 64 | 65 | 66 | 67 | {% endfor %} 68 | 69 | 70 |
    73 |

    Cache Stats

    74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | {# Todo: create a click-to-view item for full Cache call data #} 84 |
    CALLS{{ record.cache_num_calls }}
    TIME{{ record.cache_time|floatformat:"-2" }} ms
    HITS{{ record.cache_hits }}
    MISSES{{ record.cache_misses }}
    SETS{{ record.cache_sets }}
    GETS{{ record.cache_gets }}
    GET MANY{{ record.cache_get_many }}
    DELETES{{ record.cache_deletes }}
    85 | {% endblock %} 86 | 87 | -------------------------------------------------------------------------------- /debug_logging/templates/debug_logging/run_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "debug_logging/base.html" %} 2 | 3 | {% block title %}Test Run: {{ test_run }}{% endblock %} 4 | 5 | {% block breadcrumbs %} 6 |
  • »Test Run: {{ test_run }}
  • 7 | {% endblock %} 8 | 9 | {% block sidebar %} 10 |
    11 |

    Aggregated stats:

    12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
    Avg Elapsed Time{{ test_run.avg_time|floatformat:"-2" }} ms
    Avg CPU Time{{ test_run.avg_cpu_time|floatformat:"-2" }} ms
    Avg SQL Time{{ test_run.avg_sql_time|floatformat:"-2" }} ms
    Avg Queries{{ test_run.avg_sql_queries|floatformat:"-2" }}
    Max Queries{{ test_run.max_sql_queries }}
    Total Queries{{ test_run.total_sql_queries }}
    40 |

    Sort by:

    41 |
    42 | 48 |
    49 |

    Highlight thresholds:

    50 |
    51 |

    52 | 53 | 54 |

    55 |

    56 | 57 | 58 |

    59 |
    60 |
    61 | {% endblock %} 62 | 63 | {% block body %} 64 |

    Test Run: {{ test_run }}

    65 | 66 |
      67 | {% for record in page.object_list %} 68 | {% include "debug_logging/_inline_record.html" %} 69 | {% endfor %} 70 |
    71 | 72 | {% if page.has_previous or page.has_next %} 73 |
    74 |
    Page {{ page.number }}
    75 |
      76 | {% if page.has_previous %} 77 |
    • 78 | Previous Page 79 |
    • 80 | {% else %} 81 |
    • Previous Page
    • 82 | {% endif %} 83 | {% if page.has_next %} 84 |
    • 85 | Next Page 86 |
    • 87 | {% else %} 88 |
    • Next Page
    • 89 | {% endif %} 90 |
    91 |
    92 | {% endif %} 93 | {% endblock %} 94 | -------------------------------------------------------------------------------- /debug_logging/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, url 2 | 3 | 4 | urlpatterns = patterns('debug_logging.views', 5 | url(r'^$', 'index', name='debug_logging_index'), 6 | url(r'^delete$', 'delete_runs', name='debug_logging_delete_runs'), 7 | url(r'^run/(\d+)/$', 'run_detail', name='debug_logging_run_detail'), 8 | url(r'^record/(\d+)/$', 'record_detail', name='debug_logging_record_detail'), 9 | ) 10 | -------------------------------------------------------------------------------- /debug_logging/utils.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import platform 3 | import subprocess 4 | 5 | from django.conf import settings 6 | from django.utils.importlib import import_module 7 | 8 | 9 | def get_project_name(): 10 | return settings.SETTINGS_MODULE.split('.')[0] 11 | 12 | 13 | def get_hostname(): 14 | return platform.node() 15 | 16 | 17 | def get_revision(): 18 | vcs = getattr(settings, 'DEBUG_TOOLBAR_CONFIG', {}).get('VCS', None) 19 | if vcs == 'git': 20 | module = import_module(settings.SETTINGS_MODULE) 21 | path = os.path.realpath(os.path.dirname(module.__file__)) 22 | cmd = 'cd %s && git rev-parse --verify --short HEAD' % path 23 | proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) 24 | proc_stdout, proc_stderr = proc.communicate() 25 | return proc_stdout 26 | 27 | 28 | def import_from_string(path): 29 | i = path.rfind('.') 30 | module, attr = path[:i], path[i + 1:] 31 | try: 32 | mod = import_module(module) 33 | except ImportError, e: 34 | raise ImproperlyConfigured( 35 | 'Error importing module %s: "%s"' % (module, e) 36 | ) 37 | try: 38 | instance = getattr(mod, attr) 39 | except AttributeError: 40 | raise ImproperlyConfigured( 41 | 'Module "%s" does not define a "%s" attribute' % (module, attr) 42 | ) 43 | return instance 44 | -------------------------------------------------------------------------------- /debug_logging/views.py: -------------------------------------------------------------------------------- 1 | from django.core.paginator import Paginator 2 | 3 | from django.shortcuts import get_object_or_404, render_to_response, redirect 4 | from django.template import RequestContext 5 | 6 | from debug_logging.models import DebugLogRecord, TestRun 7 | 8 | RECORDS_PER_PAGE = 50 9 | 10 | 11 | def _get_all_test_runs(): 12 | return TestRun.objects.all() 13 | 14 | 15 | def index(request): 16 | return render_to_response("debug_logging/index.html", { 17 | 'all_test_runs': _get_all_test_runs(), 18 | }, context_instance=RequestContext(request)) 19 | 20 | def delete_runs(request): 21 | if request.method == "POST": 22 | runs = map(int, request.POST.getlist("run_id")) 23 | 24 | TestRun.objects.filter(pk__in = runs).delete() 25 | 26 | return redirect("debug_logging_index") 27 | 28 | def run_detail(request, run_id): 29 | test_run = get_object_or_404(TestRun, id=run_id) 30 | 31 | sort = request.GET.get('sort') 32 | 33 | if sort == 'response_time': 34 | order_by = '-timer_total' 35 | elif sort == 'sql_queries': 36 | order_by = '-sql_num_queries' 37 | elif sort == 'sql_time': 38 | order_by = '-sql_time' 39 | else: 40 | order_by = '-timestamp' 41 | 42 | test_run.set_aggregates() 43 | 44 | p = Paginator(test_run.records.order_by(order_by), RECORDS_PER_PAGE) 45 | try: 46 | page_num = int(request.GET.get('p', 1)) 47 | except ValueError: 48 | page_num = 1 49 | page = p.page(page_num) 50 | 51 | return render_to_response("debug_logging/run_detail.html", { 52 | 'page': page, 53 | 'test_run': test_run, 54 | 'all_test_runs': _get_all_test_runs(), 55 | }, context_instance=RequestContext(request)) 56 | 57 | 58 | def record_detail(request, record_id): 59 | record = get_object_or_404(DebugLogRecord, pk=record_id) 60 | return render_to_response("debug_logging/record_detail.html", { 61 | 'test_run': record.test_run, 62 | 'record': record, 63 | 'all_test_runs': _get_all_test_runs(), 64 | }, context_instance=RequestContext(request)) 65 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-debug-logging.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-debug-logging.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-debug-logging" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-debug-logging" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-debug-logging documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Nov 30 09:06:54 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | DOCS_BASE = os.path.dirname(__file__) 22 | sys.path.insert(0, os.path.abspath(os.path.join(DOCS_BASE, '..'))) 23 | 24 | import debug_logging 25 | 26 | # -- General configuration ----------------------------------------------------- 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | #needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be extensions 32 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 33 | extensions = ['sphinx.ext.autodoc'] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The suffix of source filenames. 39 | source_suffix = '.rst' 40 | 41 | # The encoding of source files. 42 | #source_encoding = 'utf-8-sig' 43 | 44 | # The master toctree document. 45 | master_doc = 'index' 46 | 47 | # General information about the project. 48 | project = u'django-debug-logging' 49 | copyright = u'2011, Lincoln Loop' 50 | 51 | # The version info for the project you're documenting, acts as replacement for 52 | # |version| and |release|, also used in various other places throughout the 53 | # built documents. 54 | # 55 | # The short X.Y version. 56 | version = debug_logging.get_version(short=True) 57 | # The full version, including alpha/beta/rc tags. 58 | release = debug_logging.__version__ 59 | 60 | # The language for content autogenerated by Sphinx. Refer to documentation 61 | # for a list of supported languages. 62 | #language = None 63 | 64 | # There are two options for replacing |today|: either, you set today to some 65 | # non-false value, then it is used: 66 | #today = '' 67 | # Else, today_fmt is used as the format for a strftime call. 68 | #today_fmt = '%B %d, %Y' 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | exclude_patterns = ['_build'] 73 | 74 | # The reST default role (used for this markup: `text`) to use for all documents. 75 | #default_role = None 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | #add_function_parentheses = True 79 | 80 | # If true, the current module name will be prepended to all description 81 | # unit titles (such as .. function::). 82 | #add_module_names = True 83 | 84 | # If true, sectionauthor and moduleauthor directives will be shown in the 85 | # output. They are ignored by default. 86 | #show_authors = False 87 | 88 | # The name of the Pygments (syntax highlighting) style to use. 89 | pygments_style = 'sphinx' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | 95 | # -- Options for HTML output --------------------------------------------------- 96 | 97 | # The theme to use for HTML and HTML Help pages. See the documentation for 98 | # a list of builtin themes. 99 | html_theme = 'default' 100 | 101 | # Theme options are theme-specific and customize the look and feel of a theme 102 | # further. For a list of options available for each theme, see the 103 | # documentation. 104 | #html_theme_options = {} 105 | 106 | # Add any paths that contain custom themes here, relative to this directory. 107 | #html_theme_path = [] 108 | 109 | # The name for this set of Sphinx documents. If None, it defaults to 110 | # " v documentation". 111 | #html_title = None 112 | 113 | # A shorter title for the navigation bar. Default is the same as html_title. 114 | #html_short_title = None 115 | 116 | # The name of an image file (relative to this directory) to place at the top 117 | # of the sidebar. 118 | #html_logo = None 119 | 120 | # The name of an image file (within the static path) to use as favicon of the 121 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 122 | # pixels large. 123 | #html_favicon = None 124 | 125 | # Add any paths that contain custom static files (such as style sheets) here, 126 | # relative to this directory. They are copied after the builtin static files, 127 | # so a file named "default.css" will overwrite the builtin "default.css". 128 | html_static_path = ['_static'] 129 | 130 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 131 | # using the given strftime format. 132 | #html_last_updated_fmt = '%b %d, %Y' 133 | 134 | # If true, SmartyPants will be used to convert quotes and dashes to 135 | # typographically correct entities. 136 | #html_use_smartypants = True 137 | 138 | # Custom sidebar templates, maps document names to template names. 139 | #html_sidebars = {} 140 | 141 | # Additional templates that should be rendered to pages, maps page names to 142 | # template names. 143 | #html_additional_pages = {} 144 | 145 | # If false, no module index is generated. 146 | #html_domain_indices = True 147 | 148 | # If false, no index is generated. 149 | #html_use_index = True 150 | 151 | # If true, the index is split into individual pages for each letter. 152 | #html_split_index = False 153 | 154 | # If true, links to the reST sources are added to the pages. 155 | #html_show_sourcelink = True 156 | 157 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 158 | #html_show_sphinx = True 159 | 160 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 161 | #html_show_copyright = True 162 | 163 | # If true, an OpenSearch description file will be output, and all pages will 164 | # contain a tag referring to it. The value of this option must be the 165 | # base URL from which the finished HTML is served. 166 | #html_use_opensearch = '' 167 | 168 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 169 | #html_file_suffix = None 170 | 171 | # Output file base name for HTML help builder. 172 | htmlhelp_basename = 'django-debug-loggingdoc' 173 | 174 | 175 | # -- Options for LaTeX output -------------------------------------------------- 176 | 177 | # The paper size ('letter' or 'a4'). 178 | #latex_paper_size = 'letter' 179 | 180 | # The font size ('10pt', '11pt' or '12pt'). 181 | #latex_font_size = '10pt' 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'django-debug-logging.tex', u'django-debug-logging Documentation', 187 | u'Lincoln Loop', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Additional stuff for the LaTeX preamble. 205 | #latex_preamble = '' 206 | 207 | # Documents to append as an appendix to all manuals. 208 | #latex_appendices = [] 209 | 210 | # If false, no module index is generated. 211 | #latex_domain_indices = True 212 | 213 | 214 | # -- Options for manual page output -------------------------------------------- 215 | 216 | # One entry per manual page. List of tuples 217 | # (source start file, name, description, authors, manual section). 218 | man_pages = [ 219 | ('index', 'django-debug-logging', u'django-debug-logging Documentation', 220 | [u'Lincoln Loop'], 1) 221 | ] 222 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-debug-logging documentation master file, created by 2 | sphinx-quickstart on Wed Nov 30 09:06:54 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to django-debug-logging's documentation! 7 | ================================================ 8 | 9 | Django Debug Logging is a "plugin" for the `Django Debug Toolbar`_ that allows 10 | users to log the debug toolbar statistics to the database during a site crawl. 11 | This allows users to create performance testing plans to exercise the site, and 12 | then review and aggregate the results afterwards to identify performance 13 | problems. 14 | 15 | It also provides a basic UI for browsing the details that have been logged to 16 | the database and reviewing aggregated information about test runs. The UI 17 | borrows a lot from the custom Sphinx theme by the Read the Docs team, and the 18 | Sentry project from Disqus. 19 | 20 | The overall goal is to use this tool to monitor performance statistics over 21 | time, so that you can see trends and spikes in the number of queries, cache 22 | misses, cpu time, etc., and identify where in the app the problems are coming 23 | from. It is not intended as a load testing tool, so features like concurrency 24 | and warmup periods will not be part of the initial focus. 25 | 26 | .. _Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar 27 | 28 | Contents: 29 | 30 | .. toctree:: 31 | :maxdepth: 2 32 | 33 | install 34 | settings 35 | running 36 | 37 | Indices and tables 38 | ================== 39 | 40 | * :ref:`genindex` 41 | * :ref:`modindex` 42 | * :ref:`search` 43 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Prerequisites 5 | ------------- 6 | 7 | These requirements are installed automatically by *pip* and *easy_install*, and 8 | are in the included *requirements.pip* file. 9 | 10 | `Django Debug Toolbar`_ - This project is designed to work alongside the Django 11 | Debug Toolbar and extend its functionality to support logging. 12 | 13 | Picklefield_ - Used to saved pickled versions of settings, sql queries, and 14 | cache calls to the database. 15 | 16 | .. _Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar 17 | .. _Picklefield: https://github.com/gintas/django-picklefield 18 | 19 | Installing 20 | ---------- 21 | 22 | Before you begin, make sure Django Debug Toolbar is configured and working 23 | properly. 24 | 25 | Install the project with pip:: 26 | 27 | $ pip install django-debug-logging 28 | 29 | This should install django-picklefield as well, which is needed. 30 | 31 | Next, you'll add *debug_logging* to your INSTALLED_APPS:: 32 | 33 | INSTALLED_APPS = ( 34 | ... 35 | 'debug_toolbar', 36 | 'debug_logging', 37 | ) 38 | 39 | Now, you'll need to replace the standard DebugToolbarMiddleware with a 40 | middleware that extends it to add logging functionality. The toolbar will 41 | still function normally when logging is disabled. 42 | 43 | From your MIDDLEWARE_CLASSES setting, remove:: 44 | 45 | 'debug_toolbar.middleware.DebugToolbarMiddleware', 46 | 47 | Replace it with:: 48 | 49 | 'debug_logging.middleware.DebugLoggingMiddleware', 50 | 51 | Now, you'll need to replace a few of the panels with extended versions that 52 | support logging. If you don't want the data from any one of these panels to 53 | be logged, you can skip it. 54 | 55 | From your DEBUG_TOOLBAR_PANELS setting, remove:: 56 | 57 | 'debug_toolbar.panels.cache.CacheDebugPanel', 58 | 'debug_toolbar.panels.settings_vars.SettingsVarsDebugPanel', 59 | 'debug_toolbar.panels.sql.SQLDebugPanel', 60 | 'debug_toolbar.panels.timer.TimerDebugPanel', 61 | 62 | Replace them with:: 63 | 64 | 'debug_logging.panels.cache.CacheLoggingPanel', 65 | 'debug_logging.panels.settings_vars.SettingsVarsLoggingPanel', 66 | 'debug_logging.panels.sql.SQLLoggingPanel', 67 | 'debug_logging.panels.timer.TimerLoggingPanel', 68 | 69 | There are also a couple of panels that are unique to Django Debug Logging that 70 | you may find convenient when logging data over time. If you'd like, you can 71 | add them to your DEBUG_TOOLBAR_PANELS setting:: 72 | 73 | 'debug_logging.panels.revision.RevisionLoggingPanel', 74 | 'debug_logging.panels.identity.IdentityLoggingPanel', 75 | 76 | Add the debug logging urls to your urls.py:: 77 | 78 | urlpatterns = patterns('', 79 | ... 80 | url(r'^debug-logging/', include('debug_logging.urls')), 81 | ) 82 | 83 | The Debug Logger will ignore requests made to this frontend interface, so your 84 | log won't be clogged with information you have no use for. 85 | 86 | Finally, run syncdb to create the models for statistic logging:: 87 | 88 | $ python manage.py syncdb 89 | 90 | South migrations are included in case migrations are needed when upgrading to 91 | new versions. 92 | 93 | Logging 94 | ------- 95 | 96 | Requests are logged when they contain a 'DJANGO_DEBUG_LOGGING' header set to 97 | True. This header is added automatically by the 'log_urls' command when it is 98 | used. To prevent any performance impact from the rendering of the Debug Toolbar, 99 | it is not shown when this header is present. 100 | 101 | For the best results, don't use the site while a test run is in progress. 102 | -------------------------------------------------------------------------------- /docs/running.rst: -------------------------------------------------------------------------------- 1 | Running a Url Test 2 | ================== 3 | 4 | A management command is included that uses the test client to hit a list of 5 | urls in sequence, allowing them to be logged to the database. To use it, first 6 | create a list of urls with a new url on each line. Lines beginning with # are 7 | ignored. :: 8 | 9 | # Main urls 10 | / 11 | /my/url/ 12 | /my/other/url/ 13 | # Comments 14 | /my/comment/url/ 15 | 16 | Then, enable logging and run the *log_urls* management command:: 17 | 18 | $ python manage.py log_urls myapp/my_urls.txt 19 | 20 | Unless it is run with a verbosity of 0 the command will output status 21 | messages, such as urls that return codes other than 200 and urls that raise 22 | errors. 23 | 24 | To run the test as an authenticated user, use the username and password 25 | options:: 26 | 27 | $ python manage.py log_urls my_urls.txt --username Legen --password dary 28 | 29 | You can also add a name and a description to your run, if you'd like:: 30 | 31 | $ python manage.py log_urls my_urls.txt --name "Admin Urls" --description "Urls used by site admins" 32 | 33 | If you'd like to conduct a test run with a tool other than the log_urls 34 | management command, you can use the command to manually start and end TestRun 35 | objects, so that your results will be organized correctly in the UI. Before you 36 | conduct your test, simply run:: 37 | 38 | $ python manage.py log_urls --manual-start 39 | 40 | Then, when you are finished hitting your desired urls:: 41 | 42 | $ python manage.py log_urls --manual-end 43 | -------------------------------------------------------------------------------- /docs/screenshots/debug_logging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lincolnloop/django-debug-logging/dc2f60b5abaf5f344de45a791f3a3473873ae304/docs/screenshots/debug_logging.png -------------------------------------------------------------------------------- /docs/screenshots/debug_logging_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lincolnloop/django-debug-logging/dc2f60b5abaf5f344de45a791f3a3473873ae304/docs/screenshots/debug_logging_2.png -------------------------------------------------------------------------------- /docs/screenshots/debug_logging_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lincolnloop/django-debug-logging/dc2f60b5abaf5f344de45a791f3a3473873ae304/docs/screenshots/debug_logging_3.png -------------------------------------------------------------------------------- /docs/settings.rst: -------------------------------------------------------------------------------- 1 | Settings 2 | ======== 3 | 4 | * ``SQL_EXTRA``: This setting determines whether the full details of each query 5 | are logged, or just the number of queries and the total time. It defaults to 6 | ``False``. 7 | 8 | * ``CACHE_EXTRA``: This determines whether the full details of each cache call 9 | are logged, or just the summary details. It defaults to `` False``. 10 | 11 | * ``BLACKLIST``: Add a list of url prefixes that you would like to exclude from 12 | logging here. The url for the Debug Logging frontend interface is added to 13 | this blacklist automatically. 14 | -------------------------------------------------------------------------------- /requirements.pip: -------------------------------------------------------------------------------- 1 | django-debug-toolbar==0.8.5 2 | django-picklefield==0.1.9 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='django-debug-logging', 5 | version=__import__('debug_logging').__version__, 6 | description='A plugin for django_debug_toolbar that logs results to the database for aggregated review.', 7 | long_description=open('README.rst').read(), 8 | author='Brandon Konkle', 9 | author_email='brandon@lincolnloop.com', 10 | url='http://github.com/lincolnloop/django-debug-logging/', 11 | download_url='http://github.com/lincolnloop/django-debug-logging/downloads', 12 | license='BSD', 13 | packages=find_packages(exclude=['ez_setup']), 14 | include_package_data=True, 15 | zip_safe=False, # because we're including media that Django needs 16 | install_requires=['django-debug-toolbar', 'django-picklefield'], 17 | classifiers=[ 18 | 'Development Status :: 4 - Beta', 19 | 'Environment :: Web Environment', 20 | 'Framework :: Django', 21 | 'Intended Audience :: Developers', 22 | 'License :: OSI Approved :: BSD License', 23 | 'Operating System :: OS Independent', 24 | 'Programming Language :: Python', 25 | 'Topic :: Software Development :: Libraries :: Python Modules', 26 | ], 27 | ) 28 | --------------------------------------------------------------------------------