##// END OF EJS Templates
app: added statsd calls for monitoring.
milka -
r4632:ed2c6dae stable
parent child Browse files
Show More
@@ -0,0 +1,46 b''
1 from __future__ import absolute_import, division, unicode_literals
2
3 import logging
4
5 from .stream import TCPStatsClient, UnixSocketStatsClient # noqa
6 from .udp import StatsClient # noqa
7
8 HOST = 'localhost'
9 PORT = 8125
10 IPV6 = False
11 PREFIX = None
12 MAXUDPSIZE = 512
13
14 log = logging.getLogger('rhodecode.statsd')
15
16
17 def statsd_config(config, prefix='statsd.'):
18 _config = {}
19 for key in config.keys():
20 if key.startswith(prefix):
21 _config[key[len(prefix):]] = config[key]
22 return _config
23
24
25 def client_from_config(configuration, prefix='statsd.', **kwargs):
26 from pyramid.settings import asbool
27
28 _config = statsd_config(configuration, prefix)
29 statsd_enabled = asbool(_config.pop('enabled', False))
30 if not statsd_enabled:
31 log.debug('statsd client not enabled by statsd.enabled = flag, skipping...')
32 return
33
34 host = _config.pop('statsd_host', HOST)
35 port = _config.pop('statsd_port', PORT)
36 prefix = _config.pop('statsd_prefix', PREFIX)
37 maxudpsize = _config.pop('statsd_maxudpsize', MAXUDPSIZE)
38 ipv6 = asbool(_config.pop('statsd_ipv6', IPV6))
39 log.debug('configured statsd client %s:%s', host, port)
40
41 return StatsClient(
42 host=host, port=port, prefix=prefix, maxudpsize=maxudpsize, ipv6=ipv6)
43
44
45 def get_statsd_client(request):
46 return client_from_config(request.registry.settings)
@@ -0,0 +1,107 b''
1 from __future__ import absolute_import, division, unicode_literals
2
3 import random
4 from collections import deque
5 from datetime import timedelta
6
7 from .timer import Timer
8
9
10 class StatsClientBase(object):
11 """A Base class for various statsd clients."""
12
13 def close(self):
14 """Used to close and clean up any underlying resources."""
15 raise NotImplementedError()
16
17 def _send(self):
18 raise NotImplementedError()
19
20 def pipeline(self):
21 raise NotImplementedError()
22
23 def timer(self, stat, rate=1):
24 return Timer(self, stat, rate)
25
26 def timing(self, stat, delta, rate=1):
27 """
28 Send new timing information.
29
30 `delta` can be either a number of milliseconds or a timedelta.
31 """
32 if isinstance(delta, timedelta):
33 # Convert timedelta to number of milliseconds.
34 delta = delta.total_seconds() * 1000.
35 self._send_stat(stat, '%0.6f|ms' % delta, rate)
36
37 def incr(self, stat, count=1, rate=1):
38 """Increment a stat by `count`."""
39 self._send_stat(stat, '%s|c' % count, rate)
40
41 def decr(self, stat, count=1, rate=1):
42 """Decrement a stat by `count`."""
43 self.incr(stat, -count, rate)
44
45 def gauge(self, stat, value, rate=1, delta=False):
46 """Set a gauge value."""
47 if value < 0 and not delta:
48 if rate < 1:
49 if random.random() > rate:
50 return
51 with self.pipeline() as pipe:
52 pipe._send_stat(stat, '0|g', 1)
53 pipe._send_stat(stat, '%s|g' % value, 1)
54 else:
55 prefix = '+' if delta and value >= 0 else ''
56 self._send_stat(stat, '%s%s|g' % (prefix, value), rate)
57
58 def set(self, stat, value, rate=1):
59 """Set a set value."""
60 self._send_stat(stat, '%s|s' % value, rate)
61
62 def _send_stat(self, stat, value, rate):
63 self._after(self._prepare(stat, value, rate))
64
65 def _prepare(self, stat, value, rate):
66 if rate < 1:
67 if random.random() > rate:
68 return
69 value = '%s|@%s' % (value, rate)
70
71 if self._prefix:
72 stat = '%s.%s' % (self._prefix, stat)
73
74 return '%s:%s' % (stat, value)
75
76 def _after(self, data):
77 if data:
78 self._send(data)
79
80
81 class PipelineBase(StatsClientBase):
82
83 def __init__(self, client):
84 self._client = client
85 self._prefix = client._prefix
86 self._stats = deque()
87
88 def _send(self):
89 raise NotImplementedError()
90
91 def _after(self, data):
92 if data is not None:
93 self._stats.append(data)
94
95 def __enter__(self):
96 return self
97
98 def __exit__(self, typ, value, tb):
99 self.send()
100
101 def send(self):
102 if not self._stats:
103 return
104 self._send()
105
106 def pipeline(self):
107 return self.__class__(self)
@@ -0,0 +1,75 b''
1 from __future__ import absolute_import, division, unicode_literals
2
3 import socket
4
5 from .base import StatsClientBase, PipelineBase
6
7
8 class StreamPipeline(PipelineBase):
9 def _send(self):
10 self._client._after('\n'.join(self._stats))
11 self._stats.clear()
12
13
14 class StreamClientBase(StatsClientBase):
15 def connect(self):
16 raise NotImplementedError()
17
18 def close(self):
19 if self._sock and hasattr(self._sock, 'close'):
20 self._sock.close()
21 self._sock = None
22
23 def reconnect(self):
24 self.close()
25 self.connect()
26
27 def pipeline(self):
28 return StreamPipeline(self)
29
30 def _send(self, data):
31 """Send data to statsd."""
32 if not self._sock:
33 self.connect()
34 self._do_send(data)
35
36 def _do_send(self, data):
37 self._sock.sendall(data.encode('ascii') + b'\n')
38
39
40 class TCPStatsClient(StreamClientBase):
41 """TCP version of StatsClient."""
42
43 def __init__(self, host='localhost', port=8125, prefix=None,
44 timeout=None, ipv6=False):
45 """Create a new client."""
46 self._host = host
47 self._port = port
48 self._ipv6 = ipv6
49 self._timeout = timeout
50 self._prefix = prefix
51 self._sock = None
52
53 def connect(self):
54 fam = socket.AF_INET6 if self._ipv6 else socket.AF_INET
55 family, _, _, _, addr = socket.getaddrinfo(
56 self._host, self._port, fam, socket.SOCK_STREAM)[0]
57 self._sock = socket.socket(family, socket.SOCK_STREAM)
58 self._sock.settimeout(self._timeout)
59 self._sock.connect(addr)
60
61
62 class UnixSocketStatsClient(StreamClientBase):
63 """Unix domain socket version of StatsClient."""
64
65 def __init__(self, socket_path, prefix=None, timeout=None):
66 """Create a new client."""
67 self._socket_path = socket_path
68 self._timeout = timeout
69 self._prefix = prefix
70 self._sock = None
71
72 def connect(self):
73 self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
74 self._sock.settimeout(self._timeout)
75 self._sock.connect(self._socket_path)
@@ -0,0 +1,71 b''
1 from __future__ import absolute_import, division, unicode_literals
2
3 import functools
4
5 # Use timer that's not susceptible to time of day adjustments.
6 try:
7 # perf_counter is only present on Py3.3+
8 from time import perf_counter as time_now
9 except ImportError:
10 # fall back to using time
11 from time import time as time_now
12
13
14 def safe_wraps(wrapper, *args, **kwargs):
15 """Safely wraps partial functions."""
16 while isinstance(wrapper, functools.partial):
17 wrapper = wrapper.func
18 return functools.wraps(wrapper, *args, **kwargs)
19
20
21 class Timer(object):
22 """A context manager/decorator for statsd.timing()."""
23
24 def __init__(self, client, stat, rate=1):
25 self.client = client
26 self.stat = stat
27 self.rate = rate
28 self.ms = None
29 self._sent = False
30 self._start_time = None
31
32 def __call__(self, f):
33 """Thread-safe timing function decorator."""
34 @safe_wraps(f)
35 def _wrapped(*args, **kwargs):
36 start_time = time_now()
37 try:
38 return f(*args, **kwargs)
39 finally:
40 elapsed_time_ms = 1000.0 * (time_now() - start_time)
41 self.client.timing(self.stat, elapsed_time_ms, self.rate)
42 return _wrapped
43
44 def __enter__(self):
45 return self.start()
46
47 def __exit__(self, typ, value, tb):
48 self.stop()
49
50 def start(self):
51 self.ms = None
52 self._sent = False
53 self._start_time = time_now()
54 return self
55
56 def stop(self, send=True):
57 if self._start_time is None:
58 raise RuntimeError('Timer has not started.')
59 dt = time_now() - self._start_time
60 self.ms = 1000.0 * dt # Convert to milliseconds.
61 if send:
62 self.send()
63 return self
64
65 def send(self):
66 if self.ms is None:
67 raise RuntimeError('No data recorded.')
68 if self._sent:
69 raise RuntimeError('Already sent data.')
70 self._sent = True
71 self.client.timing(self.stat, self.ms, self.rate)
@@ -0,0 +1,55 b''
1 from __future__ import absolute_import, division, unicode_literals
2
3 import socket
4
5 from .base import StatsClientBase, PipelineBase
6
7
8 class Pipeline(PipelineBase):
9
10 def __init__(self, client):
11 super(Pipeline, self).__init__(client)
12 self._maxudpsize = client._maxudpsize
13
14 def _send(self):
15 data = self._stats.popleft()
16 while self._stats:
17 # Use popleft to preserve the order of the stats.
18 stat = self._stats.popleft()
19 if len(stat) + len(data) + 1 >= self._maxudpsize:
20 self._client._after(data)
21 data = stat
22 else:
23 data += '\n' + stat
24 self._client._after(data)
25
26
27 class StatsClient(StatsClientBase):
28 """A client for statsd."""
29
30 def __init__(self, host='localhost', port=8125, prefix=None,
31 maxudpsize=512, ipv6=False):
32 """Create a new client."""
33 fam = socket.AF_INET6 if ipv6 else socket.AF_INET
34 family, _, _, _, addr = socket.getaddrinfo(
35 host, port, fam, socket.SOCK_DGRAM)[0]
36 self._addr = addr
37 self._sock = socket.socket(family, socket.SOCK_DGRAM)
38 self._prefix = prefix
39 self._maxudpsize = maxudpsize
40
41 def _send(self, data):
42 """Send data to statsd."""
43 try:
44 self._sock.sendto(data.encode('ascii'), self._addr)
45 except (socket.error, RuntimeError):
46 # No time for love, Dr. Jones!
47 pass
48
49 def close(self):
50 if self._sock and hasattr(self._sock, 'close'):
51 self._sock.close()
52 self._sock = None
53
54 def pipeline(self):
55 return Pipeline(self)
@@ -1,778 +1,782 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import tempfile
25 import tempfile
26 import time
26 import time
27
27
28 from paste.gzipper import make_gzip_middleware
28 from paste.gzipper import make_gzip_middleware
29 import pyramid.events
29 import pyramid.events
30 from pyramid.wsgi import wsgiapp
30 from pyramid.wsgi import wsgiapp
31 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.authorization import ACLAuthorizationPolicy
32 from pyramid.config import Configurator
32 from pyramid.config import Configurator
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.httpexceptions import (
34 from pyramid.httpexceptions import (
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 from pyramid.renderers import render_to_response
36 from pyramid.renderers import render_to_response
37
37
38 from rhodecode.model import meta
38 from rhodecode.model import meta
39 from rhodecode.config import patches
39 from rhodecode.config import patches
40 from rhodecode.config import utils as config_utils
40 from rhodecode.config import utils as config_utils
41 from rhodecode.config.environment import load_pyramid_environment
41 from rhodecode.config.environment import load_pyramid_environment
42
42
43 import rhodecode.events
43 import rhodecode.events
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 from rhodecode.lib.request import Request
45 from rhodecode.lib.request import Request
46 from rhodecode.lib.vcs import VCSCommunicationError
46 from rhodecode.lib.vcs import VCSCommunicationError
47 from rhodecode.lib.exceptions import VCSServerUnavailable
47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
50 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
51 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 from rhodecode.lib.exc_tracking import store_exception
52 from rhodecode.lib.exc_tracking import store_exception
53 from rhodecode.subscribers import (
53 from rhodecode.subscribers import (
54 scan_repositories_if_enabled, write_js_routes_if_enabled,
54 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 write_metadata_if_needed, write_usage_data)
55 write_metadata_if_needed, write_usage_data)
56
56
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60
60
61 def is_http_error(response):
61 def is_http_error(response):
62 # error which should have traceback
62 # error which should have traceback
63 return response.status_code > 499
63 return response.status_code > 499
64
64
65
65
66 def should_load_all():
66 def should_load_all():
67 """
67 """
68 Returns if all application components should be loaded. In some cases it's
68 Returns if all application components should be loaded. In some cases it's
69 desired to skip apps loading for faster shell script execution
69 desired to skip apps loading for faster shell script execution
70 """
70 """
71 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
71 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
72 if ssh_cmd:
72 if ssh_cmd:
73 return False
73 return False
74
74
75 return True
75 return True
76
76
77
77
78 def make_pyramid_app(global_config, **settings):
78 def make_pyramid_app(global_config, **settings):
79 """
79 """
80 Constructs the WSGI application based on Pyramid.
80 Constructs the WSGI application based on Pyramid.
81
81
82 Specials:
82 Specials:
83
83
84 * The application can also be integrated like a plugin via the call to
84 * The application can also be integrated like a plugin via the call to
85 `includeme`. This is accompanied with the other utility functions which
85 `includeme`. This is accompanied with the other utility functions which
86 are called. Changing this should be done with great care to not break
86 are called. Changing this should be done with great care to not break
87 cases when these fragments are assembled from another place.
87 cases when these fragments are assembled from another place.
88
88
89 """
89 """
90
90
91 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
91 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
92 # will be replaced by the value of the environment variable "NAME" in this case.
92 # will be replaced by the value of the environment variable "NAME" in this case.
93 start_time = time.time()
93 start_time = time.time()
94 log.info('Pyramid app config starting')
94 log.info('Pyramid app config starting')
95
95
96 debug = asbool(global_config.get('debug'))
96 debug = asbool(global_config.get('debug'))
97 if debug:
97 if debug:
98 enable_debug()
98 enable_debug()
99
99
100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
101
101
102 global_config = _substitute_values(global_config, environ)
102 global_config = _substitute_values(global_config, environ)
103 settings = _substitute_values(settings, environ)
103 settings = _substitute_values(settings, environ)
104
104
105 sanitize_settings_and_apply_defaults(global_config, settings)
105 sanitize_settings_and_apply_defaults(global_config, settings)
106
106
107 config = Configurator(settings=settings)
107 config = Configurator(settings=settings)
108
108
109 # Apply compatibility patches
109 # Apply compatibility patches
110 patches.inspect_getargspec()
110 patches.inspect_getargspec()
111
111
112 load_pyramid_environment(global_config, settings)
112 load_pyramid_environment(global_config, settings)
113
113
114 # Static file view comes first
114 # Static file view comes first
115 includeme_first(config)
115 includeme_first(config)
116
116
117 includeme(config)
117 includeme(config)
118
118
119 pyramid_app = config.make_wsgi_app()
119 pyramid_app = config.make_wsgi_app()
120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
121 pyramid_app.config = config
121 pyramid_app.config = config
122
122
123 config.configure_celery(global_config['__file__'])
123 config.configure_celery(global_config['__file__'])
124
124
125 # creating the app uses a connection - return it after we are done
125 # creating the app uses a connection - return it after we are done
126 meta.Session.remove()
126 meta.Session.remove()
127 total_time = time.time() - start_time
127 total_time = time.time() - start_time
128 log.info('Pyramid app `%s` created and configured in %.2fs',
128 log.info('Pyramid app `%s` created and configured in %.2fs',
129 pyramid_app.func_name, total_time)
129 pyramid_app.func_name, total_time)
130
130
131 return pyramid_app
131 return pyramid_app
132
132
133
133
134 def not_found_view(request):
134 def not_found_view(request):
135 """
135 """
136 This creates the view which should be registered as not-found-view to
136 This creates the view which should be registered as not-found-view to
137 pyramid.
137 pyramid.
138 """
138 """
139
139
140 if not getattr(request, 'vcs_call', None):
140 if not getattr(request, 'vcs_call', None):
141 # handle like regular case with our error_handler
141 # handle like regular case with our error_handler
142 return error_handler(HTTPNotFound(), request)
142 return error_handler(HTTPNotFound(), request)
143
143
144 # handle not found view as a vcs call
144 # handle not found view as a vcs call
145 settings = request.registry.settings
145 settings = request.registry.settings
146 ae_client = getattr(request, 'ae_client', None)
146 ae_client = getattr(request, 'ae_client', None)
147 vcs_app = VCSMiddleware(
147 vcs_app = VCSMiddleware(
148 HTTPNotFound(), request.registry, settings,
148 HTTPNotFound(), request.registry, settings,
149 appenlight_client=ae_client)
149 appenlight_client=ae_client)
150
150
151 return wsgiapp(vcs_app)(None, request)
151 return wsgiapp(vcs_app)(None, request)
152
152
153
153
154 def error_handler(exception, request):
154 def error_handler(exception, request):
155 import rhodecode
155 import rhodecode
156 from rhodecode.lib import helpers
156 from rhodecode.lib import helpers
157
157
158 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
158 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
159
159
160 base_response = HTTPInternalServerError()
160 base_response = HTTPInternalServerError()
161 # prefer original exception for the response since it may have headers set
161 # prefer original exception for the response since it may have headers set
162 if isinstance(exception, HTTPException):
162 if isinstance(exception, HTTPException):
163 base_response = exception
163 base_response = exception
164 elif isinstance(exception, VCSCommunicationError):
164 elif isinstance(exception, VCSCommunicationError):
165 base_response = VCSServerUnavailable()
165 base_response = VCSServerUnavailable()
166
166
167 if is_http_error(base_response):
167 if is_http_error(base_response):
168 log.exception(
168 log.exception(
169 'error occurred handling this request for path: %s', request.path)
169 'error occurred handling this request for path: %s', request.path)
170
170
171 error_explanation = base_response.explanation or str(base_response)
171 error_explanation = base_response.explanation or str(base_response)
172 if base_response.status_code == 404:
172 if base_response.status_code == 404:
173 error_explanation += " Optionally you don't have permission to access this page."
173 error_explanation += " Optionally you don't have permission to access this page."
174 c = AttributeDict()
174 c = AttributeDict()
175 c.error_message = base_response.status
175 c.error_message = base_response.status
176 c.error_explanation = error_explanation
176 c.error_explanation = error_explanation
177 c.visual = AttributeDict()
177 c.visual = AttributeDict()
178
178
179 c.visual.rhodecode_support_url = (
179 c.visual.rhodecode_support_url = (
180 request.registry.settings.get('rhodecode_support_url') or
180 request.registry.settings.get('rhodecode_support_url') or
181 request.route_url('rhodecode_support')
181 request.route_url('rhodecode_support')
182 )
182 )
183 c.redirect_time = 0
183 c.redirect_time = 0
184 c.rhodecode_name = rhodecode_title
184 c.rhodecode_name = rhodecode_title
185 if not c.rhodecode_name:
185 if not c.rhodecode_name:
186 c.rhodecode_name = 'Rhodecode'
186 c.rhodecode_name = 'Rhodecode'
187
187
188 c.causes = []
188 c.causes = []
189 if is_http_error(base_response):
189 if is_http_error(base_response):
190 c.causes.append('Server is overloaded.')
190 c.causes.append('Server is overloaded.')
191 c.causes.append('Server database connection is lost.')
191 c.causes.append('Server database connection is lost.')
192 c.causes.append('Server expected unhandled error.')
192 c.causes.append('Server expected unhandled error.')
193
193
194 if hasattr(base_response, 'causes'):
194 if hasattr(base_response, 'causes'):
195 c.causes = base_response.causes
195 c.causes = base_response.causes
196
196
197 c.messages = helpers.flash.pop_messages(request=request)
197 c.messages = helpers.flash.pop_messages(request=request)
198
198
199 exc_info = sys.exc_info()
199 exc_info = sys.exc_info()
200 c.exception_id = id(exc_info)
200 c.exception_id = id(exc_info)
201 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
201 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
202 or base_response.status_code > 499
202 or base_response.status_code > 499
203 c.exception_id_url = request.route_url(
203 c.exception_id_url = request.route_url(
204 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
204 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
205
205
206 if c.show_exception_id:
206 if c.show_exception_id:
207 store_exception(c.exception_id, exc_info)
207 store_exception(c.exception_id, exc_info)
208
208
209 response = render_to_response(
209 response = render_to_response(
210 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
210 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
211 response=base_response)
211 response=base_response)
212
212
213 return response
213 return response
214
214
215
215
216 def includeme_first(config):
216 def includeme_first(config):
217 # redirect automatic browser favicon.ico requests to correct place
217 # redirect automatic browser favicon.ico requests to correct place
218 def favicon_redirect(context, request):
218 def favicon_redirect(context, request):
219 return HTTPFound(
219 return HTTPFound(
220 request.static_path('rhodecode:public/images/favicon.ico'))
220 request.static_path('rhodecode:public/images/favicon.ico'))
221
221
222 config.add_view(favicon_redirect, route_name='favicon')
222 config.add_view(favicon_redirect, route_name='favicon')
223 config.add_route('favicon', '/favicon.ico')
223 config.add_route('favicon', '/favicon.ico')
224
224
225 def robots_redirect(context, request):
225 def robots_redirect(context, request):
226 return HTTPFound(
226 return HTTPFound(
227 request.static_path('rhodecode:public/robots.txt'))
227 request.static_path('rhodecode:public/robots.txt'))
228
228
229 config.add_view(robots_redirect, route_name='robots')
229 config.add_view(robots_redirect, route_name='robots')
230 config.add_route('robots', '/robots.txt')
230 config.add_route('robots', '/robots.txt')
231
231
232 config.add_static_view(
232 config.add_static_view(
233 '_static/deform', 'deform:static')
233 '_static/deform', 'deform:static')
234 config.add_static_view(
234 config.add_static_view(
235 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
235 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
236
236
237
237
238 def includeme(config, auth_resources=None):
238 def includeme(config, auth_resources=None):
239 from rhodecode.lib.celerylib.loader import configure_celery
239 from rhodecode.lib.celerylib.loader import configure_celery
240 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
240 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
241 settings = config.registry.settings
241 settings = config.registry.settings
242 config.set_request_factory(Request)
242 config.set_request_factory(Request)
243
243
244 # plugin information
244 # plugin information
245 config.registry.rhodecode_plugins = collections.OrderedDict()
245 config.registry.rhodecode_plugins = collections.OrderedDict()
246
246
247 config.add_directive(
247 config.add_directive(
248 'register_rhodecode_plugin', register_rhodecode_plugin)
248 'register_rhodecode_plugin', register_rhodecode_plugin)
249
249
250 config.add_directive('configure_celery', configure_celery)
250 config.add_directive('configure_celery', configure_celery)
251
251
252 if asbool(settings.get('appenlight', 'false')):
252 if asbool(settings.get('appenlight', 'false')):
253 config.include('appenlight_client.ext.pyramid_tween')
253 config.include('appenlight_client.ext.pyramid_tween')
254
254
255 load_all = should_load_all()
255 load_all = should_load_all()
256
256
257 # Includes which are required. The application would fail without them.
257 # Includes which are required. The application would fail without them.
258 config.include('pyramid_mako')
258 config.include('pyramid_mako')
259 config.include('rhodecode.lib.rc_beaker')
259 config.include('rhodecode.lib.rc_beaker')
260 config.include('rhodecode.lib.rc_cache')
260 config.include('rhodecode.lib.rc_cache')
261 config.include('rhodecode.apps._base.navigation')
261 config.include('rhodecode.apps._base.navigation')
262 config.include('rhodecode.apps._base.subscribers')
262 config.include('rhodecode.apps._base.subscribers')
263 config.include('rhodecode.tweens')
263 config.include('rhodecode.tweens')
264 config.include('rhodecode.authentication')
264 config.include('rhodecode.authentication')
265
265
266 if load_all:
266 if load_all:
267 ce_auth_resources = [
267 ce_auth_resources = [
268 'rhodecode.authentication.plugins.auth_crowd',
268 'rhodecode.authentication.plugins.auth_crowd',
269 'rhodecode.authentication.plugins.auth_headers',
269 'rhodecode.authentication.plugins.auth_headers',
270 'rhodecode.authentication.plugins.auth_jasig_cas',
270 'rhodecode.authentication.plugins.auth_jasig_cas',
271 'rhodecode.authentication.plugins.auth_ldap',
271 'rhodecode.authentication.plugins.auth_ldap',
272 'rhodecode.authentication.plugins.auth_pam',
272 'rhodecode.authentication.plugins.auth_pam',
273 'rhodecode.authentication.plugins.auth_rhodecode',
273 'rhodecode.authentication.plugins.auth_rhodecode',
274 'rhodecode.authentication.plugins.auth_token',
274 'rhodecode.authentication.plugins.auth_token',
275 ]
275 ]
276
276
277 # load CE authentication plugins
277 # load CE authentication plugins
278
278
279 if auth_resources:
279 if auth_resources:
280 ce_auth_resources.extend(auth_resources)
280 ce_auth_resources.extend(auth_resources)
281
281
282 for resource in ce_auth_resources:
282 for resource in ce_auth_resources:
283 config.include(resource)
283 config.include(resource)
284
284
285 # Auto discover authentication plugins and include their configuration.
285 # Auto discover authentication plugins and include their configuration.
286 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
286 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
287 from rhodecode.authentication import discover_legacy_plugins
287 from rhodecode.authentication import discover_legacy_plugins
288 discover_legacy_plugins(config)
288 discover_legacy_plugins(config)
289
289
290 # apps
290 # apps
291 if load_all:
291 if load_all:
292 config.include('rhodecode.api')
292 config.include('rhodecode.api')
293 config.include('rhodecode.apps._base')
293 config.include('rhodecode.apps._base')
294 config.include('rhodecode.apps.hovercards')
294 config.include('rhodecode.apps.hovercards')
295 config.include('rhodecode.apps.ops')
295 config.include('rhodecode.apps.ops')
296 config.include('rhodecode.apps.channelstream')
296 config.include('rhodecode.apps.channelstream')
297 config.include('rhodecode.apps.file_store')
297 config.include('rhodecode.apps.file_store')
298 config.include('rhodecode.apps.admin')
298 config.include('rhodecode.apps.admin')
299 config.include('rhodecode.apps.login')
299 config.include('rhodecode.apps.login')
300 config.include('rhodecode.apps.home')
300 config.include('rhodecode.apps.home')
301 config.include('rhodecode.apps.journal')
301 config.include('rhodecode.apps.journal')
302
302
303 config.include('rhodecode.apps.repository')
303 config.include('rhodecode.apps.repository')
304 config.include('rhodecode.apps.repo_group')
304 config.include('rhodecode.apps.repo_group')
305 config.include('rhodecode.apps.user_group')
305 config.include('rhodecode.apps.user_group')
306 config.include('rhodecode.apps.search')
306 config.include('rhodecode.apps.search')
307 config.include('rhodecode.apps.user_profile')
307 config.include('rhodecode.apps.user_profile')
308 config.include('rhodecode.apps.user_group_profile')
308 config.include('rhodecode.apps.user_group_profile')
309 config.include('rhodecode.apps.my_account')
309 config.include('rhodecode.apps.my_account')
310 config.include('rhodecode.apps.gist')
310 config.include('rhodecode.apps.gist')
311
311
312 config.include('rhodecode.apps.svn_support')
312 config.include('rhodecode.apps.svn_support')
313 config.include('rhodecode.apps.ssh_support')
313 config.include('rhodecode.apps.ssh_support')
314 config.include('rhodecode.apps.debug_style')
314 config.include('rhodecode.apps.debug_style')
315
315
316 if load_all:
316 if load_all:
317 config.include('rhodecode.integrations')
317 config.include('rhodecode.integrations')
318
318
319 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
319 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
320 config.add_translation_dirs('rhodecode:i18n/')
320 config.add_translation_dirs('rhodecode:i18n/')
321 settings['default_locale_name'] = settings.get('lang', 'en')
321 settings['default_locale_name'] = settings.get('lang', 'en')
322
322
323 # Add subscribers.
323 # Add subscribers.
324 if load_all:
324 if load_all:
325 config.add_subscriber(scan_repositories_if_enabled,
325 config.add_subscriber(scan_repositories_if_enabled,
326 pyramid.events.ApplicationCreated)
326 pyramid.events.ApplicationCreated)
327 config.add_subscriber(write_metadata_if_needed,
327 config.add_subscriber(write_metadata_if_needed,
328 pyramid.events.ApplicationCreated)
328 pyramid.events.ApplicationCreated)
329 config.add_subscriber(write_usage_data,
329 config.add_subscriber(write_usage_data,
330 pyramid.events.ApplicationCreated)
330 pyramid.events.ApplicationCreated)
331 config.add_subscriber(write_js_routes_if_enabled,
331 config.add_subscriber(write_js_routes_if_enabled,
332 pyramid.events.ApplicationCreated)
332 pyramid.events.ApplicationCreated)
333
333
334 # request custom methods
334 # request custom methods
335 config.add_request_method(
335 config.add_request_method(
336 'rhodecode.lib.partial_renderer.get_partial_renderer',
336 'rhodecode.lib.partial_renderer.get_partial_renderer',
337 'get_partial_renderer')
337 'get_partial_renderer')
338
338
339 config.add_request_method(
339 config.add_request_method(
340 'rhodecode.lib.request_counter.get_request_counter',
340 'rhodecode.lib.request_counter.get_request_counter',
341 'request_count')
341 'request_count')
342
342
343 config.add_request_method(
344 'rhodecode.lib._vendor.statsd.get_statsd_client',
345 'statsd', reify=True)
346
343 # Set the authorization policy.
347 # Set the authorization policy.
344 authz_policy = ACLAuthorizationPolicy()
348 authz_policy = ACLAuthorizationPolicy()
345 config.set_authorization_policy(authz_policy)
349 config.set_authorization_policy(authz_policy)
346
350
347 # Set the default renderer for HTML templates to mako.
351 # Set the default renderer for HTML templates to mako.
348 config.add_mako_renderer('.html')
352 config.add_mako_renderer('.html')
349
353
350 config.add_renderer(
354 config.add_renderer(
351 name='json_ext',
355 name='json_ext',
352 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
356 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
353
357
354 config.add_renderer(
358 config.add_renderer(
355 name='string_html',
359 name='string_html',
356 factory='rhodecode.lib.string_renderer.html')
360 factory='rhodecode.lib.string_renderer.html')
357
361
358 # include RhodeCode plugins
362 # include RhodeCode plugins
359 includes = aslist(settings.get('rhodecode.includes', []))
363 includes = aslist(settings.get('rhodecode.includes', []))
360 for inc in includes:
364 for inc in includes:
361 config.include(inc)
365 config.include(inc)
362
366
363 # custom not found view, if our pyramid app doesn't know how to handle
367 # custom not found view, if our pyramid app doesn't know how to handle
364 # the request pass it to potential VCS handling ap
368 # the request pass it to potential VCS handling ap
365 config.add_notfound_view(not_found_view)
369 config.add_notfound_view(not_found_view)
366 if not settings.get('debugtoolbar.enabled', False):
370 if not settings.get('debugtoolbar.enabled', False):
367 # disabled debugtoolbar handle all exceptions via the error_handlers
371 # disabled debugtoolbar handle all exceptions via the error_handlers
368 config.add_view(error_handler, context=Exception)
372 config.add_view(error_handler, context=Exception)
369
373
370 # all errors including 403/404/50X
374 # all errors including 403/404/50X
371 config.add_view(error_handler, context=HTTPError)
375 config.add_view(error_handler, context=HTTPError)
372
376
373
377
374 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
378 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
375 """
379 """
376 Apply outer WSGI middlewares around the application.
380 Apply outer WSGI middlewares around the application.
377 """
381 """
378 registry = config.registry
382 registry = config.registry
379 settings = registry.settings
383 settings = registry.settings
380
384
381 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
385 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
382 pyramid_app = HttpsFixup(pyramid_app, settings)
386 pyramid_app = HttpsFixup(pyramid_app, settings)
383
387
384 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
388 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
385 pyramid_app, settings)
389 pyramid_app, settings)
386 registry.ae_client = _ae_client
390 registry.ae_client = _ae_client
387
391
388 if settings['gzip_responses']:
392 if settings['gzip_responses']:
389 pyramid_app = make_gzip_middleware(
393 pyramid_app = make_gzip_middleware(
390 pyramid_app, settings, compress_level=1)
394 pyramid_app, settings, compress_level=1)
391
395
392 # this should be the outer most middleware in the wsgi stack since
396 # this should be the outer most middleware in the wsgi stack since
393 # middleware like Routes make database calls
397 # middleware like Routes make database calls
394 def pyramid_app_with_cleanup(environ, start_response):
398 def pyramid_app_with_cleanup(environ, start_response):
395 try:
399 try:
396 return pyramid_app(environ, start_response)
400 return pyramid_app(environ, start_response)
397 finally:
401 finally:
398 # Dispose current database session and rollback uncommitted
402 # Dispose current database session and rollback uncommitted
399 # transactions.
403 # transactions.
400 meta.Session.remove()
404 meta.Session.remove()
401
405
402 # In a single threaded mode server, on non sqlite db we should have
406 # In a single threaded mode server, on non sqlite db we should have
403 # '0 Current Checked out connections' at the end of a request,
407 # '0 Current Checked out connections' at the end of a request,
404 # if not, then something, somewhere is leaving a connection open
408 # if not, then something, somewhere is leaving a connection open
405 pool = meta.Base.metadata.bind.engine.pool
409 pool = meta.Base.metadata.bind.engine.pool
406 log.debug('sa pool status: %s', pool.status())
410 log.debug('sa pool status: %s', pool.status())
407 log.debug('Request processing finalized')
411 log.debug('Request processing finalized')
408
412
409 return pyramid_app_with_cleanup
413 return pyramid_app_with_cleanup
410
414
411
415
412 def sanitize_settings_and_apply_defaults(global_config, settings):
416 def sanitize_settings_and_apply_defaults(global_config, settings):
413 """
417 """
414 Applies settings defaults and does all type conversion.
418 Applies settings defaults and does all type conversion.
415
419
416 We would move all settings parsing and preparation into this place, so that
420 We would move all settings parsing and preparation into this place, so that
417 we have only one place left which deals with this part. The remaining parts
421 we have only one place left which deals with this part. The remaining parts
418 of the application would start to rely fully on well prepared settings.
422 of the application would start to rely fully on well prepared settings.
419
423
420 This piece would later be split up per topic to avoid a big fat monster
424 This piece would later be split up per topic to avoid a big fat monster
421 function.
425 function.
422 """
426 """
423
427
424 settings.setdefault('rhodecode.edition', 'Community Edition')
428 settings.setdefault('rhodecode.edition', 'Community Edition')
425 settings.setdefault('rhodecode.edition_id', 'CE')
429 settings.setdefault('rhodecode.edition_id', 'CE')
426
430
427 if 'mako.default_filters' not in settings:
431 if 'mako.default_filters' not in settings:
428 # set custom default filters if we don't have it defined
432 # set custom default filters if we don't have it defined
429 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
433 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
430 settings['mako.default_filters'] = 'h_filter'
434 settings['mako.default_filters'] = 'h_filter'
431
435
432 if 'mako.directories' not in settings:
436 if 'mako.directories' not in settings:
433 mako_directories = settings.setdefault('mako.directories', [
437 mako_directories = settings.setdefault('mako.directories', [
434 # Base templates of the original application
438 # Base templates of the original application
435 'rhodecode:templates',
439 'rhodecode:templates',
436 ])
440 ])
437 log.debug(
441 log.debug(
438 "Using the following Mako template directories: %s",
442 "Using the following Mako template directories: %s",
439 mako_directories)
443 mako_directories)
440
444
441 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
445 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
442 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
446 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
443 raw_url = settings['beaker.session.url']
447 raw_url = settings['beaker.session.url']
444 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
448 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
445 settings['beaker.session.url'] = 'redis://' + raw_url
449 settings['beaker.session.url'] = 'redis://' + raw_url
446
450
447 # Default includes, possible to change as a user
451 # Default includes, possible to change as a user
448 pyramid_includes = settings.setdefault('pyramid.includes', [])
452 pyramid_includes = settings.setdefault('pyramid.includes', [])
449 log.debug(
453 log.debug(
450 "Using the following pyramid.includes: %s",
454 "Using the following pyramid.includes: %s",
451 pyramid_includes)
455 pyramid_includes)
452
456
453 # TODO: johbo: Re-think this, usually the call to config.include
457 # TODO: johbo: Re-think this, usually the call to config.include
454 # should allow to pass in a prefix.
458 # should allow to pass in a prefix.
455 settings.setdefault('rhodecode.api.url', '/_admin/api')
459 settings.setdefault('rhodecode.api.url', '/_admin/api')
456 settings.setdefault('__file__', global_config.get('__file__'))
460 settings.setdefault('__file__', global_config.get('__file__'))
457
461
458 # Sanitize generic settings.
462 # Sanitize generic settings.
459 _list_setting(settings, 'default_encoding', 'UTF-8')
463 _list_setting(settings, 'default_encoding', 'UTF-8')
460 _bool_setting(settings, 'is_test', 'false')
464 _bool_setting(settings, 'is_test', 'false')
461 _bool_setting(settings, 'gzip_responses', 'false')
465 _bool_setting(settings, 'gzip_responses', 'false')
462
466
463 # Call split out functions that sanitize settings for each topic.
467 # Call split out functions that sanitize settings for each topic.
464 _sanitize_appenlight_settings(settings)
468 _sanitize_appenlight_settings(settings)
465 _sanitize_vcs_settings(settings)
469 _sanitize_vcs_settings(settings)
466 _sanitize_cache_settings(settings)
470 _sanitize_cache_settings(settings)
467
471
468 # configure instance id
472 # configure instance id
469 config_utils.set_instance_id(settings)
473 config_utils.set_instance_id(settings)
470
474
471 return settings
475 return settings
472
476
473
477
474 def enable_debug():
478 def enable_debug():
475 """
479 """
476 Helper to enable debug on running instance
480 Helper to enable debug on running instance
477 :return:
481 :return:
478 """
482 """
479 import tempfile
483 import tempfile
480 import textwrap
484 import textwrap
481 import logging.config
485 import logging.config
482
486
483 ini_template = textwrap.dedent("""
487 ini_template = textwrap.dedent("""
484 #####################################
488 #####################################
485 ### DEBUG LOGGING CONFIGURATION ####
489 ### DEBUG LOGGING CONFIGURATION ####
486 #####################################
490 #####################################
487 [loggers]
491 [loggers]
488 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
492 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
489
493
490 [handlers]
494 [handlers]
491 keys = console, console_sql
495 keys = console, console_sql
492
496
493 [formatters]
497 [formatters]
494 keys = generic, color_formatter, color_formatter_sql
498 keys = generic, color_formatter, color_formatter_sql
495
499
496 #############
500 #############
497 ## LOGGERS ##
501 ## LOGGERS ##
498 #############
502 #############
499 [logger_root]
503 [logger_root]
500 level = NOTSET
504 level = NOTSET
501 handlers = console
505 handlers = console
502
506
503 [logger_sqlalchemy]
507 [logger_sqlalchemy]
504 level = INFO
508 level = INFO
505 handlers = console_sql
509 handlers = console_sql
506 qualname = sqlalchemy.engine
510 qualname = sqlalchemy.engine
507 propagate = 0
511 propagate = 0
508
512
509 [logger_beaker]
513 [logger_beaker]
510 level = DEBUG
514 level = DEBUG
511 handlers =
515 handlers =
512 qualname = beaker.container
516 qualname = beaker.container
513 propagate = 1
517 propagate = 1
514
518
515 [logger_rhodecode]
519 [logger_rhodecode]
516 level = DEBUG
520 level = DEBUG
517 handlers =
521 handlers =
518 qualname = rhodecode
522 qualname = rhodecode
519 propagate = 1
523 propagate = 1
520
524
521 [logger_ssh_wrapper]
525 [logger_ssh_wrapper]
522 level = DEBUG
526 level = DEBUG
523 handlers =
527 handlers =
524 qualname = ssh_wrapper
528 qualname = ssh_wrapper
525 propagate = 1
529 propagate = 1
526
530
527 [logger_celery]
531 [logger_celery]
528 level = DEBUG
532 level = DEBUG
529 handlers =
533 handlers =
530 qualname = celery
534 qualname = celery
531
535
532
536
533 ##############
537 ##############
534 ## HANDLERS ##
538 ## HANDLERS ##
535 ##############
539 ##############
536
540
537 [handler_console]
541 [handler_console]
538 class = StreamHandler
542 class = StreamHandler
539 args = (sys.stderr, )
543 args = (sys.stderr, )
540 level = DEBUG
544 level = DEBUG
541 formatter = color_formatter
545 formatter = color_formatter
542
546
543 [handler_console_sql]
547 [handler_console_sql]
544 # "level = DEBUG" logs SQL queries and results.
548 # "level = DEBUG" logs SQL queries and results.
545 # "level = INFO" logs SQL queries.
549 # "level = INFO" logs SQL queries.
546 # "level = WARN" logs neither. (Recommended for production systems.)
550 # "level = WARN" logs neither. (Recommended for production systems.)
547 class = StreamHandler
551 class = StreamHandler
548 args = (sys.stderr, )
552 args = (sys.stderr, )
549 level = WARN
553 level = WARN
550 formatter = color_formatter_sql
554 formatter = color_formatter_sql
551
555
552 ################
556 ################
553 ## FORMATTERS ##
557 ## FORMATTERS ##
554 ################
558 ################
555
559
556 [formatter_generic]
560 [formatter_generic]
557 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
561 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
558 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
562 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
559 datefmt = %Y-%m-%d %H:%M:%S
563 datefmt = %Y-%m-%d %H:%M:%S
560
564
561 [formatter_color_formatter]
565 [formatter_color_formatter]
562 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
566 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
563 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
567 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
564 datefmt = %Y-%m-%d %H:%M:%S
568 datefmt = %Y-%m-%d %H:%M:%S
565
569
566 [formatter_color_formatter_sql]
570 [formatter_color_formatter_sql]
567 class = rhodecode.lib.logging_formatter.ColorFormatterSql
571 class = rhodecode.lib.logging_formatter.ColorFormatterSql
568 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
572 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
569 datefmt = %Y-%m-%d %H:%M:%S
573 datefmt = %Y-%m-%d %H:%M:%S
570 """)
574 """)
571
575
572 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
576 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
573 delete=False) as f:
577 delete=False) as f:
574 log.info('Saved Temporary DEBUG config at %s', f.name)
578 log.info('Saved Temporary DEBUG config at %s', f.name)
575 f.write(ini_template)
579 f.write(ini_template)
576
580
577 logging.config.fileConfig(f.name)
581 logging.config.fileConfig(f.name)
578 log.debug('DEBUG MODE ON')
582 log.debug('DEBUG MODE ON')
579 os.remove(f.name)
583 os.remove(f.name)
580
584
581
585
582 def _sanitize_appenlight_settings(settings):
586 def _sanitize_appenlight_settings(settings):
583 _bool_setting(settings, 'appenlight', 'false')
587 _bool_setting(settings, 'appenlight', 'false')
584
588
585
589
586 def _sanitize_vcs_settings(settings):
590 def _sanitize_vcs_settings(settings):
587 """
591 """
588 Applies settings defaults and does type conversion for all VCS related
592 Applies settings defaults and does type conversion for all VCS related
589 settings.
593 settings.
590 """
594 """
591 _string_setting(settings, 'vcs.svn.compatible_version', '')
595 _string_setting(settings, 'vcs.svn.compatible_version', '')
592 _string_setting(settings, 'vcs.hooks.protocol', 'http')
596 _string_setting(settings, 'vcs.hooks.protocol', 'http')
593 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
597 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
594 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
598 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
595 _string_setting(settings, 'vcs.server', '')
599 _string_setting(settings, 'vcs.server', '')
596 _string_setting(settings, 'vcs.server.protocol', 'http')
600 _string_setting(settings, 'vcs.server.protocol', 'http')
597 _bool_setting(settings, 'startup.import_repos', 'false')
601 _bool_setting(settings, 'startup.import_repos', 'false')
598 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
602 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
599 _bool_setting(settings, 'vcs.server.enable', 'true')
603 _bool_setting(settings, 'vcs.server.enable', 'true')
600 _bool_setting(settings, 'vcs.start_server', 'false')
604 _bool_setting(settings, 'vcs.start_server', 'false')
601 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
605 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
602 _int_setting(settings, 'vcs.connection_timeout', 3600)
606 _int_setting(settings, 'vcs.connection_timeout', 3600)
603
607
604 # Support legacy values of vcs.scm_app_implementation. Legacy
608 # Support legacy values of vcs.scm_app_implementation. Legacy
605 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
609 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
606 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
610 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
607 scm_app_impl = settings['vcs.scm_app_implementation']
611 scm_app_impl = settings['vcs.scm_app_implementation']
608 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
612 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
609 settings['vcs.scm_app_implementation'] = 'http'
613 settings['vcs.scm_app_implementation'] = 'http'
610
614
611
615
612 def _sanitize_cache_settings(settings):
616 def _sanitize_cache_settings(settings):
613 temp_store = tempfile.gettempdir()
617 temp_store = tempfile.gettempdir()
614 default_cache_dir = os.path.join(temp_store, 'rc_cache')
618 default_cache_dir = os.path.join(temp_store, 'rc_cache')
615
619
616 # save default, cache dir, and use it for all backends later.
620 # save default, cache dir, and use it for all backends later.
617 default_cache_dir = _string_setting(
621 default_cache_dir = _string_setting(
618 settings,
622 settings,
619 'cache_dir',
623 'cache_dir',
620 default_cache_dir, lower=False, default_when_empty=True)
624 default_cache_dir, lower=False, default_when_empty=True)
621
625
622 # ensure we have our dir created
626 # ensure we have our dir created
623 if not os.path.isdir(default_cache_dir):
627 if not os.path.isdir(default_cache_dir):
624 os.makedirs(default_cache_dir, mode=0o755)
628 os.makedirs(default_cache_dir, mode=0o755)
625
629
626 # exception store cache
630 # exception store cache
627 _string_setting(
631 _string_setting(
628 settings,
632 settings,
629 'exception_tracker.store_path',
633 'exception_tracker.store_path',
630 temp_store, lower=False, default_when_empty=True)
634 temp_store, lower=False, default_when_empty=True)
631 _bool_setting(
635 _bool_setting(
632 settings,
636 settings,
633 'exception_tracker.send_email',
637 'exception_tracker.send_email',
634 'false')
638 'false')
635 _string_setting(
639 _string_setting(
636 settings,
640 settings,
637 'exception_tracker.email_prefix',
641 'exception_tracker.email_prefix',
638 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
642 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
639
643
640 # cache_perms
644 # cache_perms
641 _string_setting(
645 _string_setting(
642 settings,
646 settings,
643 'rc_cache.cache_perms.backend',
647 'rc_cache.cache_perms.backend',
644 'dogpile.cache.rc.file_namespace', lower=False)
648 'dogpile.cache.rc.file_namespace', lower=False)
645 _int_setting(
649 _int_setting(
646 settings,
650 settings,
647 'rc_cache.cache_perms.expiration_time',
651 'rc_cache.cache_perms.expiration_time',
648 60)
652 60)
649 _string_setting(
653 _string_setting(
650 settings,
654 settings,
651 'rc_cache.cache_perms.arguments.filename',
655 'rc_cache.cache_perms.arguments.filename',
652 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
656 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
653
657
654 # cache_repo
658 # cache_repo
655 _string_setting(
659 _string_setting(
656 settings,
660 settings,
657 'rc_cache.cache_repo.backend',
661 'rc_cache.cache_repo.backend',
658 'dogpile.cache.rc.file_namespace', lower=False)
662 'dogpile.cache.rc.file_namespace', lower=False)
659 _int_setting(
663 _int_setting(
660 settings,
664 settings,
661 'rc_cache.cache_repo.expiration_time',
665 'rc_cache.cache_repo.expiration_time',
662 60)
666 60)
663 _string_setting(
667 _string_setting(
664 settings,
668 settings,
665 'rc_cache.cache_repo.arguments.filename',
669 'rc_cache.cache_repo.arguments.filename',
666 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
670 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
667
671
668 # cache_license
672 # cache_license
669 _string_setting(
673 _string_setting(
670 settings,
674 settings,
671 'rc_cache.cache_license.backend',
675 'rc_cache.cache_license.backend',
672 'dogpile.cache.rc.file_namespace', lower=False)
676 'dogpile.cache.rc.file_namespace', lower=False)
673 _int_setting(
677 _int_setting(
674 settings,
678 settings,
675 'rc_cache.cache_license.expiration_time',
679 'rc_cache.cache_license.expiration_time',
676 5*60)
680 5*60)
677 _string_setting(
681 _string_setting(
678 settings,
682 settings,
679 'rc_cache.cache_license.arguments.filename',
683 'rc_cache.cache_license.arguments.filename',
680 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
684 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
681
685
682 # cache_repo_longterm memory, 96H
686 # cache_repo_longterm memory, 96H
683 _string_setting(
687 _string_setting(
684 settings,
688 settings,
685 'rc_cache.cache_repo_longterm.backend',
689 'rc_cache.cache_repo_longterm.backend',
686 'dogpile.cache.rc.memory_lru', lower=False)
690 'dogpile.cache.rc.memory_lru', lower=False)
687 _int_setting(
691 _int_setting(
688 settings,
692 settings,
689 'rc_cache.cache_repo_longterm.expiration_time',
693 'rc_cache.cache_repo_longterm.expiration_time',
690 345600)
694 345600)
691 _int_setting(
695 _int_setting(
692 settings,
696 settings,
693 'rc_cache.cache_repo_longterm.max_size',
697 'rc_cache.cache_repo_longterm.max_size',
694 10000)
698 10000)
695
699
696 # sql_cache_short
700 # sql_cache_short
697 _string_setting(
701 _string_setting(
698 settings,
702 settings,
699 'rc_cache.sql_cache_short.backend',
703 'rc_cache.sql_cache_short.backend',
700 'dogpile.cache.rc.memory_lru', lower=False)
704 'dogpile.cache.rc.memory_lru', lower=False)
701 _int_setting(
705 _int_setting(
702 settings,
706 settings,
703 'rc_cache.sql_cache_short.expiration_time',
707 'rc_cache.sql_cache_short.expiration_time',
704 30)
708 30)
705 _int_setting(
709 _int_setting(
706 settings,
710 settings,
707 'rc_cache.sql_cache_short.max_size',
711 'rc_cache.sql_cache_short.max_size',
708 10000)
712 10000)
709
713
710
714
711 def _int_setting(settings, name, default):
715 def _int_setting(settings, name, default):
712 settings[name] = int(settings.get(name, default))
716 settings[name] = int(settings.get(name, default))
713 return settings[name]
717 return settings[name]
714
718
715
719
716 def _bool_setting(settings, name, default):
720 def _bool_setting(settings, name, default):
717 input_val = settings.get(name, default)
721 input_val = settings.get(name, default)
718 if isinstance(input_val, unicode):
722 if isinstance(input_val, unicode):
719 input_val = input_val.encode('utf8')
723 input_val = input_val.encode('utf8')
720 settings[name] = asbool(input_val)
724 settings[name] = asbool(input_val)
721 return settings[name]
725 return settings[name]
722
726
723
727
724 def _list_setting(settings, name, default):
728 def _list_setting(settings, name, default):
725 raw_value = settings.get(name, default)
729 raw_value = settings.get(name, default)
726
730
727 old_separator = ','
731 old_separator = ','
728 if old_separator in raw_value:
732 if old_separator in raw_value:
729 # If we get a comma separated list, pass it to our own function.
733 # If we get a comma separated list, pass it to our own function.
730 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
734 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
731 else:
735 else:
732 # Otherwise we assume it uses pyramids space/newline separation.
736 # Otherwise we assume it uses pyramids space/newline separation.
733 settings[name] = aslist(raw_value)
737 settings[name] = aslist(raw_value)
734 return settings[name]
738 return settings[name]
735
739
736
740
737 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
741 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
738 value = settings.get(name, default)
742 value = settings.get(name, default)
739
743
740 if default_when_empty and not value:
744 if default_when_empty and not value:
741 # use default value when value is empty
745 # use default value when value is empty
742 value = default
746 value = default
743
747
744 if lower:
748 if lower:
745 value = value.lower()
749 value = value.lower()
746 settings[name] = value
750 settings[name] = value
747 return settings[name]
751 return settings[name]
748
752
749
753
750 def _substitute_values(mapping, substitutions):
754 def _substitute_values(mapping, substitutions):
751 result = {}
755 result = {}
752
756
753 try:
757 try:
754 for key, value in mapping.items():
758 for key, value in mapping.items():
755 # initialize without substitution first
759 # initialize without substitution first
756 result[key] = value
760 result[key] = value
757
761
758 # Note: Cannot use regular replacements, since they would clash
762 # Note: Cannot use regular replacements, since they would clash
759 # with the implementation of ConfigParser. Using "format" instead.
763 # with the implementation of ConfigParser. Using "format" instead.
760 try:
764 try:
761 result[key] = value.format(**substitutions)
765 result[key] = value.format(**substitutions)
762 except KeyError as e:
766 except KeyError as e:
763 env_var = '{}'.format(e.args[0])
767 env_var = '{}'.format(e.args[0])
764
768
765 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
769 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
766 'Make sure your environment has {var} set, or remove this ' \
770 'Make sure your environment has {var} set, or remove this ' \
767 'variable from config file'.format(key=key, var=env_var)
771 'variable from config file'.format(key=key, var=env_var)
768
772
769 if env_var.startswith('ENV_'):
773 if env_var.startswith('ENV_'):
770 raise ValueError(msg)
774 raise ValueError(msg)
771 else:
775 else:
772 log.warning(msg)
776 log.warning(msg)
773
777
774 except ValueError as e:
778 except ValueError as e:
775 log.warning('Failed to substitute ENV variable: %s', e)
779 log.warning('Failed to substitute ENV variable: %s', e)
776 result = mapping
780 result = mapping
777
781
778 return result
782 return result
@@ -1,65 +1,73 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.lib.auth import AuthUser
25 from rhodecode.lib.auth import AuthUser
26 from rhodecode.lib.base import get_ip_addr, get_access_path, get_user_agent
26 from rhodecode.lib.base import get_ip_addr, get_access_path, get_user_agent
27 from rhodecode.lib.utils2 import safe_str, get_current_rhodecode_user
27 from rhodecode.lib.utils2 import safe_str, get_current_rhodecode_user
28
28
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 class RequestWrapperTween(object):
33 class RequestWrapperTween(object):
34 def __init__(self, handler, registry):
34 def __init__(self, handler, registry):
35 self.handler = handler
35 self.handler = handler
36 self.registry = registry
36 self.registry = registry
37
37
38 # one-time configuration code goes here
38 # one-time configuration code goes here
39
39
40 def _get_user_info(self, request):
41 user = get_current_rhodecode_user(request)
42 if not user:
43 user = AuthUser.repr_user(ip=get_ip_addr(request.environ))
44 return user
45
40 def __call__(self, request):
46 def __call__(self, request):
41 start = time.time()
47 start = time.time()
42 log.debug('Starting request time measurement')
48 log.debug('Starting request time measurement')
43 try:
49 try:
44 response = self.handler(request)
50 response = self.handler(request)
45 finally:
51 finally:
46 end = time.time()
47 total = end - start
48 count = request.request_count()
52 count = request.request_count()
49 _ver_ = rhodecode.__version__
53 _ver_ = rhodecode.__version__
50 default_user_info = AuthUser.repr_user(ip=get_ip_addr(request.environ))
54 statsd = request.statsd
51 user_info = get_current_rhodecode_user(request) or default_user_info
55 total = time.time() - start
56 if statsd:
57 statsd.timing('rhodecode.req.timing', total)
58 statsd.incr('rhodecode.req.count')
59
52 log.info(
60 log.info(
53 'Req[%4s] %s %s Request to %s time: %.4fs [%s], RhodeCode %s',
61 'Req[%4s] %s %s Request to %s time: %.4fs [%s], RhodeCode %s',
54 count, user_info, request.environ.get('REQUEST_METHOD'),
62 count, self._get_user_info(request), request.environ.get('REQUEST_METHOD'),
55 safe_str(get_access_path(request.environ)), total,
63 safe_str(get_access_path(request.environ)), total,
56 get_user_agent(request. environ), _ver_
64 get_user_agent(request. environ), _ver_
57 )
65 )
58
66
59 return response
67 return response
60
68
61
69
62 def includeme(config):
70 def includeme(config):
63 config.add_tween(
71 config.add_tween(
64 'rhodecode.lib.middleware.request_wrapper.RequestWrapperTween',
72 'rhodecode.lib.middleware.request_wrapper.RequestWrapperTween',
65 )
73 )
General Comments 0
You need to be logged in to leave comments. Login now