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