##// END OF EJS Templates
exceptions: allow error tracking and exception storage on vcsserver.
marcink -
r491:5e446817 default
parent child Browse files
Show More
@@ -0,0 +1,146 b''
1 # -*- coding: utf-8 -*-
2
3 # RhodeCode VCSServer provides access to different vcs backends via network.
4 # Copyright (C) 2014-2018 RhodeCode GmbH
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software Foundation,
18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
20
21 import os
22 import time
23 import datetime
24 import msgpack
25 import logging
26 import traceback
27 import tempfile
28
29
30 log = logging.getLogger(__name__)
31
32 # NOTE: Any changes should be synced with exc_tracking at rhodecode.lib.exc_tracking
33 global_prefix = 'vcsserver'
34
35
36 def exc_serialize(exc_id, tb, exc_type):
37
38 data = {
39 'version': 'v1',
40 'exc_id': exc_id,
41 'exc_utc_date': datetime.datetime.utcnow().isoformat(),
42 'exc_timestamp': repr(time.time()),
43 'exc_message': tb,
44 'exc_type': exc_type,
45 }
46 return msgpack.packb(data), data
47
48
49 def exc_unserialize(tb):
50 return msgpack.unpackb(tb)
51
52
53 def get_exc_store():
54 """
55 Get and create exception store if it's not existing
56 """
57 exc_store_dir = 'rc_exception_store_v1'
58 # fallback
59 _exc_store_path = os.path.join(tempfile.gettempdir(), exc_store_dir)
60
61 exc_store_dir = '' # TODO: need a persistent cross instance store here
62 if exc_store_dir:
63 _exc_store_path = os.path.join(exc_store_dir, exc_store_dir)
64
65 _exc_store_path = os.path.abspath(_exc_store_path)
66 if not os.path.isdir(_exc_store_path):
67 os.makedirs(_exc_store_path)
68 log.debug('Initializing exceptions store at %s', _exc_store_path)
69 return _exc_store_path
70
71
72 def _store_exception(exc_id, exc_info, prefix):
73 exc_type, exc_value, exc_traceback = exc_info
74 tb = ''.join(traceback.format_exception(
75 exc_type, exc_value, exc_traceback, None))
76
77 exc_type_name = exc_type.__name__
78 exc_store_path = get_exc_store()
79 exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name)
80 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
81 if not os.path.isdir(exc_store_path):
82 os.makedirs(exc_store_path)
83 stored_exc_path = os.path.join(exc_store_path, exc_pref_id)
84 with open(stored_exc_path, 'wb') as f:
85 f.write(exc_data)
86 log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
87
88
89 def store_exception(exc_id, exc_info, prefix=global_prefix):
90 try:
91 _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix)
92 except Exception:
93 log.exception('Failed to store exception `%s` information', exc_id)
94 # there's no way this can fail, it will crash server badly if it does.
95 pass
96
97
98 def _find_exc_file(exc_id, prefix=global_prefix):
99 exc_store_path = get_exc_store()
100 if prefix:
101 exc_id = '{}_{}'.format(exc_id, prefix)
102 else:
103 # search without a prefix
104 exc_id = '{}'.format(exc_id)
105
106 # we need to search the store for such start pattern as above
107 for fname in os.listdir(exc_store_path):
108 if fname.startswith(exc_id):
109 exc_id = os.path.join(exc_store_path, fname)
110 break
111 continue
112 else:
113 exc_id = None
114
115 return exc_id
116
117
118 def _read_exception(exc_id, prefix):
119 exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix)
120 if exc_id_file_path:
121 with open(exc_id_file_path, 'rb') as f:
122 return exc_unserialize(f.read())
123 else:
124 log.debug('Exception File `%s` not found', exc_id_file_path)
125 return None
126
127
128 def read_exception(exc_id, prefix=global_prefix):
129 try:
130 return _read_exception(exc_id=exc_id, prefix=prefix)
131 except Exception:
132 log.exception('Failed to read exception `%s` information', exc_id)
133 # there's no way this can fail, it will crash server badly if it does.
134 return None
135
136
137 def delete_exception(exc_id, prefix=global_prefix):
138 try:
139 exc_id_file_path = _find_exc_file(exc_id, prefix=prefix)
140 if exc_id_file_path:
141 os.remove(exc_id_file_path)
142
143 except Exception:
144 log.exception('Failed to remove exception `%s` information', exc_id)
145 # there's no way this can fail, it will crash server badly if it does.
146 pass
@@ -1,523 +1,545 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2018 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 import sys
19 20 import base64
20 21 import locale
21 22 import logging
22 23 import uuid
23 24 import wsgiref.util
24 25 import traceback
25 26 from itertools import chain
26 27
27 28 import simplejson as json
28 29 import msgpack
29 30 from pyramid.config import Configurator
30 31 from pyramid.settings import asbool, aslist
31 32 from pyramid.wsgi import wsgiapp
32 33 from pyramid.compat import configparser
33 34
34 35 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
35 36 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
36 37 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
37 38 from vcsserver.echo_stub.echo_app import EchoApp
38 39 from vcsserver.exceptions import HTTPRepoLocked
40 from vcsserver.lib.exc_tracking import store_exception
39 41 from vcsserver.server import VcsServer
40 42
41 43 try:
42 44 from vcsserver.git import GitFactory, GitRemote
43 45 except ImportError:
44 46 GitFactory = None
45 47 GitRemote = None
46 48
47 49 try:
48 50 from vcsserver.hg import MercurialFactory, HgRemote
49 51 except ImportError:
50 52 MercurialFactory = None
51 53 HgRemote = None
52 54
53 55 try:
54 56 from vcsserver.svn import SubversionFactory, SvnRemote
55 57 except ImportError:
56 58 SubversionFactory = None
57 59 SvnRemote = None
58 60
59 61 log = logging.getLogger(__name__)
60 62
61 63
62 64 def _is_request_chunked(environ):
63 65 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
64 66 return stream
65 67
66 68
67 69 def _int_setting(settings, name, default):
68 70 settings[name] = int(settings.get(name, default))
69 71
70 72
71 73 def _bool_setting(settings, name, default):
72 74 input_val = settings.get(name, default)
73 75 if isinstance(input_val, unicode):
74 76 input_val = input_val.encode('utf8')
75 77 settings[name] = asbool(input_val)
76 78
77 79
78 80 def _list_setting(settings, name, default):
79 81 raw_value = settings.get(name, default)
80 82
81 83 # Otherwise we assume it uses pyramids space/newline separation.
82 84 settings[name] = aslist(raw_value)
83 85
84 86
85 87 def _string_setting(settings, name, default, lower=True):
86 88 value = settings.get(name, default)
87 89 if lower:
88 90 value = value.lower()
89 91 settings[name] = value
90 92
91 93
92 94 class VCS(object):
93 95 def __init__(self, locale=None, cache_config=None):
94 96 self.locale = locale
95 97 self.cache_config = cache_config
96 98 self._configure_locale()
97 99
98 100 if GitFactory and GitRemote:
99 101 git_factory = GitFactory()
100 102 self._git_remote = GitRemote(git_factory)
101 103 else:
102 104 log.info("Git client import failed")
103 105
104 106 if MercurialFactory and HgRemote:
105 107 hg_factory = MercurialFactory()
106 108 self._hg_remote = HgRemote(hg_factory)
107 109 else:
108 110 log.info("Mercurial client import failed")
109 111
110 112 if SubversionFactory and SvnRemote:
111 113 svn_factory = SubversionFactory()
112 114
113 115 # hg factory is used for svn url validation
114 116 hg_factory = MercurialFactory()
115 117 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
116 118 else:
117 119 log.info("Subversion client import failed")
118 120
119 121 self._vcsserver = VcsServer()
120 122
121 123 def _configure_locale(self):
122 124 if self.locale:
123 125 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
124 126 else:
125 127 log.info(
126 128 'Configuring locale subsystem based on environment variables')
127 129 try:
128 130 # If self.locale is the empty string, then the locale
129 131 # module will use the environment variables. See the
130 132 # documentation of the package `locale`.
131 133 locale.setlocale(locale.LC_ALL, self.locale)
132 134
133 135 language_code, encoding = locale.getlocale()
134 136 log.info(
135 137 'Locale set to language code "%s" with encoding "%s".',
136 138 language_code, encoding)
137 139 except locale.Error:
138 140 log.exception(
139 141 'Cannot set locale, not configuring the locale system')
140 142
141 143
142 144 class WsgiProxy(object):
143 145 def __init__(self, wsgi):
144 146 self.wsgi = wsgi
145 147
146 148 def __call__(self, environ, start_response):
147 149 input_data = environ['wsgi.input'].read()
148 150 input_data = msgpack.unpackb(input_data)
149 151
150 152 error = None
151 153 try:
152 154 data, status, headers = self.wsgi.handle(
153 155 input_data['environment'], input_data['input_data'],
154 156 *input_data['args'], **input_data['kwargs'])
155 157 except Exception as e:
156 158 data, status, headers = [], None, None
157 159 error = {
158 160 'message': str(e),
159 161 '_vcs_kind': getattr(e, '_vcs_kind', None)
160 162 }
161 163
162 164 start_response(200, {})
163 165 return self._iterator(error, status, headers, data)
164 166
165 167 def _iterator(self, error, status, headers, data):
166 168 initial_data = [
167 169 error,
168 170 status,
169 171 headers,
170 172 ]
171 173
172 174 for d in chain(initial_data, data):
173 175 yield msgpack.packb(d)
174 176
175 177
176 178 class HTTPApplication(object):
177 179 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
178 180
179 181 remote_wsgi = remote_wsgi
180 182 _use_echo_app = False
181 183
182 184 def __init__(self, settings=None, global_config=None):
183 185 self._sanitize_settings_and_apply_defaults(settings)
184 186
185 187 self.config = Configurator(settings=settings)
186 188 self.global_config = global_config
187 189 self.config.include('vcsserver.lib.rc_cache')
188 190
189 191 locale = settings.get('locale', '') or 'en_US.UTF-8'
190 192 vcs = VCS(locale=locale, cache_config=settings)
191 193 self._remotes = {
192 194 'hg': vcs._hg_remote,
193 195 'git': vcs._git_remote,
194 196 'svn': vcs._svn_remote,
195 197 'server': vcs._vcsserver,
196 198 }
197 199 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
198 200 self._use_echo_app = True
199 201 log.warning("Using EchoApp for VCS operations.")
200 202 self.remote_wsgi = remote_wsgi_stub
201 203 self._configure_settings(settings)
202 204 self._configure()
203 205
204 206 def _configure_settings(self, app_settings):
205 207 """
206 208 Configure the settings module.
207 209 """
208 210 git_path = app_settings.get('git_path', None)
209 211 if git_path:
210 212 settings.GIT_EXECUTABLE = git_path
211 213 binary_dir = app_settings.get('core.binary_dir', None)
212 214 if binary_dir:
213 215 settings.BINARY_DIR = binary_dir
214 216
215 217 def _sanitize_settings_and_apply_defaults(self, settings):
216 218 # repo_object cache
217 219 _string_setting(
218 220 settings,
219 221 'rc_cache.repo_object.backend',
220 222 'dogpile.cache.rc.memory_lru')
221 223 _int_setting(
222 224 settings,
223 225 'rc_cache.repo_object.expiration_time',
224 226 300)
225 227 _int_setting(
226 228 settings,
227 229 'rc_cache.repo_object.max_size',
228 230 1024)
229 231
230 232 def _configure(self):
231 233 self.config.add_renderer(
232 234 name='msgpack',
233 235 factory=self._msgpack_renderer_factory)
234 236
235 237 self.config.add_route('service', '/_service')
236 238 self.config.add_route('status', '/status')
237 239 self.config.add_route('hg_proxy', '/proxy/hg')
238 240 self.config.add_route('git_proxy', '/proxy/git')
239 241 self.config.add_route('vcs', '/{backend}')
240 242 self.config.add_route('stream_git', '/stream/git/*repo_name')
241 243 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
242 244
243 245 self.config.add_view(
244 246 self.status_view, route_name='status', renderer='json')
245 247 self.config.add_view(
246 248 self.service_view, route_name='service', renderer='msgpack')
247 249
248 250 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
249 251 self.config.add_view(self.git_proxy(), route_name='git_proxy')
250 252 self.config.add_view(
251 253 self.vcs_view, route_name='vcs', renderer='msgpack',
252 254 custom_predicates=[self.is_vcs_view])
253 255
254 256 self.config.add_view(self.hg_stream(), route_name='stream_hg')
255 257 self.config.add_view(self.git_stream(), route_name='stream_git')
256 258
257 259 def notfound(request):
258 260 return {'status': '404 NOT FOUND'}
259 261 self.config.add_notfound_view(notfound, renderer='json')
260 262
261 263 self.config.add_view(self.handle_vcs_exception, context=Exception)
262 264
263 265 self.config.add_tween(
264 266 'vcsserver.tweens.RequestWrapperTween',
265 267 )
266 268
267 269 def wsgi_app(self):
268 270 return self.config.make_wsgi_app()
269 271
270 272 def vcs_view(self, request):
271 273 remote = self._remotes[request.matchdict['backend']]
272 274 payload = msgpack.unpackb(request.body, use_list=True)
273 275 method = payload.get('method')
274 276 params = payload.get('params')
275 277 wire = params.get('wire')
276 278 args = params.get('args')
277 279 kwargs = params.get('kwargs')
278 280 context_uid = None
279 281
280 282 if wire:
281 283 try:
282 284 wire['context'] = context_uid = uuid.UUID(wire['context'])
283 285 except KeyError:
284 286 pass
285 287 args.insert(0, wire)
286 288
287 289 log.debug('method called:%s with kwargs:%s context_uid: %s',
288 290 method, kwargs, context_uid)
289 291 try:
290 292 resp = getattr(remote, method)(*args, **kwargs)
291 293 except Exception as e:
292 tb_info = traceback.format_exc()
294 exc_info = list(sys.exc_info())
295 exc_type, exc_value, exc_traceback = exc_info
296
297 org_exc = getattr(e, '_org_exc', None)
298 org_exc_name = None
299 if org_exc:
300 org_exc_name = org_exc.__class__.__name__
301 # replace our "faked" exception with our org
302 exc_info[0] = org_exc.__class__
303 exc_info[1] = org_exc
304
305 store_exception(id(exc_info), exc_info)
306
307 tb_info = ''.join(
308 traceback.format_exception(exc_type, exc_value, exc_traceback))
293 309
294 310 type_ = e.__class__.__name__
295 311 if type_ not in self.ALLOWED_EXCEPTIONS:
296 312 type_ = None
297 313
298 314 resp = {
299 315 'id': payload.get('id'),
300 316 'error': {
301 317 'message': e.message,
302 318 'traceback': tb_info,
319 'org_exc': org_exc_name,
303 320 'type': type_
304 321 }
305 322 }
306 323 try:
307 324 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
308 325 except AttributeError:
309 326 pass
310 327 else:
311 328 resp = {
312 329 'id': payload.get('id'),
313 330 'result': resp
314 331 }
315 332
316 333 return resp
317 334
318 335 def status_view(self, request):
319 336 import vcsserver
320 337 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
321 338 'pid': os.getpid()}
322 339
323 340 def service_view(self, request):
324 341 import vcsserver
325 342
326 343 payload = msgpack.unpackb(request.body, use_list=True)
327 344
328 345 try:
329 346 path = self.global_config['__file__']
330 347 config = configparser.ConfigParser()
331 348 config.read(path)
332 349 parsed_ini = config
333 350 if parsed_ini.has_section('server:main'):
334 351 parsed_ini = dict(parsed_ini.items('server:main'))
335 352 except Exception:
336 353 log.exception('Failed to read .ini file for display')
337 354 parsed_ini = {}
338 355
339 356 resp = {
340 357 'id': payload.get('id'),
341 358 'result': dict(
342 359 version=vcsserver.__version__,
343 360 config=parsed_ini,
344 361 payload=payload,
345 362 )
346 363 }
347 364 return resp
348 365
349 366 def _msgpack_renderer_factory(self, info):
350 367 def _render(value, system):
351 368 value = msgpack.packb(value)
352 369 request = system.get('request')
353 370 if request is not None:
354 371 response = request.response
355 372 ct = response.content_type
356 373 if ct == response.default_content_type:
357 374 response.content_type = 'application/x-msgpack'
358 375 return value
359 376 return _render
360 377
361 378 def set_env_from_config(self, environ, config):
362 379 dict_conf = {}
363 380 try:
364 381 for elem in config:
365 382 if elem[0] == 'rhodecode':
366 383 dict_conf = json.loads(elem[2])
367 384 break
368 385 except Exception:
369 386 log.exception('Failed to fetch SCM CONFIG')
370 387 return
371 388
372 389 username = dict_conf.get('username')
373 390 if username:
374 391 environ['REMOTE_USER'] = username
375 392 # mercurial specific, some extension api rely on this
376 393 environ['HGUSER'] = username
377 394
378 395 ip = dict_conf.get('ip')
379 396 if ip:
380 397 environ['REMOTE_HOST'] = ip
381 398
382 399 if _is_request_chunked(environ):
383 400 # set the compatibility flag for webob
384 401 environ['wsgi.input_terminated'] = True
385 402
386 403 def hg_proxy(self):
387 404 @wsgiapp
388 405 def _hg_proxy(environ, start_response):
389 406 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
390 407 return app(environ, start_response)
391 408 return _hg_proxy
392 409
393 410 def git_proxy(self):
394 411 @wsgiapp
395 412 def _git_proxy(environ, start_response):
396 413 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
397 414 return app(environ, start_response)
398 415 return _git_proxy
399 416
400 417 def hg_stream(self):
401 418 if self._use_echo_app:
402 419 @wsgiapp
403 420 def _hg_stream(environ, start_response):
404 421 app = EchoApp('fake_path', 'fake_name', None)
405 422 return app(environ, start_response)
406 423 return _hg_stream
407 424 else:
408 425 @wsgiapp
409 426 def _hg_stream(environ, start_response):
410 427 log.debug('http-app: handling hg stream')
411 428 repo_path = environ['HTTP_X_RC_REPO_PATH']
412 429 repo_name = environ['HTTP_X_RC_REPO_NAME']
413 430 packed_config = base64.b64decode(
414 431 environ['HTTP_X_RC_REPO_CONFIG'])
415 432 config = msgpack.unpackb(packed_config)
416 433 app = scm_app.create_hg_wsgi_app(
417 434 repo_path, repo_name, config)
418 435
419 436 # Consistent path information for hgweb
420 437 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
421 438 environ['REPO_NAME'] = repo_name
422 439 self.set_env_from_config(environ, config)
423 440
424 441 log.debug('http-app: starting app handler '
425 442 'with %s and process request', app)
426 443 return app(environ, ResponseFilter(start_response))
427 444 return _hg_stream
428 445
429 446 def git_stream(self):
430 447 if self._use_echo_app:
431 448 @wsgiapp
432 449 def _git_stream(environ, start_response):
433 450 app = EchoApp('fake_path', 'fake_name', None)
434 451 return app(environ, start_response)
435 452 return _git_stream
436 453 else:
437 454 @wsgiapp
438 455 def _git_stream(environ, start_response):
439 456 log.debug('http-app: handling git stream')
440 457 repo_path = environ['HTTP_X_RC_REPO_PATH']
441 458 repo_name = environ['HTTP_X_RC_REPO_NAME']
442 459 packed_config = base64.b64decode(
443 460 environ['HTTP_X_RC_REPO_CONFIG'])
444 461 config = msgpack.unpackb(packed_config)
445 462
446 463 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
447 464 self.set_env_from_config(environ, config)
448 465
449 466 content_type = environ.get('CONTENT_TYPE', '')
450 467
451 468 path = environ['PATH_INFO']
452 469 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
453 470 log.debug(
454 471 'LFS: Detecting if request `%s` is LFS server path based '
455 472 'on content type:`%s`, is_lfs:%s',
456 473 path, content_type, is_lfs_request)
457 474
458 475 if not is_lfs_request:
459 476 # fallback detection by path
460 477 if GIT_LFS_PROTO_PAT.match(path):
461 478 is_lfs_request = True
462 479 log.debug(
463 480 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
464 481 path, is_lfs_request)
465 482
466 483 if is_lfs_request:
467 484 app = scm_app.create_git_lfs_wsgi_app(
468 485 repo_path, repo_name, config)
469 486 else:
470 487 app = scm_app.create_git_wsgi_app(
471 488 repo_path, repo_name, config)
472 489
473 490 log.debug('http-app: starting app handler '
474 491 'with %s and process request', app)
475 492
476 493 return app(environ, start_response)
477 494
478 495 return _git_stream
479 496
480 497 def is_vcs_view(self, context, request):
481 498 """
482 499 View predicate that returns true if given backend is supported by
483 500 defined remotes.
484 501 """
485 502 backend = request.matchdict.get('backend')
486 503 return backend in self._remotes
487 504
488 505 def handle_vcs_exception(self, exception, request):
489 506 _vcs_kind = getattr(exception, '_vcs_kind', '')
490 507 if _vcs_kind == 'repo_locked':
491 508 # Get custom repo-locked status code if present.
492 509 status_code = request.headers.get('X-RC-Locked-Status-Code')
493 510 return HTTPRepoLocked(
494 511 title=exception.message, status_code=status_code)
512
513 exc_info = request.exc_info
514 store_exception(id(exc_info), exc_info)
515
495 516 traceback_info = 'unavailable'
496 517 if request.exc_info:
497 traceback_info = traceback.format_exc(request.exc_info[2])
518 exc_type, exc_value, exc_tb = request.exc_info
519 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
498 520
499 521 log.error(
500 522 'error occurred handling this request for path: %s, \n tb: %s',
501 523 request.path, traceback_info)
502 524 raise exception
503 525
504 526
505 527 class ResponseFilter(object):
506 528
507 529 def __init__(self, start_response):
508 530 self._start_response = start_response
509 531
510 532 def __call__(self, status, response_headers, exc_info=None):
511 533 headers = tuple(
512 534 (h, v) for h, v in response_headers
513 535 if not wsgiref.util.is_hop_by_hop(h))
514 536 return self._start_response(status, headers, exc_info)
515 537
516 538
517 539 def main(global_config, **settings):
518 540 if MercurialFactory:
519 541 hgpatches.patch_largefiles_capabilities()
520 542 hgpatches.patch_subrepo_type_mapping()
521 543
522 544 app = HTTPApplication(settings=settings, global_config=global_config)
523 545 return app.wsgi_app()
General Comments 0
You need to be logged in to leave comments. Login now