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