##// END OF EJS Templates
metrics: use promethues naming
super-admin -
r1012:095071a6 default
parent child Browse files
Show More
@@ -1,718 +1,718 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import os
19 19 import sys
20 20 import base64
21 21 import locale
22 22 import logging
23 23 import uuid
24 24 import wsgiref.util
25 25 import traceback
26 26 import tempfile
27 27 import psutil
28 28 from itertools import chain
29 29 from cStringIO import StringIO
30 30
31 31 import simplejson as json
32 32 import msgpack
33 33 from pyramid.config import Configurator
34 34 from pyramid.settings import asbool, aslist
35 35 from pyramid.wsgi import wsgiapp
36 36 from pyramid.compat import configparser
37 37 from pyramid.response import Response
38 38
39 39 from vcsserver.utils import safe_int
40 40 from vcsserver.lib.statsd_client import StatsdClient
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
45 45 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
46 46
47 47 try:
48 48 locale.setlocale(locale.LC_ALL, '')
49 49 except locale.Error as e:
50 50 log.error(
51 51 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
52 52 os.environ['LC_ALL'] = 'C'
53 53
54 54 import vcsserver
55 55 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
56 56 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
57 57 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
58 58 from vcsserver.echo_stub.echo_app import EchoApp
59 59 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
60 60 from vcsserver.lib.exc_tracking import store_exception
61 61 from vcsserver.server import VcsServer
62 62
63 63 try:
64 64 from vcsserver.git import GitFactory, GitRemote
65 65 except ImportError:
66 66 GitFactory = None
67 67 GitRemote = None
68 68
69 69 try:
70 70 from vcsserver.hg import MercurialFactory, HgRemote
71 71 except ImportError:
72 72 MercurialFactory = None
73 73 HgRemote = None
74 74
75 75 try:
76 76 from vcsserver.svn import SubversionFactory, SvnRemote
77 77 except ImportError:
78 78 SubversionFactory = None
79 79 SvnRemote = None
80 80
81 81
82 82 def _is_request_chunked(environ):
83 83 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
84 84 return stream
85 85
86 86
87 87 def _int_setting(settings, name, default):
88 88 settings[name] = int(settings.get(name, default))
89 89 return settings[name]
90 90
91 91
92 92 def _bool_setting(settings, name, default):
93 93 input_val = settings.get(name, default)
94 94 if isinstance(input_val, unicode):
95 95 input_val = input_val.encode('utf8')
96 96 settings[name] = asbool(input_val)
97 97 return settings[name]
98 98
99 99
100 100 def _list_setting(settings, name, default):
101 101 raw_value = settings.get(name, default)
102 102
103 103 # Otherwise we assume it uses pyramids space/newline separation.
104 104 settings[name] = aslist(raw_value)
105 105 return settings[name]
106 106
107 107
108 108 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
109 109 value = settings.get(name, default)
110 110
111 111 if default_when_empty and not value:
112 112 # use default value when value is empty
113 113 value = default
114 114
115 115 if lower:
116 116 value = value.lower()
117 117 settings[name] = value
118 118 return settings[name]
119 119
120 120
121 121 def log_max_fd():
122 122 try:
123 123 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
124 124 log.info('Max file descriptors value: %s', maxfd)
125 125 except Exception:
126 126 pass
127 127
128 128
129 129 class VCS(object):
130 130 def __init__(self, locale_conf=None, cache_config=None):
131 131 self.locale = locale_conf
132 132 self.cache_config = cache_config
133 133 self._configure_locale()
134 134
135 135 log_max_fd()
136 136
137 137 if GitFactory and GitRemote:
138 138 git_factory = GitFactory()
139 139 self._git_remote = GitRemote(git_factory)
140 140 else:
141 141 log.info("Git client import failed")
142 142
143 143 if MercurialFactory and HgRemote:
144 144 hg_factory = MercurialFactory()
145 145 self._hg_remote = HgRemote(hg_factory)
146 146 else:
147 147 log.info("Mercurial client import failed")
148 148
149 149 if SubversionFactory and SvnRemote:
150 150 svn_factory = SubversionFactory()
151 151
152 152 # hg factory is used for svn url validation
153 153 hg_factory = MercurialFactory()
154 154 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
155 155 else:
156 156 log.warning("Subversion client import failed")
157 157
158 158 self._vcsserver = VcsServer()
159 159
160 160 def _configure_locale(self):
161 161 if self.locale:
162 162 log.info('Settings locale: `LC_ALL` to %s', self.locale)
163 163 else:
164 164 log.info(
165 165 'Configuring locale subsystem based on environment variables')
166 166 try:
167 167 # If self.locale is the empty string, then the locale
168 168 # module will use the environment variables. See the
169 169 # documentation of the package `locale`.
170 170 locale.setlocale(locale.LC_ALL, self.locale)
171 171
172 172 language_code, encoding = locale.getlocale()
173 173 log.info(
174 174 'Locale set to language code "%s" with encoding "%s".',
175 175 language_code, encoding)
176 176 except locale.Error:
177 177 log.exception(
178 178 'Cannot set locale, not configuring the locale system')
179 179
180 180
181 181 class WsgiProxy(object):
182 182 def __init__(self, wsgi):
183 183 self.wsgi = wsgi
184 184
185 185 def __call__(self, environ, start_response):
186 186 input_data = environ['wsgi.input'].read()
187 187 input_data = msgpack.unpackb(input_data)
188 188
189 189 error = None
190 190 try:
191 191 data, status, headers = self.wsgi.handle(
192 192 input_data['environment'], input_data['input_data'],
193 193 *input_data['args'], **input_data['kwargs'])
194 194 except Exception as e:
195 195 data, status, headers = [], None, None
196 196 error = {
197 197 'message': str(e),
198 198 '_vcs_kind': getattr(e, '_vcs_kind', None)
199 199 }
200 200
201 201 start_response(200, {})
202 202 return self._iterator(error, status, headers, data)
203 203
204 204 def _iterator(self, error, status, headers, data):
205 205 initial_data = [
206 206 error,
207 207 status,
208 208 headers,
209 209 ]
210 210
211 211 for d in chain(initial_data, data):
212 212 yield msgpack.packb(d)
213 213
214 214
215 215 def not_found(request):
216 216 return {'status': '404 NOT FOUND'}
217 217
218 218
219 219 class VCSViewPredicate(object):
220 220 def __init__(self, val, config):
221 221 self.remotes = val
222 222
223 223 def text(self):
224 224 return 'vcs view method = %s' % (self.remotes.keys(),)
225 225
226 226 phash = text
227 227
228 228 def __call__(self, context, request):
229 229 """
230 230 View predicate that returns true if given backend is supported by
231 231 defined remotes.
232 232 """
233 233 backend = request.matchdict.get('backend')
234 234 return backend in self.remotes
235 235
236 236
237 237 class HTTPApplication(object):
238 238 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
239 239
240 240 remote_wsgi = remote_wsgi
241 241 _use_echo_app = False
242 242
243 243 def __init__(self, settings=None, global_config=None):
244 244 self._sanitize_settings_and_apply_defaults(settings)
245 245
246 246 self.config = Configurator(settings=settings)
247 247 # Init our statsd at very start
248 248 self.config.registry.statsd = StatsdClient.statsd
249 249
250 250 self.global_config = global_config
251 251 self.config.include('vcsserver.lib.rc_cache')
252 252
253 253 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
254 254 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
255 255 self._remotes = {
256 256 'hg': vcs._hg_remote,
257 257 'git': vcs._git_remote,
258 258 'svn': vcs._svn_remote,
259 259 'server': vcs._vcsserver,
260 260 }
261 261 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
262 262 self._use_echo_app = True
263 263 log.warning("Using EchoApp for VCS operations.")
264 264 self.remote_wsgi = remote_wsgi_stub
265 265
266 266 self._configure_settings(global_config, settings)
267 267
268 268 self._configure()
269 269
270 270 def _configure_settings(self, global_config, app_settings):
271 271 """
272 272 Configure the settings module.
273 273 """
274 274 settings_merged = global_config.copy()
275 275 settings_merged.update(app_settings)
276 276
277 277 git_path = app_settings.get('git_path', None)
278 278 if git_path:
279 279 settings.GIT_EXECUTABLE = git_path
280 280 binary_dir = app_settings.get('core.binary_dir', None)
281 281 if binary_dir:
282 282 settings.BINARY_DIR = binary_dir
283 283
284 284 # Store the settings to make them available to other modules.
285 285 vcsserver.PYRAMID_SETTINGS = settings_merged
286 286 vcsserver.CONFIG = settings_merged
287 287
288 288 def _sanitize_settings_and_apply_defaults(self, settings):
289 289 temp_store = tempfile.gettempdir()
290 290 default_cache_dir = os.path.join(temp_store, 'rc_cache')
291 291
292 292 # save default, cache dir, and use it for all backends later.
293 293 default_cache_dir = _string_setting(
294 294 settings,
295 295 'cache_dir',
296 296 default_cache_dir, lower=False, default_when_empty=True)
297 297
298 298 # ensure we have our dir created
299 299 if not os.path.isdir(default_cache_dir):
300 300 os.makedirs(default_cache_dir, mode=0o755)
301 301
302 302 # exception store cache
303 303 _string_setting(
304 304 settings,
305 305 'exception_tracker.store_path',
306 306 temp_store, lower=False, default_when_empty=True)
307 307
308 308 # repo_object cache
309 309 _string_setting(
310 310 settings,
311 311 'rc_cache.repo_object.backend',
312 312 'dogpile.cache.rc.file_namespace', lower=False)
313 313 _int_setting(
314 314 settings,
315 315 'rc_cache.repo_object.expiration_time',
316 316 30 * 24 * 60 * 60)
317 317 _string_setting(
318 318 settings,
319 319 'rc_cache.repo_object.arguments.filename',
320 320 os.path.join(default_cache_dir, 'vcsserver_cache_1'), lower=False)
321 321
322 322 def _configure(self):
323 323 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
324 324
325 325 self.config.add_route('service', '/_service')
326 326 self.config.add_route('status', '/status')
327 327 self.config.add_route('hg_proxy', '/proxy/hg')
328 328 self.config.add_route('git_proxy', '/proxy/git')
329 329
330 330 # rpc methods
331 331 self.config.add_route('vcs', '/{backend}')
332 332
333 333 # streaming rpc remote methods
334 334 self.config.add_route('vcs_stream', '/{backend}/stream')
335 335
336 336 # vcs operations clone/push as streaming
337 337 self.config.add_route('stream_git', '/stream/git/*repo_name')
338 338 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
339 339
340 340 self.config.add_view(self.status_view, route_name='status', renderer='json')
341 341 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
342 342
343 343 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
344 344 self.config.add_view(self.git_proxy(), route_name='git_proxy')
345 345 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
346 346 vcs_view=self._remotes)
347 347 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
348 348 vcs_view=self._remotes)
349 349
350 350 self.config.add_view(self.hg_stream(), route_name='stream_hg')
351 351 self.config.add_view(self.git_stream(), route_name='stream_git')
352 352
353 353 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
354 354
355 355 self.config.add_notfound_view(not_found, renderer='json')
356 356
357 357 self.config.add_view(self.handle_vcs_exception, context=Exception)
358 358
359 359 self.config.add_tween(
360 360 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
361 361 )
362 362 self.config.add_request_method(
363 363 'vcsserver.lib.request_counter.get_request_counter',
364 364 'request_count')
365 365
366 366 def wsgi_app(self):
367 367 return self.config.make_wsgi_app()
368 368
369 369 def _vcs_view_params(self, request):
370 370 remote = self._remotes[request.matchdict['backend']]
371 371 payload = msgpack.unpackb(request.body, use_list=True)
372 372 method = payload.get('method')
373 373 params = payload['params']
374 374 wire = params.get('wire')
375 375 args = params.get('args')
376 376 kwargs = params.get('kwargs')
377 377 context_uid = None
378 378
379 379 if wire:
380 380 try:
381 381 wire['context'] = context_uid = uuid.UUID(wire['context'])
382 382 except KeyError:
383 383 pass
384 384 args.insert(0, wire)
385 385 repo_state_uid = wire.get('repo_state_uid') if wire else None
386 386
387 387 # NOTE(marcink): trading complexity for slight performance
388 388 if log.isEnabledFor(logging.DEBUG):
389 389 no_args_methods = [
390 390
391 391 ]
392 392 if method in no_args_methods:
393 393 call_args = ''
394 394 else:
395 395 call_args = args[1:]
396 396
397 397 log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
398 398 method, call_args, kwargs, context_uid, repo_state_uid)
399 399
400 400 statsd = request.registry.statsd
401 401 if statsd:
402 402 statsd.incr(
403 'vcsserver_method_count', tags=[
403 'vcsserver_method_total', tags=[
404 404 "method:{}".format(method),
405 405 ])
406 406 return payload, remote, method, args, kwargs
407 407
408 408 def vcs_view(self, request):
409 409
410 410 payload, remote, method, args, kwargs = self._vcs_view_params(request)
411 411 payload_id = payload.get('id')
412 412
413 413 try:
414 414 resp = getattr(remote, method)(*args, **kwargs)
415 415 except Exception as e:
416 416 exc_info = list(sys.exc_info())
417 417 exc_type, exc_value, exc_traceback = exc_info
418 418
419 419 org_exc = getattr(e, '_org_exc', None)
420 420 org_exc_name = None
421 421 org_exc_tb = ''
422 422 if org_exc:
423 423 org_exc_name = org_exc.__class__.__name__
424 424 org_exc_tb = getattr(e, '_org_exc_tb', '')
425 425 # replace our "faked" exception with our org
426 426 exc_info[0] = org_exc.__class__
427 427 exc_info[1] = org_exc
428 428
429 429 should_store_exc = True
430 430 if org_exc:
431 431 def get_exc_fqn(_exc_obj):
432 432 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
433 433 return module_name + '.' + org_exc_name
434 434
435 435 exc_fqn = get_exc_fqn(org_exc)
436 436
437 437 if exc_fqn in ['mercurial.error.RepoLookupError',
438 438 'vcsserver.exceptions.RefNotFoundException']:
439 439 should_store_exc = False
440 440
441 441 if should_store_exc:
442 442 store_exception(id(exc_info), exc_info, request_path=request.path)
443 443
444 444 tb_info = ''.join(
445 445 traceback.format_exception(exc_type, exc_value, exc_traceback))
446 446
447 447 type_ = e.__class__.__name__
448 448 if type_ not in self.ALLOWED_EXCEPTIONS:
449 449 type_ = None
450 450
451 451 resp = {
452 452 'id': payload_id,
453 453 'error': {
454 454 'message': e.message,
455 455 'traceback': tb_info,
456 456 'org_exc': org_exc_name,
457 457 'org_exc_tb': org_exc_tb,
458 458 'type': type_
459 459 }
460 460 }
461 461
462 462 try:
463 463 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
464 464 except AttributeError:
465 465 pass
466 466 else:
467 467 resp = {
468 468 'id': payload_id,
469 469 'result': resp
470 470 }
471 471
472 472 return resp
473 473
474 474 def vcs_stream_view(self, request):
475 475 payload, remote, method, args, kwargs = self._vcs_view_params(request)
476 476 # this method has a stream: marker we remove it here
477 477 method = method.split('stream:')[-1]
478 478 chunk_size = safe_int(payload.get('chunk_size')) or 4096
479 479
480 480 try:
481 481 resp = getattr(remote, method)(*args, **kwargs)
482 482 except Exception as e:
483 483 raise
484 484
485 485 def get_chunked_data(method_resp):
486 486 stream = StringIO(method_resp)
487 487 while 1:
488 488 chunk = stream.read(chunk_size)
489 489 if not chunk:
490 490 break
491 491 yield chunk
492 492
493 493 response = Response(app_iter=get_chunked_data(resp))
494 494 response.content_type = 'application/octet-stream'
495 495
496 496 return response
497 497
498 498 def status_view(self, request):
499 499 import vcsserver
500 500 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
501 501 'pid': os.getpid()}
502 502
503 503 def service_view(self, request):
504 504 import vcsserver
505 505
506 506 payload = msgpack.unpackb(request.body, use_list=True)
507 507 server_config, app_config = {}, {}
508 508
509 509 try:
510 510 path = self.global_config['__file__']
511 511 config = configparser.RawConfigParser()
512 512
513 513 config.read(path)
514 514
515 515 if config.has_section('server:main'):
516 516 server_config = dict(config.items('server:main'))
517 517 if config.has_section('app:main'):
518 518 app_config = dict(config.items('app:main'))
519 519
520 520 except Exception:
521 521 log.exception('Failed to read .ini file for display')
522 522
523 523 environ = os.environ.items()
524 524
525 525 resp = {
526 526 'id': payload.get('id'),
527 527 'result': dict(
528 528 version=vcsserver.__version__,
529 529 config=server_config,
530 530 app_config=app_config,
531 531 environ=environ,
532 532 payload=payload,
533 533 )
534 534 }
535 535 return resp
536 536
537 537 def _msgpack_renderer_factory(self, info):
538 538 def _render(value, system):
539 539 request = system.get('request')
540 540 if request is not None:
541 541 response = request.response
542 542 ct = response.content_type
543 543 if ct == response.default_content_type:
544 544 response.content_type = 'application/x-msgpack'
545 545 return msgpack.packb(value)
546 546 return _render
547 547
548 548 def set_env_from_config(self, environ, config):
549 549 dict_conf = {}
550 550 try:
551 551 for elem in config:
552 552 if elem[0] == 'rhodecode':
553 553 dict_conf = json.loads(elem[2])
554 554 break
555 555 except Exception:
556 556 log.exception('Failed to fetch SCM CONFIG')
557 557 return
558 558
559 559 username = dict_conf.get('username')
560 560 if username:
561 561 environ['REMOTE_USER'] = username
562 562 # mercurial specific, some extension api rely on this
563 563 environ['HGUSER'] = username
564 564
565 565 ip = dict_conf.get('ip')
566 566 if ip:
567 567 environ['REMOTE_HOST'] = ip
568 568
569 569 if _is_request_chunked(environ):
570 570 # set the compatibility flag for webob
571 571 environ['wsgi.input_terminated'] = True
572 572
573 573 def hg_proxy(self):
574 574 @wsgiapp
575 575 def _hg_proxy(environ, start_response):
576 576 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
577 577 return app(environ, start_response)
578 578 return _hg_proxy
579 579
580 580 def git_proxy(self):
581 581 @wsgiapp
582 582 def _git_proxy(environ, start_response):
583 583 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
584 584 return app(environ, start_response)
585 585 return _git_proxy
586 586
587 587 def hg_stream(self):
588 588 if self._use_echo_app:
589 589 @wsgiapp
590 590 def _hg_stream(environ, start_response):
591 591 app = EchoApp('fake_path', 'fake_name', None)
592 592 return app(environ, start_response)
593 593 return _hg_stream
594 594 else:
595 595 @wsgiapp
596 596 def _hg_stream(environ, start_response):
597 597 log.debug('http-app: handling hg stream')
598 598 repo_path = environ['HTTP_X_RC_REPO_PATH']
599 599 repo_name = environ['HTTP_X_RC_REPO_NAME']
600 600 packed_config = base64.b64decode(
601 601 environ['HTTP_X_RC_REPO_CONFIG'])
602 602 config = msgpack.unpackb(packed_config)
603 603 app = scm_app.create_hg_wsgi_app(
604 604 repo_path, repo_name, config)
605 605
606 606 # Consistent path information for hgweb
607 607 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
608 608 environ['REPO_NAME'] = repo_name
609 609 self.set_env_from_config(environ, config)
610 610
611 611 log.debug('http-app: starting app handler '
612 612 'with %s and process request', app)
613 613 return app(environ, ResponseFilter(start_response))
614 614 return _hg_stream
615 615
616 616 def git_stream(self):
617 617 if self._use_echo_app:
618 618 @wsgiapp
619 619 def _git_stream(environ, start_response):
620 620 app = EchoApp('fake_path', 'fake_name', None)
621 621 return app(environ, start_response)
622 622 return _git_stream
623 623 else:
624 624 @wsgiapp
625 625 def _git_stream(environ, start_response):
626 626 log.debug('http-app: handling git stream')
627 627 repo_path = environ['HTTP_X_RC_REPO_PATH']
628 628 repo_name = environ['HTTP_X_RC_REPO_NAME']
629 629 packed_config = base64.b64decode(
630 630 environ['HTTP_X_RC_REPO_CONFIG'])
631 631 config = msgpack.unpackb(packed_config)
632 632
633 633 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
634 634 self.set_env_from_config(environ, config)
635 635
636 636 content_type = environ.get('CONTENT_TYPE', '')
637 637
638 638 path = environ['PATH_INFO']
639 639 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
640 640 log.debug(
641 641 'LFS: Detecting if request `%s` is LFS server path based '
642 642 'on content type:`%s`, is_lfs:%s',
643 643 path, content_type, is_lfs_request)
644 644
645 645 if not is_lfs_request:
646 646 # fallback detection by path
647 647 if GIT_LFS_PROTO_PAT.match(path):
648 648 is_lfs_request = True
649 649 log.debug(
650 650 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
651 651 path, is_lfs_request)
652 652
653 653 if is_lfs_request:
654 654 app = scm_app.create_git_lfs_wsgi_app(
655 655 repo_path, repo_name, config)
656 656 else:
657 657 app = scm_app.create_git_wsgi_app(
658 658 repo_path, repo_name, config)
659 659
660 660 log.debug('http-app: starting app handler '
661 661 'with %s and process request', app)
662 662
663 663 return app(environ, start_response)
664 664
665 665 return _git_stream
666 666
667 667 def handle_vcs_exception(self, exception, request):
668 668 _vcs_kind = getattr(exception, '_vcs_kind', '')
669 669 if _vcs_kind == 'repo_locked':
670 670 # Get custom repo-locked status code if present.
671 671 status_code = request.headers.get('X-RC-Locked-Status-Code')
672 672 return HTTPRepoLocked(
673 673 title=exception.message, status_code=status_code)
674 674
675 675 elif _vcs_kind == 'repo_branch_protected':
676 676 # Get custom repo-branch-protected status code if present.
677 677 return HTTPRepoBranchProtected(title=exception.message)
678 678
679 679 exc_info = request.exc_info
680 680 store_exception(id(exc_info), exc_info)
681 681
682 682 traceback_info = 'unavailable'
683 683 if request.exc_info:
684 684 exc_type, exc_value, exc_tb = request.exc_info
685 685 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
686 686
687 687 log.error(
688 688 'error occurred handling this request for path: %s, \n tb: %s',
689 689 request.path, traceback_info)
690 690
691 691 statsd = request.registry.statsd
692 692 if statsd:
693 statsd.incr('vcsserver_exception')
693 statsd.incr('vcsserver_exception_total')
694 694 raise exception
695 695
696 696
697 697 class ResponseFilter(object):
698 698
699 699 def __init__(self, start_response):
700 700 self._start_response = start_response
701 701
702 702 def __call__(self, status, response_headers, exc_info=None):
703 703 headers = tuple(
704 704 (h, v) for h, v in response_headers
705 705 if not wsgiref.util.is_hop_by_hop(h))
706 706 return self._start_response(status, headers, exc_info)
707 707
708 708
709 709 def main(global_config, **settings):
710 710 if MercurialFactory:
711 711 hgpatches.patch_largefiles_capabilities()
712 712 hgpatches.patch_subrepo_type_mapping()
713 713
714 714 # init and bootstrap StatsdClient
715 715 StatsdClient.setup(settings)
716 716
717 717 app = HTTPApplication(settings=settings, global_config=global_config)
718 718 return app.wsgi_app()
@@ -1,80 +1,76 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import time
19 19 import logging
20 20
21 21 import vcsserver
22 22 from vcsserver.utils import safe_str
23 23
24 24
25 25 log = logging.getLogger(__name__)
26 26
27 27
28 28 def get_access_path(environ):
29 29 path = environ.get('PATH_INFO')
30 30 return path
31 31
32 32
33 33 def get_user_agent(environ):
34 34 return environ.get('HTTP_USER_AGENT')
35 35
36 36
37 37 class RequestWrapperTween(object):
38 38 def __init__(self, handler, registry):
39 39 self.handler = handler
40 40 self.registry = registry
41 41
42 42 # one-time configuration code goes here
43 43
44 44 def __call__(self, request):
45 45 start = time.time()
46 46 log.debug('Starting request time measurement')
47 47 try:
48 48 response = self.handler(request)
49 49 finally:
50 50 count = request.request_count()
51 51 _ver_ = vcsserver.__version__
52 52 _path = safe_str(get_access_path(request.environ))
53 53
54 54 total = time.time() - start
55 55 log.info(
56 56 'Req[%4s] IP: %s %s Request to %s time: %.4fs [%s], VCSServer %s',
57 57 count, '127.0.0.1', request.environ.get('REQUEST_METHOD'),
58 58 _path, total, get_user_agent(request.environ), _ver_
59 59 )
60 60
61 61 statsd = request.registry.statsd
62 62 if statsd:
63 63 elapsed_time_ms = 1000.0 * total
64 64 statsd.timing(
65 65 'vcsserver_req_timing', elapsed_time_ms,
66 tags=[
67 "path:{}".format(_path),
68 ]
66 tags=["path:{}".format(_path)]
69 67 )
70 68 statsd.incr(
71 'vcsserver_req_count', tags=[
72 "path:{}".format(_path),
73 ])
69 'vcsserver_req_total', tags=["path:{}".format(_path)])
74 70 return response
75 71
76 72
77 73 def includeme(config):
78 74 config.add_tween(
79 75 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
80 76 )
General Comments 0
You need to be logged in to leave comments. Login now