Mercurial > public > src > rhodecode
changeset 1619:98fb507e4c8c demo
merged stable into demo
author | Marcin Kuzminski <marcin@python-works.com> |
---|---|
date | Sat, 08 Oct 2011 16:26:42 +0200 |
parents | 2913444c3848 (diff) a05d16fc1d87 (current diff) |
children | 7695d34b76b4 |
files | rhodecode/config/middleware.py |
diffstat | 11 files changed, 1300 insertions(+), 6 deletions(-) [+] |
line wrap: on
line diff
--- a/production.ini Sat Oct 08 16:26:29 2011 +0200 +++ b/production.ini Sat Oct 08 16:26:42 2011 +0200 @@ -52,6 +52,14 @@ commit_parse_limit = 50 use_gravatar = true +errormator = true +errormator.server_url = https://api.errormator.com +errormator.api_key = AewWgTYtmlcijfJlARVJYJUwFcekxyCi +errormator.buffer_flush_time = 60 +errormator.server = RhodeCode/Demo +errormator.report_404 = true + + #################################### ### CELERY CONFIG #### ####################################
--- a/rhodecode/config/middleware.py Sat Oct 08 16:26:29 2011 +0200 +++ b/rhodecode/config/middleware.py Sat Oct 08 16:26:42 2011 +0200 @@ -58,6 +58,10 @@ if asbool(full_stack): # Handle Python exceptions + if asbool(config.get('errormator')): + from rhodecode.lib.errormator_client import make_errormator_middleware + app = make_errormator_middleware(app, config) + app = ErrorHandler(app, global_conf, **config['pylons.errorware']) # Display error documents for 401, 403, 404 status codes (and @@ -67,6 +71,10 @@ else: app = StatusCodeRedirect(app, [400, 401, 403, 404, 500]) + + + + #enable https redirets based on HTTP_X_URL_SCHEME set by proxy app = HttpsFixup(app, config)
--- a/rhodecode/controllers/admin/repos.py Sat Oct 08 16:26:29 2011 +0200 +++ b/rhodecode/controllers/admin/repos.py Sat Oct 08 16:26:42 2011 +0200 @@ -269,6 +269,10 @@ # method='delete') # url('repo', repo_name=ID) + h.flash(_('deleted repository %s [no effect in demo]') + % repo_name, category='success') + return redirect(url('repos')) + repo_model = RepoModel() repo = repo_model.get_by_repo_name(repo_name) if not repo:
--- a/rhodecode/controllers/admin/settings.py Sat Oct 08 16:26:29 2011 +0200 +++ b/rhodecode/controllers/admin/settings.py Sat Oct 08 16:26:42 2011 +0200 @@ -173,7 +173,7 @@ hgsettings2 = self.sa.query(RhodeCodeUi)\ .filter(RhodeCodeUi.ui_key == '/').one() - hgsettings2.ui_value = form_result['paths_root_path'] + #hgsettings2.ui_value = form_result['paths_root_path'] #HOOKS hgsettings3 = self.sa.query(RhodeCodeUi)\
--- a/rhodecode/controllers/admin/users.py Sat Oct 08 16:26:29 2011 +0200 +++ b/rhodecode/controllers/admin/users.py Sat Oct 08 16:26:42 2011 +0200 @@ -140,6 +140,11 @@ # h.form(url('delete_user', id=ID), # method='delete') # url('user', id=ID) + + h.flash(_('successfully deleted user [no effect in demo]'), + category='success') + return redirect(url('users')) + user_model = UserModel() try: user_model.delete(id)
--- a/rhodecode/controllers/settings.py Sat Oct 08 16:26:29 2011 +0200 +++ b/rhodecode/controllers/settings.py Sat Oct 08 16:26:42 2011 +0200 @@ -138,6 +138,10 @@ # method='delete') # url('repo_settings_delete', repo_name=ID) + h.flash(_('deleted repository %s [no effect in demo]') + % repo_name, category='success') + return redirect(url('repos')) + repo_model = RepoModel() repo = repo_model.get_by_repo_name(repo_name) if not repo:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/errormator_client.py Sat Oct 08 16:26:42 2011 +0200 @@ -0,0 +1,1187 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2010, Webreactor - Marcin Lulek <info@webreactor.eu> +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the <organization> nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import cStringIO +import datetime +import gzip +import logging +import urllib +import urllib2 +import socket +import sys +import threading +import time +from logging.handlers import MemoryHandler, BufferingHandler +from webob import Request + +import json +class DateTimeEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, datetime.date): + return obj.isoformat() + if isinstance(obj, datetime.datetime): + return obj.isoformat() + return json.JSONEncoder.default(self, obj) + +from paste import request as paste_req +from paste.util.converters import asbool + +LEVELS = {'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + 'critical': logging.CRITICAL} + +log = logging.getLogger(__name__) +# used for slow query GATHERING/ - to be picked up by threaded logger +log_slow = logging.getLogger('errormator_client.slow') +log_slow.setLevel(logging.DEBUG) +# used to log errors and flushing them out to api +log_errors = logging.getLogger('errormator_client.error') +log_errors.setLevel(logging.DEBUG) +# used to log slow reports and flushing them out to api +log_slow_reports = logging.getLogger('errormator_client.slow_report') +log_slow_reports.setLevel(logging.DEBUG) + +DATE_FRMT = '%Y-%m-%dT%H:%M:%S.%f' + +def gzipcompress(bytestr): + stream = cStringIO.StringIO() + gzstream = gzip.GzipFile(fileobj=stream, compresslevel=1, mode='wb') + try: + try: + gzstream.write(bytestr) + finally: + gzstream.close() + return stream.getvalue() + finally: + stream.close() + + +#lets try to find fqdn +fqdn = socket.getfqdn() + +def sqlalchemy_07_listener(delta): + from sqlalchemy import event + from sqlalchemy.engine.base import Engine + + @event.listens_for(Engine, "before_cursor_execute") + def _before_cursor_execute(conn, cursor, stmt, params, context, execmany): + setattr(conn, 'err_query_start', datetime.datetime.utcnow()) + + @event.listens_for(Engine, "after_cursor_execute") + def _after_cursor_execute(conn, cursor, stmt, params, context, execmany): + td = datetime.datetime.utcnow() - conn.err_query_start + if td >= delta: + duration = float('%s.%s' % ( + (td.seconds + td.days * 24 * 3600) * 10 ** 6 / 10 ** 6, + td.microseconds) + ) + query_info = {'type':'sqlalchemy', + 'timestamp':conn.err_query_start.isoformat(), + 'duration': duration, + 'statement': stmt, + 'parameters': params + } + log_slow.debug('slow query detected', + extra={'errormator_data':query_info} + ) + delattr(conn, 'err_query_start') + +class ErrormatorException(Exception): + + @property + def message(self): + return self._message + + @message.setter + def message_set(self, message): + self._message = message + + def __str__(self): + return repr(self.args) + +#utils + +def send_request(data, request_url, timeout=30, + exception_on_failure=False, + gzip=False): + try: + req = urllib2.Request(request_url, + json.dumps(data, cls=DateTimeEncoder), + headers={'Content-Type': 'application/json'}) + #req.headers['Content-Encoding'] = 'gzip' + try: + conn = urllib2.urlopen(req, timeout=timeout) + conn.close() + return True + except TypeError as e: + conn = urllib2.urlopen(req) + if conn.getcode() != 200: + message = 'ERRORMATOR: response code: %s' % conn.getcode() + log.error(message) + if exception_on_failure: + raise ErrormatorException(message) + except IOError as e: + message = 'ERRORMATOR: problem: %s' % e + log.error(message) + if exception_on_failure: + raise ErrormatorException(message) + +def process_environ(environ, traceback=False): + parsed_request = {} + additional_info = {} + for key, value in environ.items(): + if key.startswith('errormator.'): + additional_info[key[11:]] = unicode(value) + try: + if isinstance(value, str): + parsed_request[key] = value.decode('utf8') + else: + parsed_request[key] = unicode(value) + except: + # this CAN go wrong + pass + if traceback: + # only do this if there was an error + # reparse with webob to get all the info we want + req = Request(environ) + parsed_request['ERRORMATOR_COOKIES'] = dict(req.cookies) + parsed_request['ERRORMATOR_GET'] = dict([(k, req.GET.getall(k)) for k in req.GET]) + parsed_request['ERRORMATOR_POST'] = dict([(k, req.POST.getall(k)) for k in req.POST]) + if environ.get("HTTP_X_REAL_IP"): + remote_addr = environ.get("HTTP_X_REAL_IP") + elif environ.get("HTTP_X_FORWARDED_FOR"): + remote_addr = environ.get("HTTP_X_FORWARDED_FOR")\ + .split(',')[0].strip() + else: + remote_addr = environ.get('REMOTE_ADDR') + return parsed_request, remote_addr, additional_info + +def create_report_structure(environ, traceback=None, message=None, + http_status=200, server='unknown server'): + (parsed_request, remote_addr, additional_info) = \ + process_environ(environ, traceback) + parsed_data = {'report_details': []} + parsed_data['http_status'] = 500 if traceback else http_status + parsed_data['priority'] = 5 + parsed_data['server'] = (server or + environ.get('SERVER_NAME', 'unknown server')) + detail_entry = {} + if traceback: + detail_entry['request'] = parsed_request + #conserve bandwidth + detail_entry['request'].pop('HTTP_USER_AGENT', None) + detail_entry['request'].pop('REMOTE_ADDR', None) + detail_entry['request'].pop('HTTP_COOKIE', None) + detail_entry['request'].pop('webob._parsed_cookies', None) + detail_entry['request'].pop('webob._parsed_post_vars', None) + detail_entry['request'].pop('webob._parsed_query_vars', None) + + + detail_entry['ip'] = remote_addr + detail_entry['user_agent'] = environ.get('HTTP_USER_AGENT') + detail_entry['username'] = environ.get('REMOTE_USER', u'') + detail_entry['url'] = paste_req.construct_url(environ) + message = message or additional_info.get('message', u'') + detail_entry['message'] = message + parsed_data['report_details'].append(detail_entry) + return parsed_data, additional_info + + + +class ErrormatorLogHandler(MemoryHandler): + def __init__(self, capacity=50, async=True, api_key=None, server_url=None, + server_name=None, timeout=30, buffer_flush_time=60): + """ + Initialize the handler with the buffer size, the level at which + flushing should occur and an optional target. + """ + BufferingHandler.__init__(self, capacity) + self.capacity = capacity + self.async = asbool(async) + self.api_key = api_key + self.server_url = server_url + self.timeout = timeout + self.server = server_name or fqdn + self.buffer_flush_time = buffer_flush_time + self.last_flush_time = datetime.datetime.now() + + def shouldFlush(self, record): + """ + Check for buffer full or a record at the flushLevel or higher. + """ + tdelta = datetime.datetime.now() - self.last_flush_time + return (len(self.buffer) >= self.capacity or + tdelta.seconds >= self.buffer_flush_time) + + def emit(self, record): + #skip reports from errormator itself + if record.name.startswith('errormator_client'): + return + MemoryHandler.emit(self, record) + + def flush(self): + """ + For a ErrormatorLog, flushing means just sending the buffered + records to the listerner, if there is one. + """ + entries = [] + # if service basic data is not supplied just clear the buffer + if self.api_key and self.server_url: + for record in self.buffer: + if not getattr(record, 'created'): + time_string = datetime.datetime.utcnow().isoformat() + else: + time_string = time.strftime(DATE_FRMT, + time.gmtime(record.created)) % record.msecs + try: + entries.append( + {'log_level':record.levelname, + 'message':'%s %s' % (record.name, record.getMessage(),), + 'server': self.server, + 'date':time_string + }) + except TypeError, e : + #handle some weird case where record.getMessage() fails + pass + + remote_call = RemoteCall(entries) + if self.async: + remote_call_async = AsyncRemoteCall(self.api_key, + self.server_url, + remote_call, + self.timeout, + endpoint='/api/logs') + remote_call_async.start() + else: + remote_call.submit(self.api_key, self.server_url, + timeout=self.timeout, endpoint='/api/logs') + self.buffer = [] + self.last_flush_time = datetime.datetime.now() + +class ErrormatorReportHandler(MemoryHandler): + def __init__(self, capacity=5, async=True, api_key=None, server_url=None, + server_name=None, timeout=30, buffer_flush_time=60, endpoint=None): + """ + Initialize the handler with the buffer size, the level at which + flushing should occur and an optional target. + """ + BufferingHandler.__init__(self, capacity) + self.capacity = capacity + self.async = asbool(async) + self.api_key = api_key + self.server_url = server_url + self.timeout = timeout + self.server = server_name or fqdn + self.buffer_flush_time = buffer_flush_time + self.last_flush_time = datetime.datetime.now() + self.endpoint = endpoint + if endpoint == '/api/slow_reports': + self.allow_emit = 'errormator_client.slow_report' + elif endpoint == '/api/reports': + self.allow_emit = 'errormator_client.error' + + def shouldFlush(self, record): + """ + Check for buffer full or a record at the flushLevel or higher. + """ + tdelta = datetime.datetime.now() - self.last_flush_time + return (len(self.buffer) >= self.capacity or + tdelta.seconds >= self.buffer_flush_time) + + def emit(self, record): + if record.name == self.allow_emit: + MemoryHandler.emit(self, record) + + def flush(self): + """ + For a ErrormatorLog, flushing means just sending the buffered + records to the listerner, if there is one. + """ + entries = [] + # if service basic data is not supplied just clear the buffer + if self.api_key and self.server_url and self.endpoint: + for record in self.buffer: + entries.append(record.errormator_data) + remote_call = RemoteCall(entries) + if self.async: + remote_call_async = AsyncRemoteCall(self.api_key, + self.server_url, + remote_call, + self.timeout, + endpoint=self.endpoint) + remote_call_async.start() + else: + remote_call.submit(self.api_key, self.server_url, + timeout=self.timeout, endpoint=self.endpoint) + self.buffer = [] + self.last_flush_time = datetime.datetime.now() + + +class RemoteCall(object): + """ Handled actual communication of data to the server """ + __protocol_version__ = '0.2' + + def __init__(self, payload={}): + self.payload = payload + + def submit(self, api_key, server_url, + endpoint=None, + errormator_client='python', + exception_on_failure=False, + timeout=30, + gzip=False): + if not endpoint: + raise ErrormatorException('No endpoint specified') + GET_vars = urllib.urlencode( + {'api_key': api_key, + 'protocol_version': self.__protocol_version__}) + server_url = '%s%s?%s' % (server_url, endpoint, GET_vars,) + if send_request(self.payload, server_url, timeout=timeout, + exception_on_failure=exception_on_failure): + message = '%s entries sent: %s' % (endpoint, len(self.payload),) + log.info(message) + +class AsyncRemoteCall(threading.Thread): + def __init__(self, api_key, server_url, callable, timeout, endpoint): + super(AsyncRemoteCall, self).__init__() + self.callable = callable + self.api_key = api_key + self.server_url = server_url + self.timeout = timeout + self.endpoint = endpoint + + def run(self): + self.callable.submit(self.api_key, self.server_url, + timeout=self.timeout, endpoint=self.endpoint) + +# taken from pyramid_debugtoolbar - special kudos for raydeo and pyramid team ;) +# https://github.com/Pylons/pyramid_debugtoolbar +class ThreadTrackingHandler(logging.Handler): + def __init__(self): + if threading is None: + raise NotImplementedError( + "threading module is not available, " + "the logging panel cannot be used without it") + logging.Handler.__init__(self) + self.records = {} # a dictionary that maps threads to log records + + def emit(self, record): + #append reports from errormator slow queries + if record.name in ('errormator_client.slow',): + self.get_records().append(record) + + def get_records(self, thread=None): + """ + Returns a list of records for the provided thread, of if none is + provided, returns a list for the current thread. + """ + if thread is None: + thread = threading.currentThread() + if thread not in self.records: + self.records[thread] = [] + return self.records[thread] + + def clear_records(self, thread=None): + if thread is None: + thread = threading.currentThread() + if thread in self.records: + del self.records[thread] + + +# the code below is shamelessly ripped (and slightly altered) +# from the flickzeug package + +# Copyright (c) 2009 by the Flickzeug Team, see AUTHORS for more details. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# * The names of the contributors may not be used to endorse or +# promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +""" + flickzeug.debug.tbtools + ~~~~~~~~~~~~~~~~~~~~~~~ + + This module provides various traceback related utility functions. + + :copyright: (c) 2009 by the Werkzeug Team, see AUTHORS for more details. + :license: BSD. +""" +import re +import os +import inspect +import traceback +import codecs + + +_coding_re = re.compile(r'coding[:=]\s*([-\w.]+)') +_line_re = re.compile(r'^(.*?)$(?m)') +_funcdef_re = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)') +UTF8_COOKIE = '\xef\xbb\xbf' + +system_exceptions = (SystemExit, KeyboardInterrupt) +try: + system_exceptions += (GeneratorExit,) +except NameError: + pass + + +class _Missing(object): + + def __repr__(self): + return 'no value' + + def __reduce__(self): + return '_missing' + +_missing = _Missing() + + +class cached_property(object): + """A decorator that converts a function into a lazy property. The + function wrapped is called the first time to retrieve the result + and then that calculated result is used the next time you access + the value:: + + class Foo(object): + + @cached_property + def foo(self): + # calculate something important here + return 42 + + The class has to have a `__dict__` in order for this property to + work. + + .. versionchanged:: 0.6 + the `writeable` attribute and parameter was deprecated. If a + cached property is writeable or not has to be documented now. + For performance reasons the implementation does not honor the + writeable setting and will always make the property writeable. + """ + + # implementation detail: this property is implemented as non-data + # descriptor. non-data descriptors are only invoked if there is + # no entry with the same name in the instance's __dict__. + # this allows us to completely get rid of the access function call + # overhead. If one choses to invoke __get__ by hand the property + # will still work as expected because the lookup logic is replicated + # in __get__ for manual invocation. + + def __init__(self, func, name=None, doc=None, writeable=False): + if writeable: + from warnings import warn + warn(DeprecationWarning('the writeable argument to the ' + 'cached property is a noop since 0.6 ' + 'because the property is writeable ' + 'by default for performance reasons')) + + self.__name__ = name or func.__name__ + self.__module__ = func.__module__ + self.__doc__ = doc or func.__doc__ + self.func = func + + def __get__(self, obj, type=None): + if obj is None: + return self + value = obj.__dict__.get(self.__name__, _missing) + if value is _missing: + value = self.func(obj) + obj.__dict__[self.__name__] = value + return value + + +def get_current_traceback(ignore_system_exceptions=False, + show_hidden_frames=False, skip=0): + """Get the current exception info as `Traceback` object. Per default + calling this method will reraise system exceptions such as generator exit, + system exit or others. This behavior can be disabled by passing `False` + to the function as first parameter. + """ + exc_type, exc_value, tb = sys.exc_info() + if ignore_system_exceptions and exc_type in system_exceptions: + raise + for x in xrange(skip): + if tb.tb_next is None: + break + tb = tb.tb_next + tb = Traceback(exc_type, exc_value, tb) + if not show_hidden_frames: + tb.filter_hidden_frames() + return tb + + +class Line(object): + """Helper for the source renderer.""" + __slots__ = ('lineno', 'code', 'in_frame', 'current') + + def __init__(self, lineno, code): + self.lineno = lineno + self.code = code + self.in_frame = False + self.current = False + + def classes(self): + rv = ['line'] + if self.in_frame: + rv.append('in-frame') + if self.current: + rv.append('current') + return rv + classes = property(classes) + + +class Traceback(object): + """Wraps a traceback.""" + + def __init__(self, exc_type, exc_value, tb): + self.exc_type = exc_type + self.exc_value = exc_value + if not isinstance(exc_type, str): + exception_type = exc_type.__name__ + if exc_type.__module__ not in ('__builtin__', 'exceptions'): + exception_type = exc_type.__module__ + '.' + exception_type + else: + exception_type = exc_type + self.exception_type = exception_type + + # we only add frames to the list that are not hidden. This follows + # the the magic variables as defined by paste.exceptions.collector + self.frames = [] + while tb: + self.frames.append(Frame(exc_type, exc_value, tb)) + tb = tb.tb_next + + def filter_hidden_frames(self): + """Remove the frames according to the paste spec.""" + new_frames = [] + hidden = False + for frame in self.frames: + hide = frame.hide + if hide in ('before', 'before_and_this'): + new_frames = [] + hidden = False + if hide == 'before_and_this': + continue + elif hide in ('reset', 'reset_and_this'): + hidden = False + if hide == 'reset_and_this': + continue + elif hide in ('after', 'after_and_this'): + hidden = True + if hide == 'after_and_this': + continue + elif hide or hidden: + continue + new_frames.append(frame) + + # if the last frame is missing something went terrible wrong :( + if self.frames[-1] in new_frames: + self.frames[:] = new_frames + + @property + def is_syntax_error(self): + """Is it a syntax error?""" + return isinstance(self.exc_value, SyntaxError) + + @property + def exception(self): + """String representation of the exception.""" + buf = traceback.format_exception_only(self.exc_type, self.exc_value) + return ''.join(buf).strip().decode('utf-8', 'replace') + + def log(self, logfile=None): + """Log the ASCII traceback into a file object.""" + if logfile is None: + logfile = sys.stderr + tb = self.plaintext.encode('utf-8', 'replace').rstrip() + '\n' + logfile.write(tb) + + @cached_property + def plaintext(self): + result = ['Traceback (most recent call last):'] + for frame in self.frames: + result.append('File "%s", line %s, in %s' % + (frame.filename, frame.lineno, frame.function_name,)) + result.append(' %s' % frame.current_line.strip()) + result.append('%s' % self.exception) + return '\n'.join(result) + + id = property(lambda x: id(x)) + + +class Frame(object): + """A single frame in a traceback.""" + + def __init__(self, exc_type, exc_value, tb): + self.lineno = tb.tb_lineno + self.function_name = tb.tb_frame.f_code.co_name + self.locals = tb.tb_frame.f_locals + self.globals = tb.tb_frame.f_globals + + fn = inspect.getsourcefile(tb) or inspect.getfile(tb) + if fn[-4:] in ('.pyo', '.pyc'): + fn = fn[:-1] + # if it's a file on the file system resolve the real filename. + if os.path.isfile(fn): + fn = os.path.realpath(fn) + self.filename = fn + self.module = self.globals.get('__name__') + self.loader = self.globals.get('__loader__') + self.code = tb.tb_frame.f_code + + # support for paste's traceback extensions + self.hide = self.locals.get('__traceback_hide__', False) + info = self.locals.get('__traceback_info__') + if info is not None: + try: + info = unicode(info) + except UnicodeError: + info = str(info).decode('utf-8', 'replace') + self.info = info + + def eval(self, code, mode='single'): + """Evaluate code in the context of the frame.""" + if isinstance(code, basestring): + if isinstance(code, unicode): + code = UTF8_COOKIE + code.encode('utf-8') + code = compile(code, '<interactive>', mode) + if mode != 'exec': + return eval(code, self.globals, self.locals) + exec code in self.globals, self.locals + + @cached_property + def sourcelines(self): + """The sourcecode of the file as list of unicode strings.""" + # get sourcecode from loader or file + source = None + if self.loader is not None: + try: + if hasattr(self.loader, 'get_source'): + source = self.loader.get_source(self.module) + elif hasattr(self.loader, 'get_source_by_code'): + source = self.loader.get_source_by_code(self.code) + except: + # we munch the exception so that we don't cause troubles + # if the loader is broken. + pass + + if source is None: + try: + f = file(self.filename) + except IOError: + return [] + try: + source = f.read() + finally: + f.close() + + # already unicode? return right away + if isinstance(source, unicode): + return source.splitlines() + + # yes. it should be ascii, but we don't want to reject too many + # characters in the debugger if something breaks + charset = 'utf-8' + if source.startswith(UTF8_COOKIE): + source = source[3:] + else: + for idx, match in enumerate(_line_re.finditer(source)): + match = _line_re.search(match.group()) + if match is not None: + charset = match.group(1) + break + if idx > 1: + break + + # on broken cookies we fall back to utf-8 too + try: + codecs.lookup(charset) + except LookupError: + charset = 'utf-8' + + return source.decode(charset, 'replace').splitlines() + + @property + def current_line(self): + try: + return self.sourcelines[self.lineno - 1] + except IndexError: + return u'' + + id = property(lambda x: id(x)) + + +class TracebackCatcher(object): + """Enables exception catching for an application + + TracebackCatcher enables a developer to report exceptions when they + occur in an application in an extensible fashion. When an exception + occurs, a :class:`flickzeug.tbtools.Traceback` object is created, + then passed along with the environ into the callable specified with + the callback parameter. + + A plain 500 Server Error is returned when an exception occurs. + + :param callback: the function to call with the traceback when an + exception occurs + :param catch_callback: boolean indicating whether exceptions thrown + while calling the callback should be caught + + """ + def __init__(self, app, callback, catch_callback=True): + self.app = app + self.callback = callback + self.catch_callback = catch_callback + + def __call__(self, environ, start_response): + """Run the application and conserve the traceback frames.""" + app_iter = None + try: + app_iter = self.app(environ, start_response) + for item in app_iter: + yield item + if hasattr(app_iter, 'close'): + app_iter.close() + except: + if hasattr(app_iter, 'close'): + app_iter.close() + + #we need that here + exc_type, exc_value, tb = sys.exc_info() + traceback = get_current_traceback(skip=1, show_hidden_frames=True, + ignore_system_exceptions=True) + if self.catch_callback: + try: + self.callback(traceback, environ) + except Exception, e: + log.error( + 'ERRORMATOR: Exception in logging callback: %s' % e) + else: + self.callback(traceback, environ) + # by default reraise exceptions for app/FW to handle + if self.errormator.reraise_exceptions: + raise exc_type, exc_value, tb + try: + start_response('500 INTERNAL SERVER ERROR', + [('Content-Type', 'text/html; charset=utf-8')]) + except Exception: + # if we end up here there has been output but an error + # occurred. in that situation we can do nothing fancy any + # more, better log something into the error log and fall + # back gracefully. + environ['wsgi.errors'].write( + 'TracebackCatcher middleware caught exception in streamed ' + 'response at a point where response headers were already ' + 'sent.\n') + else: + yield 'Server Error' + + +#above original Traceback Catcher for reference + +class ErrormatorBase(object): + + __version__ = 0.2 + + def __init__(self, app, config, log_handler=None): + self.app = app + self.enabled = asbool(config.get('errormator', True)) + self.server = config.get('errormator.server') or fqdn + self.async = asbool(config.get('errormator.async', True)) + self.catch_callback = asbool( + config.get('errormator.catch_callback', True)) + self.client = config.get('errormator.client', 'python') + self.api_key = config.get('errormator.api_key') + self.server_url = config.get('errormator.server_url') + self.buffer_flush_time = int(config.get('errormator.buffer_flush_time', 60)) + self.timeout = int(config.get('errormator.timeout', 20)) + self.gzip = asbool(config.get('errormator.gzip', False)) + self.reraise_exceptions = asbool( + config.get('errormator.reraise_exceptions', True)) + self.slow_request = asbool(config.get('errormator.slow_request', False)) + self.slow_request_time = float(config.get('errormator.slow_request.time', 10)) + self.slow_query_time = float(config.get('errormator.slow_query.time', 5)) + if self.slow_request_time < 1: + self.slow_request_time = 1.0 + if self.slow_query_time < 1: + self.slow_query_time = 1.0 + self.slow_request_time = datetime.timedelta(seconds=self.slow_request_time) + self.slow_query_time = datetime.timedelta(seconds=self.slow_query_time) + self.log_handler = log_handler + + def data_filter(self, structure, section=None): + if section == 'error_report': + keys_to_check = (structure['report_details'][0]['request'].get('ERRORMATOR_COOKIES'), + structure['report_details'][0]['request'].get('ERRORMATOR_GET'), + structure['report_details'][0]['request'].get('ERRORMATOR_POST') + ) + elif section == 'slow_request': + keys_to_check = (structure['request'].get('ERRORMATOR_COOKIES'), + structure['request'].get('ERRORMATOR_GET'), + structure['request'].get('ERRORMATOR_POST') + ) + else: + # do not filter for 404 by default + return structure + + for source in filter(None, keys_to_check): + for k, v in source.items(): + if ('password' in k or 'passwd' in k or 'pwd' in k + or 'auth_tkt' in k): + source[k] = u'***' + return structure + + +class ErrormatorCatcher(ErrormatorBase): + + __version__ = 0.2 + + def generate_report(self, environ, traceback=None, message=None, http_status=200): + parsed_data, additional_info = create_report_structure(environ, + traceback, + message, + http_status, + server=self.server + ) + if traceback: + exception_text = traceback.exception + traceback_text = traceback.plaintext + parsed_data['error_type'] = exception_text + parsed_data['traceback'] = traceback_text + else: + parsed_data['error_type'] = '404 Not Found' + if parsed_data['http_status'] == 404 and \ + parsed_data.get('traceback'): + #make sure traceback is empty for 404's + parsed_data['traceback'] = u'' + #lets populate with additional environ data + parsed_data.update(additional_info) + parsed_data['errormator.client'] = self.client + if traceback: + parsed_data = self.data_filter(parsed_data, 'error_report') + else: + parsed_data = self.data_filter(parsed_data, '404_report') + return parsed_data + + def __call__(self, environ, start_response): + """Run the application and conserve the traceback frames. + also determine if we got 404 + """ + app_iter = None + detected_data = [] + try: + #inject local reporting function to environ + if 'errormator.report' not in environ: + def local_report(message, include_traceback=True, + http_status=200): + if include_traceback: + traceback = get_current_traceback(skip=1, + show_hidden_frames=True, + ignore_system_exceptions=True) + else: + traceback = None + report = self.generate_report(environ, traceback, + message=message, http_status=500) + error_call = RemoteCall([report]) + error_call.submit(self.api_key, self.server_url, + timeout=self.timeout, + endpoint='/api/reports') + + environ['errormator.report'] = local_report + + #inject remote logging function to environ + if 'errormator.log' not in environ: + def local_log(level, message): + log_call = RemoteCall([ + {"log_level":level, + "message":message, + "timestamp":datetime.datetime.utcnow().isoformat(), + "server":self.server + }]) + log_call.submit(self.api_key, self.server_url, + timeout=self.timeout, + endpoint='/api/logs') + + environ['errormator.log'] = local_log + + app_iter = self.app(environ, start_response) + return app_iter + +# for item in app_iter: +# yield item +# if hasattr(app_iter, 'close'): +# app_iter.close() +# +# if detected_data and detected_data[0] == '404': +# self.report(environ) + except: + if hasattr(app_iter, 'close'): + app_iter.close() + #we need that here + exc_type, exc_value, tb = sys.exc_info() + traceback = get_current_traceback(skip=1, show_hidden_frames=True, + ignore_system_exceptions=True) + report = self.generate_report(environ, traceback, + message=None, http_status=500) + #leave trace of exception in logs + log_errors.warning('%s @ %s' % (report.get('error_type'), + report['report_details'][0].get('url')), + extra={'errormator_data':report} + ) + # by default reraise exceptions for app/FW to handle + if self.reraise_exceptions: + raise exc_type, exc_value, tb + try: + start_response('500 INTERNAL SERVER ERROR', + [('Content-Type', 'text/html; charset=utf-8')]) + except Exception: + # if we end up here there has been output but an error + # occurred. in that situation we can do nothing fancy any + # more, better log something into the error log and fall + # back gracefully. + environ['wsgi.errors'].write( + 'TracebackCatcher middleware catched exception in streamed' + ' response at a point where response headers were already' + ' sent.\n') + else: + return 'Server Error' + +class ErrormatorReport404(ErrormatorCatcher): + __version__ = 0.2 + + def __call__(self, environ, start_response): + """Run the application, determine if we got 404 + """ + app_iter = None + detected_data = [] + + def detect_headers(status, headers, *k, **kw): + detected_data[:] = status[:3], headers + return start_response(status, headers, *k, **kw) + + app_iter = self.app(environ, detect_headers) + try: + return app_iter + finally: + #lets process environ + if detected_data and detected_data[0] == '404': + report = self.generate_report(environ, traceback=None, + message=None, http_status=404) + #leave trace of exception in logs + log_errors.warning('%s @ %s' % (report.get('error_type'), + report['report_details'][0].get('url')), + extra={'errormator_data':report} + ) + +class ErrormatorSlowRequest(ErrormatorBase): + __version__ = 0.2 + + def generate_report(self, environ, start_time, end_time, records=[]): + (parsed_request, remote_addr, additional_info) = \ + process_environ(environ, True) + report = { + "start_time":start_time.isoformat(), + "end_time":end_time.isoformat(), + "template_start_time":environ.get('errormator.tmpl_start_time'), + "report_details":[], + "server": self.server, + "url": paste_req.construct_url(environ), + "request":{ + "user_agent": environ.get('HTTP_USER_AGENT'), + "username": environ.get('REMOTE_USER', u''), + "ERRORMATOR_COOKIES":parsed_request['ERRORMATOR_COOKIES'], + "ERRORMATOR_POST":parsed_request['ERRORMATOR_POST'], + "ERRORMATOR_GET":parsed_request['ERRORMATOR_GET']} + } + for record in records: + report['report_details'].append(record.errormator_data) + report = self.data_filter(report, 'slow_request') + return report + + def __call__(self, environ, start_response): + """Run the application, determine if we got 404 + """ + app_iter = None + detected_data = [] + start_time = datetime.datetime.utcnow() + app_iter = self.app(environ, start_response) + try: + return app_iter + finally: + end_time = datetime.datetime.utcnow() + delta = end_time - start_time + records = self.log_handler.get_records() + self.log_handler.clear_records() + if delta >= self.slow_request_time or len(records) > 0: + report = self.generate_report(environ, start_time, end_time, + records) + log_slow_reports.info('slow request/queries detected: %s' % + report.get('url'), + extra={'errormator_data':report} + ) + +#deprecated bw compat +class ErrormatorHTTPCodeSniffer(object): + def __init__(self, app, config): + self.app = app + + def __call__(self, environ, start_response): + return self.app(environ, start_response) + + +def make_errormator_middleware(app, global_config, **kw): + config = global_config.copy() + config.update(kw) + #this shuts down all errormator functionalities + if not asbool(config.get('errormator', True)): + return app + + filter_callable = config.get('errormator.filter_callable') + if filter_callable: + try: + parts = filter_callable.split(':') + _tmp = __import__(parts[0], globals(), locals(), [parts[1], ], -1) + filter_callable = getattr(_tmp, parts[1]) + print filter_callable + except ImportError, e: + filter_callable = None + log.error('Could not import filter callable, using default, %s' % e) + + + # batch error sending logger -> api + error_handler = ErrormatorReportHandler( + capacity=int(config.get('errormator.error.buffer', 5)), + async=asbool(config.get('errormator.error.async', True)), + api_key=config.get('errormator.api_key'), + server_url=config.get('errormator.server_url'), + server_name=config.get('errormator.server_name'), + timeout=config.get('errormator.error.timeout', 30), + buffer_flush_time=int(config.get('errormator.buffer_flush_time', 60)), + endpoint='/api/reports' + ) + error_handler.setLevel(logging.DEBUG) + logging.root.addHandler(error_handler) + + # general logging handler -> api + if asbool(config.get('errormator.logging', True)): + level = LEVELS.get(config.get('errormator.logging.level', + 'NOTSET').lower(), + logging.NOTSET) + log_handler = ErrormatorLogHandler( + capacity=int(config.get('errormator.logging.buffer', 50)), + async=asbool(config.get('errormator.logging.async', True)), + api_key=config.get('errormator.api_key'), + server_url=config.get('errormator.server_url'), + server_name=config.get('errormator.server_name'), + timeout=int(config.get('errormator.logging.timeout', 30)), + buffer_flush_time=int(config.get('errormator.buffer_flush_time', 60)) + ) + log_handler.setLevel(level) + logging.root.addHandler(log_handler) + + if asbool(config.get('errormator.slow_request', False)): + # batch error sending logger -> api - same as error handler but we dont + # want to share buffers + query_handler = ErrormatorReportHandler( + capacity=int(config.get('errormator.error.buffer', 5)), + async=asbool(config.get('errormator.error.async', True)), + api_key=config.get('errormator.api_key'), + server_url=config.get('errormator.server_url'), + server_name=config.get('errormator.server_name'), + timeout=config.get('errormator.error.timeout', 30), + buffer_flush_time=int(config.get('errormator.buffer_flush_time', 60)), + endpoint='/api/slow_reports' + ) + query_handler.setLevel(logging.DEBUG) + logging.root.addHandler(query_handler) + #register thread tracking handler + thread_tracking_handler = ThreadTrackingHandler() + logging.root.addHandler(thread_tracking_handler) + thread_tracking_handler.setLevel(logging.DEBUG) + #pass the threaded handler to middleware + app = ErrormatorSlowRequest(app, config=config, + log_handler=thread_tracking_handler) + if filter_callable: + app.data_filter = filter_callable + #register sqlalchemy listeners + if asbool(config.get('errormator.slow_request.sqlalchemy', False)): + try: + from sqlalchemy import event + from sqlalchemy.engine.base import Engine + slow_query_time = float(config.get('errormator.slow_query.time', 5)) + if slow_query_time < 1: + slow_query_time = 1.0 + tdelta = datetime.timedelta(seconds=slow_query_time) + sqlalchemy_07_listener(tdelta) + except ImportError, e: + console.warning('Sqlalchemy older than 0.7 - logging disabled') + + + if asbool(config.get('errormator.report_404', False)): + app = ErrormatorReport404(app, config=config) + if filter_callable: + app.data_filter = filter_callable + + if asbool(config.get('errormator.report_errors', True)): + app = ErrormatorCatcher(app, config=config) + if filter_callable: + app.data_filter = filter_callable + return app + +#alias for be compat +make_catcher_middleware = make_errormator_middleware + +def make_sniffer_middleware(app, global_config, **kw): + #deprecated, errormator catcher will handle everything + return app
--- a/rhodecode/model/user.py Sat Oct 08 16:26:29 2011 +0200 +++ b/rhodecode/model/user.py Sat Oct 08 16:26:42 2011 +0200 @@ -181,8 +181,10 @@ " crucial for entire application")) for k, v in form_data.items(): if k == 'new_password' and v != '': - user.password = v - user.api_key = generate_api_key(user.username) + #demo application protection + if user.username != 'demo': + user.password = v + user.api_key = generate_api_key(user.username) else: if k not in ['admin', 'active']: setattr(user, k, v)
--- a/rhodecode/templates/base/root.html Sat Oct 08 16:26:29 2011 +0200 +++ b/rhodecode/templates/base/root.html Sat Oct 08 16:26:42 2011 +0200 @@ -20,11 +20,11 @@ ${self.css()} - %if c.ga_code: + ##%if c.ga_code: <!-- Analytics --> <script type="text/javascript"> var _gaq = _gaq || []; - _gaq.push(['_setAccount', '${c.ga_code}']); + _gaq.push(['_setAccount', 'UA-9301385-2']); _gaq.push(['_trackPageview']); (function() { @@ -33,7 +33,7 @@ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })(); </script> - %endif + ##%endif ## JAVASCRIPT ## <%def name="js()">
--- a/rhodecode/templates/index_base.html Sat Oct 08 16:26:29 2011 +0200 +++ b/rhodecode/templates/index_base.html Sat Oct 08 16:26:42 2011 +0200 @@ -1,4 +1,66 @@ <%page args="parent" /> + + <div style="border: 1px solid #003367;padding:10px;margin-bottom:10px;background-color: #fff " class="top-left-rounded-corner bottom-right-rounded-corner bottom-left-rounded-corner top-right-rounded-corner"> + + <h4>Welcome to RhodeCode - ${c.rhodecode_version} demo version. </h4> + <br/> + <p><b>RhodeCode</b>is Pylons framework based Mercurial repository + browser/management tool with build in push/pull server and full text search. + It works on http/https, has build in permission/authentication system with + ability to auth via LDAP or ActiveDirectory. RhodeCode also supports + simple API so it's easy integrable with existing systems. + + RhodeCode is similar in some respects to github or bitbucket_, + however RhodeCode can be run as standalone hosted application on your own server. + It is <b>open source</b> and <a href="#" id="donate">donation ware</a> and + focuses more on providing a customized, self administered interface for + Mercurial(and soon GIT) repositories. RhodeCode is powered by a vcs library + that Lukasz Balcerzak and I created to handle multiple different version control systems. + </p> + <br/> + + <div style="padding:10px;display:none" id="donation_button"> + <h5>RhodeCode is an open source software. It's available to everybody for free, forever. + If You would like to support my development effort please donate</h5><br/> + <form action="https://www.paypal.com/cgi-bin/webscr" method="post"> + <input type="hidden" name="cmd" value="_s-xclick"> + <input type="hidden" name="hosted_button_id" value="8U2LLRPLBKWDU"> + <input style="border:0px !important" type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif" + border="0" name="submit" alt="PayPal - The safer, easier way to pay online!"> + <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1"> + </form> + </div> + + <p>See all RhodeCode features <a href="http://packages.python.org/RhodeCode/index.html#features">here </a></p> + + <p> + If You would like to install <b>RhodeCode</b> You can see installation docs <a href="http://packages.python.org/RhodeCode/installation.html">here</a> + </p> + <br/> + Default login for an administrative account is:<br/> + <b>login:demo</b> <br/> + <b>password:demo</b> <br/> + <br/> + + For better user experience deleting,updating users, and deletion of repos is turned + off. You can do pulls/pushes thru the demo app as you wish. You can + create repositories, and users. All permissions management is also available. + <br/>If you want to <b>create new user</b> do it ${h.link_to(_("here"),h.url('register'))} + <br/> + <br/> + Please report feedback,proposals and issues to: <br/> + <a href="http://www.bitbucket.org/marcinkuzminski/rhodecode/issues"> + http://www.bitbucket.org/marcinkuzminski/rhodecode/issues + </a> + <script type="text/javascript"> + + YUE.on('donate','click',function(){ + YUD.setStyle('donation_button','display',''); + }) + + </script> </div> + + <div class="box"> <!-- box / title --> <div class="title">
--- a/rhodecode/templates/login.html Sat Oct 08 16:26:29 2011 +0200 +++ b/rhodecode/templates/login.html Sat Oct 08 16:26:42 2011 +0200 @@ -74,4 +74,18 @@ </script> </div> <!-- end login --> + + + <br/> + <div style="border: 1px solid #003367;padding:10px;background-color: #fff " class="top-left-rounded-corner bottom-right-rounded-corner bottom-left-rounded-corner top-right-rounded-corner"> + + <h4>Welcome to RhodeCode - ${c.rhodecode_version} demo version. </h4> + <br/> + Default login for an administrative account is:<br/> + <b>login:demo</b> <br/> + <b>password:demo12</b> <br/> + <br/> + + </div> + </div>