diff --git a/.idea/misc.xml b/.idea/misc.xml
index e259dad37..b485cd2d3 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,7 +3,7 @@
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 5bcc80581..390b75bc8 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ In general, configuration can be done both with environment variables (taken fir
`production.ini` file.
You can configure the base URL for accessing the views provided by c2cwsgiutils with an environment variable
-named `C2C_BASE_PATH` or in the `production.ini` file with a property named `c2c.base_path`.
+named `C2C_BASE_PATH` or in the `production.ini` file with a property named `c2c.base_path`.
Pyramid
@@ -208,6 +208,14 @@ version of a app. This file is generated by calling the `c2cwsgiutils_genversion
command line. Usually done in the [Dockerfile](acceptance_tests/app/Dockerfile) of the WSGI application.
+Debugging
+---------
+
+To enable the debugging interface, you must set the `DEBUG_VIEW_SECRET` environment variable or the
+`c2c.debug_view_secret` variable. Then you can get a dump of every threads with this URL:
+`{C2C_BASE_PATH}/debug/stacks?secret={DEBUG_VIEW_SECRET}`
+
+
CORS
----
diff --git a/acceptance_tests/tests/acceptance.iml b/acceptance_tests/tests/acceptance.iml
index bb4ef979e..b233d535d 100644
--- a/acceptance_tests/tests/acceptance.iml
+++ b/acceptance_tests/tests/acceptance.iml
@@ -6,7 +6,7 @@
-
+
diff --git a/acceptance_tests/tests/docker-compose.yml b/acceptance_tests/tests/docker-compose.yml
index 135ace006..091b1b6a6 100644
--- a/acceptance_tests/tests/docker-compose.yml
+++ b/acceptance_tests/tests/docker-compose.yml
@@ -10,6 +10,7 @@ services:
STATSD_PREFIX: acceptance
LOG_VIEW_SECRET: changeme
SQL_PROFILER_SECRET: changeme
+ DEBUG_VIEW_SECRET: changeme
LOG_HOST: 172.17.0.1
LOG_TYPE: 'console,logstash'
SQL_LOG_LEVEL: DEBUG
diff --git a/acceptance_tests/tests/tests/test_debug.py b/acceptance_tests/tests/tests/test_debug.py
new file mode 100644
index 000000000..10c26db6f
--- /dev/null
+++ b/acceptance_tests/tests/tests/test_debug.py
@@ -0,0 +1,7 @@
+def test_ok(app_connection):
+ stacks = app_connection.get('c2c/debug/stacks', params={'secret': 'changeme'})
+ assert 'c2cwsgiutils/debug' in stacks
+
+
+def test_no_auth(app_connection):
+ app_connection.get_json('c2c/debug/stacks', expected_status=403)
diff --git a/c2cwsgiutils/_utils.py b/c2cwsgiutils/_utils.py
index 3f6b11f18..063abf8cd 100644
--- a/c2cwsgiutils/_utils.py
+++ b/c2cwsgiutils/_utils.py
@@ -3,6 +3,7 @@
"""
import os
+from pyramid.httpexceptions import HTTPForbidden
def get_base_path(config):
@@ -13,3 +14,8 @@ def env_or_config(config, env_name, config_name, default):
if env_name in os.environ:
return os.environ[env_name]
return config.get_settings().get(config_name, default)
+
+
+def auth_view(request, env_name, config_name):
+ if request.params.get('secret') != env_or_config(request.registry.settings, env_name, config_name, False):
+ raise HTTPForbidden('Missing or invalid secret parameter')
diff --git a/c2cwsgiutils/debug.py b/c2cwsgiutils/debug.py
new file mode 100644
index 000000000..9126c1e83
--- /dev/null
+++ b/c2cwsgiutils/debug.py
@@ -0,0 +1,32 @@
+import logging
+import threading
+import traceback
+import sys
+
+from c2cwsgiutils import _utils
+
+CONFIG_KEY = 'c2c.debug_view_secret'
+ENV_KEY = 'DEBUG_VIEW_SECRET'
+
+LOG = logging.getLogger(__name__)
+
+
+def _dump_stacks(request):
+ _utils.auth_view(request, ENV_KEY, CONFIG_KEY)
+ id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
+ code = []
+ for threadId, stack in sys._current_frames().items():
+ code.append("\n# Thread: %s(%d)" % (id2name.get(threadId, ""), threadId))
+ for filename, lineno, name, line in traceback.extract_stack(stack):
+ code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
+ if line:
+ code.append(" %s" % (line.strip()))
+ return "\n".join(code)
+
+
+def init(config):
+ if _utils.env_or_config(config, ENV_KEY, CONFIG_KEY, False):
+ config.add_route("c2c_debug_stacks", _utils.get_base_path(config) + r"/debug/stacks",
+ request_method="GET")
+ config.add_view(_dump_stacks, route_name="c2c_debug_stacks", renderer="string", http_cache=0)
+ LOG.info("Enabled the /debug/stacks API")
diff --git a/c2cwsgiutils/pyramid.py b/c2cwsgiutils/pyramid.py
index d1073de22..7163b4fdd 100644
--- a/c2cwsgiutils/pyramid.py
+++ b/c2cwsgiutils/pyramid.py
@@ -1,7 +1,7 @@
import cornice
import pyramid_tm
-from c2cwsgiutils import stats, pyramid_logging, sql_profiler, version
+from c2cwsgiutils import stats, pyramid_logging, sql_profiler, version, debug
def includeme(config):
@@ -16,4 +16,5 @@ def includeme(config):
pyramid_logging.install_subscriber(config)
sql_profiler.init(config)
version.init(config)
+ debug.init(config)
config.scan("c2cwsgiutils.errors")
diff --git a/c2cwsgiutils/pyramid_logging.py b/c2cwsgiutils/pyramid_logging.py
index 1c08420e4..7d2b9c611 100644
--- a/c2cwsgiutils/pyramid_logging.py
+++ b/c2cwsgiutils/pyramid_logging.py
@@ -8,15 +8,16 @@
A pyramid event handler is installed to setup this filter for the current request.
"""
import logging
-import os
import uuid
from cee_syslog_handler import CeeSysLogHandler
-from pyramid.httpexceptions import HTTPForbidden
from pyramid.threadlocal import get_current_request
from c2cwsgiutils import _utils
+CONFIG_KEY = 'c2c.log_view_secret'
+ENV_KEY = 'LOG_VIEW_SECRET'
+
LOG = logging.getLogger(__name__)
@@ -53,7 +54,7 @@ def install_subscriber(config):
Install the view to configure the loggers, if configured to do so.
"""
config.add_request_method(lambda request: str(uuid.uuid4()), 'c2c_request_id', reify=True)
- if 'LOG_VIEW_SECRET' in os.environ:
+ if _utils.env_or_config(config, ENV_KEY, CONFIG_KEY, False):
config.add_route("c2c_logging_level", _utils.get_base_path(config) + r"/logging/level",
request_method="GET")
config.add_view(_logging_change_level, route_name="c2c_logging_level", renderer="json", http_cache=0)
@@ -61,8 +62,7 @@ def install_subscriber(config):
def _logging_change_level(request):
- if request.params.get('secret') != os.environ['LOG_VIEW_SECRET']:
- raise HTTPForbidden('Missing or invalid secret parameter')
+ _utils.auth_view(request, ENV_KEY, CONFIG_KEY)
name = request.params['name']
level = request.params.get('level')
logger = logging.getLogger(name)
diff --git a/c2cwsgiutils/sql_profiler.py b/c2cwsgiutils/sql_profiler.py
index f099536e4..1328b9113 100644
--- a/c2cwsgiutils/sql_profiler.py
+++ b/c2cwsgiutils/sql_profiler.py
@@ -3,8 +3,6 @@
every SELECT query going through SQLAlchemy.
"""
import logging
-import os
-from pyramid.httpexceptions import HTTPForbidden
import re
import sqlalchemy.event
import sqlalchemy.engine
@@ -12,14 +10,14 @@
from c2cwsgiutils import _utils
ENV_KEY = 'SQL_PROFILER_SECRET'
+CONFIG_KEY = 'c2c.sql_profiler_secret'
LOG = logging.getLogger(__name__)
enabled = False
def _sql_profiler_view(request):
global enabled
- if request.params.get('secret') != os.environ[ENV_KEY]:
- raise HTTPForbidden('Missing or invalid secret parameter')
+ _utils.auth_view(request, ENV_KEY, CONFIG_KEY)
if 'enable' in request.params:
if request.params['enable'] == '1':
if not enabled:
@@ -68,7 +66,7 @@ def init(config):
"""
Install a pyramid event handler that adds the request information
"""
- if 'SQL_PROFILER_SECRET' in os.environ:
+ if _utils.env_or_config(config, ENV_KEY, CONFIG_KEY, False):
config.add_route("c2c_sql_profiler", _utils.get_base_path(config) + r"/sql_profiler",
request_method="GET")
config.add_view(_sql_profiler_view, route_name="c2c_sql_profiler", renderer="json", http_cache=0)
diff --git a/setup.py b/setup.py
index 0ee12b343..63b3e43c8 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@
from setuptools import setup, find_packages
-VERSION = '0.10.1'
+VERSION = '0.11.0'
HERE = os.path.abspath(os.path.dirname(__file__))
INSTALL_REQUIRES = open(os.path.join(HERE, 'rel_requirements.txt')).read().splitlines()