Exemplo n.º 1
0
    def tell_sentry(exception, state, allow_reraise=True):

        if isinstance(exception, pando.Response) and exception.code < 500:
            # Only log server errors
            return

        if isinstance(exception, NeedDatabase):
            # Don't flood Sentry when DB is down
            return

        if isinstance(exception, psycopg2.Error):
            from liberapay.website import website
            if getattr(website, 'db', None):
                try:
                    website.db.one('SELECT 1 AS x')
                except psycopg2.Error:
                    # If it can't answer this simple query, it's down.
                    website.db = NoDB()
                    # Show the proper 503 error page
                    state['exception'] = NeedDatabase()
                    # Tell gunicorn to gracefully restart this worker
                    os.kill(os.getpid(), signal.SIGTERM)

        if not sentry:
            # No Sentry, log to stderr instead
            traceback.print_exc()
            # Reraise if allowed
            if env.sentry_reraise and allow_reraise:
                raise
            return

        user = state.get('user')
        extra = {}
        if user is None:
            user_id = 'no user'
        elif user is ANON:
            user_id = 'ANON'
        elif not hasattr(user, 'id'):
            user_id = 'no id'
        else:
            user_id = user.id
            extra['user_url'] = 'https://liberapay.com/~{}/'.format(user_id)

        # Tell Sentry
        tags = {
            'user_id': user_id,
            'username': getattr(user, 'username', None),
        }
        extra['request_line'] = getattr(state.get('request'), 'line', None)
        result = sentry.captureException(tags=tags, extra=extra)

        # Put the Sentry id in the state for logging, etc
        state['sentry_ident'] = sentry.get_ident(result)
Exemplo n.º 2
0
    def tell_sentry(exception, state, allow_reraise=True):

        if isinstance(exception, pando.Response) and exception.code < 500:
            # Only log server errors
            return

        if isinstance(exception, NeedDatabase):
            # Don't flood Sentry when DB is down
            return

        if isinstance(exception, psycopg2.Error):
            from liberapay.website import website
            if getattr(website, 'db', None):
                try:
                    website.db.one('SELECT 1 AS x')
                except psycopg2.Error:
                    # If it can't answer this simple query, it's down.
                    website.db = NoDB()
                    # Show the proper 503 error page
                    state['exception'] = NeedDatabase()
                    # Tell gunicorn to gracefully restart this worker
                    os.kill(os.getpid(), signal.SIGTERM)

                if 'read-only' in str(exception):
                    # DB is in read only mode
                    state['db_is_readonly'] = True
                    # Show the proper 503 error page
                    state['exception'] = NeedDatabase()
                    # Don't reraise this in tests
                    allow_reraise = False

        if isinstance(exception, ValueError):
            if 'cannot contain NUL (0x00) characters' in str(exception):
                # https://github.com/liberapay/liberapay.com/issues/675
                response = state.get('response') or pando.Response()
                response.code = 400
                response.body = str(exception)
                return {'exception': None}

        if not sentry:
            # No Sentry, log to stderr instead
            traceback.print_exc()
            # Reraise if allowed
            if env.sentry_reraise and allow_reraise:
                raise
            return {'sentry_ident': None}

        # Prepare context data
        sentry_data = {}
        if state:
            try:
                sentry_data['tags'] = {
                    'lang': getattr(state.get('locale'), 'language', None),
                }
                request = state.get('request')
                user_data = sentry_data['user'] = {}
                if request is not None:
                    user_data['ip_address'] = str(request.source)
                    sentry_data['request'] = {
                        'method': request.method,
                        'url': request.line.uri,
                        'headers': {
                            k: b', '.join(v)
                            for k, v in request.headers.items()
                            if k != b'Cookie'
                        },
                    }
                user = state.get('user')
                if isinstance(user, Participant):
                    user_data['id'] = getattr(user, 'id', None)
                    user_data['username'] = getattr(user, 'username', None)
            except Exception as e:
                tell_sentry(e, {})

        # Tell Sentry
        result = sentry.captureException(data=sentry_data)

        # Put the Sentry id in the state for logging, etc
        return {'sentry_ident': sentry.get_ident(result)}
Exemplo n.º 3
0
 def __getattr__(self, attr):
     raise NeedDatabase()
Exemplo n.º 4
0
    def tell_sentry(exception, state, allow_reraise=True, level=None):

        if isinstance(exception, pando.Response):
            if exception.code < 500:
                # Only log server errors
                return
            if not level and exception.code in (502, 504):
                # This kind of error is usually transient and not our fault.
                level = 'warning'

        if isinstance(exception, NeedDatabase):
            # Don't flood Sentry when DB is down
            return

        if isinstance(exception, PoolError):
            # If this happens, then the `DATABASE_MAXCONN` value is too low.
            state['exception'] = NeedDatabase()

        if isinstance(exception, psycopg2.Error):
            from liberapay.website import website
            if getattr(website, 'db', None):
                try:
                    website.db.one('SELECT 1 AS x')
                except psycopg2.Error as e:
                    # If it can't answer this simple query, then it's either
                    # down or unreachable. Show the proper 503 error page.
                    website.db.okay = False
                    state['exception'] = NeedDatabase()
                    if sentry:
                        # Record the exception raised above instead of the
                        # original one, to avoid duplicate issues.
                        return tell_sentry(e, state, allow_reraise=True)

                if 'read-only' in str(exception):
                    # DB is in read only mode
                    state['db_is_readonly'] = True
                    # Show the proper 503 error page
                    state['exception'] = NeedDatabase()
                    # Don't reraise this in tests
                    allow_reraise = False

        if isinstance(exception, ValueError):
            if 'cannot contain NUL (0x00) characters' in str(exception):
                # https://github.com/liberapay/liberapay.com/issues/675
                response = state.get('response') or pando.Response()
                response.code = 400
                response.body = str(exception)
                return {'exception': None, 'response': response}

        if not sentry:
            # No Sentry, log to stderr instead
            traceback.print_exc()
            # Reraise if allowed
            if env.sentry_reraise and allow_reraise:
                raise
            return {'sentry_ident': None}

        # Prepare context data
        if not level:
            level = 'warning' if isinstance(exception, Warning) else 'error'
        sentry_data = {'level': level}
        if state:
            try:
                sentry_data['tags'] = {
                    'lang': getattr(state.get('locale'), 'language', None),
                }
                request = state.get('request')
                user_data = sentry_data['user'] = {}
                if request is not None:
                    user_data['ip_address'] = str(request.source)
                    decode = lambda b: b.decode('ascii', 'backslashreplace')
                    sentry_data['request'] = {
                        'method': request.method,
                        'url': request.line.uri.decoded,
                        'headers': {
                            decode(k): decode(b', '.join(v))
                            for k, v in request.headers.items()
                            if k != b'Cookie'
                        },
                    }
                user = state.get('user')
                if isinstance(user, Participant):
                    user_data['id'] = getattr(user, 'id', None)
                    user_data['username'] = getattr(user, 'username', None)
            except Exception as e:
                tell_sentry(e, {})

        # Tell Sentry
        result = sentry.captureException(data=sentry_data)

        # Put the Sentry id in the state for logging, etc
        return {'sentry_ident': sentry.get_ident(result)}