tgext.debugbar 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. tgext/debugbar/__init__.py +17 -0
  2. tgext/debugbar/controller.py +117 -0
  3. tgext/debugbar/initialize.py +151 -0
  4. tgext/debugbar/sections/__init__.py +17 -0
  5. tgext/debugbar/sections/base.py +20 -0
  6. tgext/debugbar/sections/controllers.py +49 -0
  7. tgext/debugbar/sections/inventing.py +198 -0
  8. tgext/debugbar/sections/logmessages.py +69 -0
  9. tgext/debugbar/sections/mingorm.py +124 -0
  10. tgext/debugbar/sections/request_vars.py +64 -0
  11. tgext/debugbar/sections/sqla.py +126 -0
  12. tgext/debugbar/sections/templates/__init__.py +2 -0
  13. tgext/debugbar/sections/templates/controllers.html +18 -0
  14. tgext/debugbar/sections/templates/inventing.html +26 -0
  15. tgext/debugbar/sections/templates/logging.html +21 -0
  16. tgext/debugbar/sections/templates/ming.html +31 -0
  17. tgext/debugbar/sections/templates/request.html +19 -0
  18. tgext/debugbar/sections/templates/sqla.html +32 -0
  19. tgext/debugbar/sections/templates/timing.html +68 -0
  20. tgext/debugbar/sections/timing.py +163 -0
  21. tgext/debugbar/statics/TgGear.png +0 -0
  22. tgext/debugbar/statics/__init__.py +1 -0
  23. tgext/debugbar/statics/jquery.js +5 -0
  24. tgext/debugbar/statics/style.css +170 -0
  25. tgext/debugbar/templates/__init__.py +1 -0
  26. tgext/debugbar/templates/debugbar.html +57 -0
  27. tgext/debugbar/templates/perform_ming.html +30 -0
  28. tgext/debugbar/templates/perform_sql.html +30 -0
  29. tgext/debugbar/utils.py +98 -0
  30. tgext.debugbar-0.6.0-py3.8-nspkg.pth +1 -0
  31. tgext_debugbar-0.6.0.dist-info/METADATA +98 -0
  32. tgext_debugbar-0.6.0.dist-info/RECORD +35 -0
  33. tgext_debugbar-0.6.0.dist-info/WHEEL +5 -0
  34. tgext_debugbar-0.6.0.dist-info/namespace_packages.txt +1 -0
  35. tgext_debugbar-0.6.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,17 @@
1
+ from .initialize import DebugBar, enable_debugbar
2
+
3
+
4
+ def plugme(app_config, options):
5
+ try:
6
+ app_config.update_blueprint({
7
+ 'debugbar.inventing': options.get('inventing', False),
8
+ 'debugbar.inventing_css': options.get('inventing_css', True),
9
+ 'debugbar.enable_logs': options.get('enable_logs', False)
10
+ })
11
+ except AttributeError:
12
+ app_config['debugbar.inventing'] = options.get('inventing', False)
13
+ app_config['debugbar.inventing_css'] = options.get('inventing_css', True)
14
+ app_config['debugbar.enable_logs'] = options.get('enable_logs', False)
15
+
16
+ enable_debugbar(app_config)
17
+ return dict(appid='tgext.debugbar')
@@ -0,0 +1,117 @@
1
+ import sys
2
+ import os
3
+ import pprint
4
+ import json
5
+
6
+ from tg import app_globals, config, expose, request
7
+ from tg.controllers import TGController, WSGIAppController
8
+ from webob.exc import HTTPBadRequest
9
+ from .utils import format_sql, format_json
10
+ from .sections import __sections__
11
+
12
+ STATICS_PATH = os.path.join(os.path.split(sys.modules['tgext.debugbar'].__file__)[0], 'statics')
13
+
14
+
15
+ try:
16
+ from webob.static import DirectoryApp
17
+ except ImportError:
18
+ from paste.urlparser import StaticURLParser as DirectoryApp
19
+
20
+ try:
21
+ from pymongo import json_util
22
+ except ImportError:
23
+ try:
24
+ from bson import json_util
25
+ except ImportError:
26
+ pass
27
+
28
+
29
+ class StaticsController(TGController):
30
+ _directory_app = DirectoryApp(STATICS_PATH)
31
+
32
+ @expose()
33
+ def _default(self, *args):
34
+ new_req = request.copy()
35
+ to_pop = len(new_req.path_info.strip('/').split('/')) - len(args)
36
+ for i in range(to_pop):
37
+ new_req.path_info_pop()
38
+ return new_req.get_response(self._directory_app)
39
+
40
+
41
+ class DebugBarController(TGController):
42
+ statics = StaticsController()
43
+
44
+ @expose('genshi:tgext.debugbar.templates.perform_sql!html')
45
+ @expose('kajiki:tgext.debugbar.templates.perform_sql!html')
46
+ def perform_sql(self, stmt, params, engine_id, duration, modify=None):
47
+ # Make sure it is a select statement
48
+ if not stmt.lower().lstrip().startswith('select'):
49
+ raise HTTPBadRequest('Not a SELECT SQL statement')
50
+ try:
51
+ if not engine_id:
52
+ raise ValueError
53
+ engine_id = int(engine_id)
54
+ engine = getattr(app_globals, 'tgdb_sqla_engines')[engine_id]()
55
+ except (AttributeError, IndexError, ValueError):
56
+ raise HTTPBadRequest('No valid database engine')
57
+
58
+ if modify and modify.lower() == 'explain':
59
+ if engine.name.startswith('sqlite'):
60
+ stmt = 'EXPLAIN QUERY PLAN %s' % stmt
61
+ else:
62
+ stmt = 'EXPLAIN %s' % stmt
63
+ title = 'Execution Plan'
64
+ else:
65
+ title = 'Query Results'
66
+
67
+ sql_params = json.loads(params)
68
+ if isinstance(sql_params, list):
69
+ sql_params = tuple(sql_params)
70
+
71
+ with engine.connect() as connection:
72
+ result = connection.exec_driver_sql(stmt, sql_params)
73
+ rows = result.fetchall()
74
+ headers = result.keys()
75
+
76
+ return dict(
77
+ sql=format_sql(stmt),
78
+ params=params,
79
+ result=rows,
80
+ headers=headers,
81
+ duration=float(duration),
82
+ title=title)
83
+
84
+ @expose('genshi:tgext.debugbar.templates.perform_ming!html')
85
+ @expose('kajiki:tgext.debugbar.templates.perform_ming!html')
86
+ def perform_ming(self, collection, command, params, duration, modify=None):
87
+ if not command.startswith('find'):
88
+ raise HTTPBadRequest('Not a find statement')
89
+
90
+ query_params = json.loads(params, object_hook=json_util.object_hook)
91
+ session = config['package'].model.DBSession
92
+
93
+ cursor = []
94
+ for i, step in enumerate(command.split('.')):
95
+ if step == 'find':
96
+ args = query_params[i]
97
+ query = args[0]
98
+ options = args[1]
99
+ cursor = session.find(collection, query, **options)
100
+ else:
101
+ args = query_params[i]
102
+ cmd = getattr(cursor, step)
103
+ cursor = cmd(*args)
104
+
105
+ isexplain = False
106
+ if modify and modify.lower() == 'explain':
107
+ cursor = cursor.ming_cursor.cursor.explain()
108
+ isexplain = True
109
+
110
+ return dict(
111
+ pformat=pprint.pformat,
112
+ action=command,
113
+ params=format_json(params),
114
+ result=cursor,
115
+ collection=collection,
116
+ duration=float(duration),
117
+ isexplain=isexplain)
@@ -0,0 +1,151 @@
1
+ import logging
2
+
3
+ from markupsafe import Markup
4
+
5
+ import tg
6
+ from tg import request, url
7
+ from tg.render import render
8
+
9
+ try:
10
+ # Verify that we have hooks with disconnect feature,
11
+ # which is only available since TG2.3.5, otherwise
12
+ # use app_config to register/disconnect hooks.
13
+ from tg import hooks as tg_hooks
14
+ if not hasattr(tg_hooks, 'disconnect'):
15
+ tg_hooks = None
16
+ except ImportError:
17
+ tg_hooks = None
18
+
19
+ try:
20
+ # TG >= 2.4
21
+ from tg import ApplicationConfigurator
22
+ except ImportError:
23
+ # TG < 2.4
24
+ class ApplicationConfigurator: pass
25
+
26
+ from tgext.debugbar.sections import __sections__
27
+ from tgext.debugbar.utils import get_root_controller
28
+
29
+ log = logging.getLogger('tgext.debugbar')
30
+
31
+
32
+ class DebugBar():
33
+ css_link = '<link rel="stylesheet" type="text/css" href="%s" />'
34
+ css_path = '/_debugbar/statics/style.css'
35
+ template = 'tgext.debugbar.templates.debugbar!html'
36
+
37
+ def __init__(self, app_config):
38
+ self.app_config = app_config
39
+ self.available_engines = None
40
+
41
+ def _register_hook(self, hook_name, handler):
42
+ if tg_hooks is None:
43
+ # 2.1+
44
+ self.app_config.register_hook(hook_name, handler)
45
+ elif hasattr(tg_hooks, 'wrap_controller'):
46
+ # 2.3+
47
+ if hook_name == 'controller_wrapper':
48
+ def _accept_decoration(decoration, controller):
49
+ return handler(controller)
50
+ tg_hooks.wrap_controller(_accept_decoration)
51
+ else:
52
+ tg_hooks.register(hook_name, handler)
53
+ else:
54
+ # 2.4+
55
+ if hook_name == 'controller_wrapper':
56
+ from tg import ApplicationConfigurator
57
+ dispatch = ApplicationConfigurator.current().get_component('dispatch')
58
+ if dispatch is None:
59
+ raise RuntimeError('TurboGears application configured without dispatching')
60
+ dispatch.register_controller_wrapper(handler)
61
+ else:
62
+ tg_hooks.register(hook_name, handler)
63
+
64
+ def _disconnect_hook(self, hook_name, handler):
65
+ if tg_hooks is None:
66
+ self.app_config.hooks[hook_name].remove(handler)
67
+ else:
68
+ tg_hooks.disconnect(hook_name, handler)
69
+
70
+ def __call__(self, configurator=None, conf=None):
71
+ if conf is None:
72
+ conf = tg.config
73
+
74
+ if not conf.get('debug', False):
75
+ return
76
+
77
+ conf['debugbar.engine'] = next(
78
+ iter(sorted(set(('genshi', 'kajiki')) & set(conf['renderers']),
79
+ key=lambda x: x == conf['default_renderer'],
80
+ reverse=True)),
81
+ None
82
+ )
83
+ if not conf['debugbar.engine']:
84
+ log.error("Genshi or Kajiki rendering engines unavailable. Please install kajiki "
85
+ "and add base_config.renderers.append('kajiki') to your app_cfg.py")
86
+ raise RuntimeError('Debugbar requires Genshi or Kajiki rendering engines')
87
+
88
+ log.log(logging.INFO, 'Enabling Debug Toolbar')
89
+ for sec in __sections__:
90
+ if not sec.is_active:
91
+ continue
92
+
93
+ log.log(logging.DEBUG, 'Enabling Section: %s' % sec.name)
94
+ for hook_name, hooks in sec.hooks.items():
95
+ for handler in hooks:
96
+ if hook_name == 'startup':
97
+ handler()
98
+ else:
99
+ try:
100
+ self._register_hook(hook_name, handler)
101
+ except:
102
+ log.exception('Unable to register hook: %s', hook_name)
103
+
104
+ self._register_hook('after_render', self.render_first)
105
+
106
+ def render_first(self, response):
107
+ try:
108
+ self._disconnect_hook('after_render', self.render_first)
109
+ except ValueError:
110
+ pass # pre-emptied by another request
111
+ else:
112
+ from tgext.debugbar.controller import DebugBarController
113
+ get_root_controller()._debugbar = DebugBarController()
114
+ self._register_hook('after_render', self.render_bars)
115
+ self.render_bars(response)
116
+
117
+ def render_bars(self, response):
118
+ page = response.get('response')
119
+ if (not page or not isinstance(page, str)
120
+ or 'text/html' not in response['content_type']
121
+ or request.headers.get(
122
+ 'X-Requested-With') == 'XMLHttpRequest'):
123
+
124
+ if tg.config.get('debugbar.enable_logs', False):
125
+ for section in __sections__:
126
+ if hasattr(section, 'log_content'):
127
+ section.log_content()
128
+
129
+ return
130
+
131
+ pos_head = page.find('</head>')
132
+ if pos_head > 0:
133
+ pos_body = page.find('</body>', pos_head + 7)
134
+ if pos_body > 0:
135
+ response['response'] = ''.join(
136
+ [page[:pos_head],
137
+ Markup(self.css_link % url(self.css_path)),
138
+ page[pos_head:pos_body],
139
+ Markup(render(dict(sections=__sections__),
140
+ tg.config['debugbar.engine'], self.template,).split('\n', 1)[-1]),
141
+ page[pos_body:]])
142
+
143
+
144
+ def enable_debugbar(app_config):
145
+ if isinstance(app_config, ApplicationConfigurator):
146
+ tg_hooks.register('initialized_config', DebugBar(app_config))
147
+ else:
148
+ if tg_hooks is None:
149
+ app_config.register_hook('startup', DebugBar(app_config))
150
+ else:
151
+ tg_hooks.register('startup', DebugBar(app_config))
@@ -0,0 +1,17 @@
1
+ from .sqla import SQLADebugSection
2
+ from .timing import TimingDebugSection
3
+ from .request_vars import RequestDebugSection
4
+ from .controllers import ControllersDebugSection
5
+ from .logmessages import LoggingDebugSection
6
+ from .mingorm import MingDebugSection
7
+ from .inventing import InventingDebugSection
8
+
9
+ __sections__ = [
10
+ TimingDebugSection(),
11
+ RequestDebugSection(),
12
+ SQLADebugSection(),
13
+ MingDebugSection(),
14
+ ControllersDebugSection(),
15
+ LoggingDebugSection(),
16
+ InventingDebugSection()
17
+ ]
@@ -0,0 +1,20 @@
1
+ """The base debug section class."""
2
+
3
+
4
+ class DebugSection(object):
5
+
6
+ name = "Unnamed"
7
+ is_active = False
8
+ hooks = dict(
9
+ startup=[],
10
+ shutdown=[],
11
+ before_validate=[],
12
+ before_call=[],
13
+ before_render=[],
14
+ after_render=[])
15
+
16
+ def title(self):
17
+ raise NotImplementedError
18
+
19
+ def content(self):
20
+ raise NotImplementedError
@@ -0,0 +1,49 @@
1
+ import inspect
2
+
3
+ import tg
4
+ from tg.controllers.decoratedcontroller import DecoratedController
5
+ from tg.i18n import ugettext as _
6
+ from tg.render import render
7
+
8
+ try:
9
+ from tg.util import odict
10
+ except ImportError:
11
+ from collections import OrderedDict as odict
12
+
13
+ from tgext.debugbar.sections.base import DebugSection
14
+ from tgext.debugbar.utils import get_root_controller
15
+
16
+
17
+ def map_controllers(path, controller, output):
18
+ if inspect.isclass(controller):
19
+ if not issubclass(controller, DecoratedController):
20
+ return
21
+ else:
22
+ if isinstance(controller, DecoratedController):
23
+ controller = controller.__class__
24
+ else:
25
+ return
26
+
27
+ exposed_methods = {}
28
+ output[path and path or '/'] = dict(
29
+ controller=controller, exposed_methods=exposed_methods)
30
+ for name, cont in controller.__dict__.items():
31
+ if hasattr(cont, 'decoration') and cont.decoration.exposed:
32
+ exposed_methods[name] = cont
33
+ map_controllers(path + '/' + name, cont, output)
34
+
35
+
36
+ class ControllersDebugSection(DebugSection):
37
+ name = 'Controllers'
38
+ is_active = True
39
+
40
+ def title(self):
41
+ return _('Controllers')
42
+
43
+ def content(self):
44
+ controllers = odict()
45
+ map_controllers('', get_root_controller(), controllers)
46
+ return str(render(
47
+ dict(controllers=controllers),
48
+ tg.config['debugbar.engine'], 'tgext.debugbar.sections.templates.controllers!html'
49
+ ).split('\n', 1)[-1])
@@ -0,0 +1,198 @@
1
+ import hashlib, re, logging, os, time
2
+ from datetime import datetime
3
+ from tg import config, tmpl_context
4
+ from tg.render import render
5
+ from tg.i18n import ugettext as _
6
+
7
+ from paste.deploy.converters import asbool
8
+ from tgext.debugbar.sections.base import DebugSection
9
+
10
+ from markupsafe import Markup
11
+
12
+ log = logging.getLogger('tgext.debugbar')
13
+
14
+ _reload_datetime = datetime.now().strftime('%Y%m%d%H%M%S')
15
+ _href_re = re.compile(r'''href=["\']([^\'\"]+)[\'"]''', re.UNICODE|re.IGNORECASE)
16
+
17
+ def detect_stylesheets(page):
18
+ try:
19
+ base_dir = config.get('paths', config.get('pylons.paths'))['static_files']
20
+ except:
21
+ log.warn('Unable to detect static files path, skipping inventing mode on stylesheets')
22
+ return []
23
+
24
+ files = []
25
+
26
+ line = page.find('stylesheet')
27
+ while line >= 0:
28
+ css_link = _href_re.search(page[line:])
29
+ if css_link:
30
+ css_file = css_link.group(1)
31
+ css_file = css_file.replace('/', os.sep)
32
+ if css_file.startswith(os.sep):
33
+ css_file = css_file[1:]
34
+
35
+ css_file = os.path.join(base_dir, css_file)
36
+ if os.path.exists(css_file):
37
+ files.append(css_file)
38
+ line = page.find('stylesheet', line+1)
39
+
40
+ return files
41
+
42
+ def on_after_render(response, *args, **kw):
43
+ content_type = response.get('content_type', '')
44
+ template = response.get('template_name')
45
+ page = response.get('response')
46
+
47
+ if content_type and 'text/html' in content_type and template and isinstance(page, str):
48
+ m = hashlib.md5()
49
+ m.update(page.encode('utf-8'))
50
+ m = m.hexdigest() + _reload_datetime
51
+
52
+ if config.get('debugbar.inventing_css', False):
53
+ newest_modified_time = 0
54
+
55
+ for f in detect_stylesheets(page):
56
+ modified_time = os.path.getmtime(f)
57
+ if modified_time > newest_modified_time:
58
+ newest_modified_time = modified_time
59
+
60
+ if newest_modified_time:
61
+ m += str(newest_modified_time)
62
+
63
+ pos_head = page.find('</head>')
64
+ if pos_head > 0:
65
+ pos_body = page.find('</body>', pos_head + 7)
66
+ if pos_body > 0:
67
+ response['response'] = ''.join([page[:pos_head],
68
+ Markup('<script>if(typeof tgext_debugbar_page_hash === "undefined") window.tgext_debugbar_page_hash="%s";</script>' % m),
69
+ page[pos_head:pos_body],
70
+ page[pos_body:]])
71
+
72
+ class InventingDebugSection(DebugSection):
73
+ name = 'Inventing'
74
+ is_active = True
75
+ hooks = dict(after_render=[on_after_render])
76
+
77
+ js_reloadscript = '''
78
+ <script>
79
+ ; (function(win, undefined) {
80
+ var tgext_debugbar_inventing = win['tgext_debugbar_inventing'] = (win['tgext_debugbar_inventing'] || {});
81
+
82
+ tgext_debugbar_inventing.start = function() {
83
+ if (!supportsLocalStorage()) {
84
+ alert("Inventing Mode requires local storage support");
85
+ return false;
86
+ }
87
+
88
+ localStorage["tgext.debugbar.enabled"] = "true";
89
+ DebugBarJQuery('#tgdb_debugbar_inventing_toggle').text('Turn Off');
90
+ tgext_debugbar_inventing.loop();
91
+ };
92
+
93
+ tgext_debugbar_inventing.loop = function() {
94
+ if (!tgext_debugbar_inventing.is_enabled())
95
+ return;
96
+
97
+ setTimeout(tgext_debugbar_check_changed, 1000);
98
+ };
99
+
100
+ tgext_debugbar_inventing.is_enabled = function() {
101
+ if (!supportsLocalStorage())
102
+ return false;
103
+
104
+ var enabled = (localStorage["tgext.debugbar.enabled"] == "true");
105
+ return enabled;
106
+ }
107
+
108
+ tgext_debugbar_inventing.init = function(force) {
109
+ var enabled = (tgext_debugbar_inventing.is_enabled() || force);
110
+ if (!enabled)
111
+ return false;
112
+
113
+ tgext_debugbar_inventing.start();
114
+ return true;
115
+ };
116
+
117
+ tgext_debugbar_inventing.toggle = function(button) {
118
+ if (!supportsLocalStorage()) {
119
+ alert("Inventing Mode requires local storage support");
120
+ return false;
121
+ }
122
+
123
+ button = DebugBarJQuery(button);
124
+
125
+ var debugbar_enabled = tgext_debugbar_inventing.is_enabled();
126
+ if (!debugbar_enabled) {
127
+ tgext_debugbar_inventing.start();
128
+ button.text('Turn Off');
129
+ }
130
+ else {
131
+ localStorage["tgext.debugbar.enabled"] = "false";
132
+ button.text('Turn On');
133
+ }
134
+ };
135
+
136
+ function tgext_debugbar_check_changed() {
137
+ DebugBarJQuery.ajax(window.location.href, {
138
+ 'success': function(data, textStatus, jqXHR) {
139
+ var page_hash_re = /tgext_debugbar_page_hash="(.*)";/;
140
+ var page_hash = page_hash_re.exec(data);
141
+ if (page_hash && page_hash.length) {
142
+ var page_hash = page_hash[1];
143
+ if(page_hash != tgext_debugbar_page_hash) {
144
+ window.location.reload();
145
+ return
146
+ }
147
+ }
148
+ DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').removeClass('tgdb_barcontent_error');
149
+ DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').removeClass('tgdb_barcontent_warning');
150
+ tgext_debugbar_inventing.loop();
151
+ },
152
+ 'error':function(jqXHR, textStatus, errorThrown) {
153
+ DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').removeClass('tgdb_barcontent_error');
154
+ DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').removeClass('tgdb_barcontent_warning');
155
+
156
+ if (jqXHR.status && jqXHR.status >= 500) {
157
+ DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').addClass('tgdb_barcontent_error');
158
+ }
159
+ else if (jqXHR.status && jqXHR.status < 500) {
160
+ //Probably not an error in this case, yet to decide if to alert it somehow
161
+ }
162
+ else {
163
+ DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').addClass('tgdb_barcontent_warning');
164
+ }
165
+ tgext_debugbar_inventing.loop();
166
+ }
167
+ });
168
+ }
169
+
170
+ function supportsLocalStorage() {
171
+ try {
172
+ return 'localStorage' in window && window['localStorage'] !== null;
173
+ } catch(e){
174
+ return false;
175
+ }
176
+ }
177
+ })(window);
178
+ </script>'''
179
+
180
+ def title(self):
181
+ return _('Inventing')
182
+
183
+ def content(self):
184
+ inventing_enabled = asbool(config.get('debugbar.inventing', 'false'))
185
+ inventing_enabled = getattr(tmpl_context, 'debugbar_inventing', inventing_enabled)
186
+
187
+ result = ''
188
+ result += render(dict(), config['debugbar.engine'],
189
+ 'tgext.debugbar.sections.templates.inventing!html')
190
+
191
+ result += Markup(self.js_reloadscript)
192
+ if inventing_enabled:
193
+ result += Markup('<script>tgext_debugbar_inventing.init(true)</script>')
194
+ else:
195
+ result += Markup('<script>tgext_debugbar_inventing.init(false)</script>')
196
+
197
+ return result
198
+
@@ -0,0 +1,69 @@
1
+ import datetime
2
+ import logging
3
+ import threading
4
+
5
+ import tg
6
+ from tg.i18n import ugettext as _
7
+ from tg.render import render
8
+
9
+ from tgext.debugbar.sections.base import DebugSection
10
+ from tgext.debugbar.utils import format_fname
11
+
12
+
13
+ class ThreadTrackingHandler(logging.Handler):
14
+ def __init__(self):
15
+ logging.Handler.__init__(self)
16
+ self.records = {}
17
+
18
+ def emit(self, record):
19
+ self.get_records().append(record)
20
+
21
+ def get_records(self, thread=None):
22
+ if thread is None:
23
+ thread = threading.current_thread()
24
+ if thread not in self.records:
25
+ self.records[thread] = []
26
+ return self.records[thread]
27
+
28
+ def clear_records(self, thread=None):
29
+ if thread is None:
30
+ thread = threading.current_thread()
31
+ if thread in self.records:
32
+ del self.records[thread]
33
+
34
+ handler = ThreadTrackingHandler()
35
+ logging.root.addHandler(handler)
36
+
37
+
38
+ class LoggingDebugSection(DebugSection):
39
+ name = 'Logging'
40
+ is_active = True
41
+
42
+ def get_and_clear(self):
43
+ records = handler.get_records()
44
+ handler.clear_records()
45
+ return records
46
+
47
+ def title(self):
48
+ return _('Logging')
49
+
50
+ def content(self):
51
+ records = []
52
+ for record in self.get_and_clear():
53
+ msg = record.getMessage()
54
+ if isinstance(msg, bytes):
55
+ msg = msg.decode('utf-8', 'ignore')
56
+ records.append({
57
+ 'message': msg,
58
+ 'time': datetime.datetime.fromtimestamp(record.created),
59
+ 'level': record.levelname,
60
+ 'file': format_fname(record.pathname),
61
+ 'file_long': record.pathname,
62
+ 'line': record.lineno,
63
+ })
64
+
65
+ records = reversed(records)
66
+ return str(render(
67
+ dict(records=records),
68
+ tg.config['debugbar.engine'], 'tgext.debugbar.sections.templates.logging!html'
69
+ ).split('\n', 1)[-1])