[IMP] sentry: black, isort

pull/2516/head
Ivan 2020-02-28 02:07:12 +05:00 committed by prabakaran
parent 165715b747
commit 532bdbdbfe
7 changed files with 157 additions and 187 deletions

View File

@ -22,41 +22,44 @@ except ImportError:
def get_odoo_commit(odoo_dir):
'''Attempts to get Odoo git commit from :param:`odoo_dir`.'''
"""Attempts to get Odoo git commit from :param:`odoo_dir`."""
if not odoo_dir:
return
try:
return raven.fetch_git_sha(odoo_dir)
except raven.exceptions.InvalidGitRepository:
_logger.debug(
'Odoo directory: "%s" not a valid git repository', odoo_dir)
_logger.debug('Odoo directory: "%s" not a valid git repository', odoo_dir)
def initialize_raven(config, client_cls=None):
'''
"""
Setup an instance of :class:`raven.Client`.
:param config: Sentry configuration
:param client: class used to instantiate the raven client.
'''
enabled = config.get('sentry_enabled', False)
"""
enabled = config.get("sentry_enabled", False)
if not (HAS_RAVEN and enabled):
return
if config.get('sentry_odoo_dir') and config.get('sentry_release'):
_logger.debug('Both sentry_odoo_dir and sentry_release defined, choosing sentry_release')
if config.get("sentry_odoo_dir") and config.get("sentry_release"):
_logger.debug(
"Both sentry_odoo_dir and sentry_release defined, choosing sentry_release"
)
options = {
'release': config.get('sentry_release', get_odoo_commit(config.get('sentry_odoo_dir'))),
"release": config.get(
"sentry_release", get_odoo_commit(config.get("sentry_odoo_dir"))
)
}
for option in const.get_sentry_options():
value = config.get('sentry_%s' % option.key, option.default)
value = config.get("sentry_%s" % option.key, option.default)
if isinstance(option.converter, collections.Callable):
value = option.converter(value)
options[option.key] = value
level = config.get('sentry_logging_level', const.DEFAULT_LOG_LEVEL)
level = config.get("sentry_logging_level", const.DEFAULT_LOG_LEVEL)
exclude_loggers = const.split_multiple(
config.get('sentry_exclude_loggers', const.DEFAULT_EXCLUDE_LOGGERS)
config.get("sentry_exclude_loggers", const.DEFAULT_EXCLUDE_LOGGERS)
)
if level not in const.LOG_LEVEL_MAP:
level = const.DEFAULT_LOG_LEVEL
@ -64,18 +67,18 @@ def initialize_raven(config, client_cls=None):
client_cls = client_cls or raven.Client
client = client_cls(**options)
handler = OdooSentryHandler(
config.get('sentry_include_context', True),
config.get("sentry_include_context", True),
client=client,
level=const.LOG_LEVEL_MAP[level],
)
if exclude_loggers:
handler.addFilter(LoggerNameFilter(
exclude_loggers, name='sentry.logger.filter'))
handler.addFilter(
LoggerNameFilter(exclude_loggers, name="sentry.logger.filter")
)
raven.conf.setup_logging(handler)
wsgi_server.application = Sentry(
wsgi_server.application, client=client)
wsgi_server.application = Sentry(wsgi_server.application, client=client)
client.captureMessage('Starting Odoo Server')
client.captureMessage("Starting Odoo Server")
return client

View File

@ -1,24 +1,18 @@
# Copyright 2016-2017 Versada <https://versada.eu/>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Sentry',
'summary': 'Report Odoo errors to Sentry',
'version': '12.0.1.0.0',
'category': 'Extra Tools',
'website': 'https://odoo-community.org/',
'author': 'Mohammed Barsi,'
'Versada,'
'Nicolas JEUDY,'
'Odoo Community Association (OCA)',
'license': 'AGPL-3',
'application': False,
'installable': True,
'external_dependencies': {
'python': [
'raven',
]
},
'depends': [
'base',
],
"name": "Sentry",
"summary": "Report Odoo errors to Sentry",
"version": "12.0.1.0.0",
"category": "Extra Tools",
"website": "https://odoo-community.org/",
"author": "Mohammed Barsi,"
"Versada,"
"Nicolas JEUDY,"
"Odoo Community Association (OCA)",
"license": "AGPL-3",
"application": False,
"installable": True,
"external_dependencies": {"python": ["raven"]},
"depends": ["base"],
}

View File

@ -14,76 +14,72 @@ except ImportError:
_logger.debug('Cannot import "raven". Please make sure it is installed.')
def split_multiple(string, delimiter=',', strip_chars=None):
'''Splits :param:`string` and strips :param:`strip_chars` from values.'''
def split_multiple(string, delimiter=",", strip_chars=None):
"""Splits :param:`string` and strips :param:`strip_chars` from values."""
if not string:
return []
return [v.strip(strip_chars) for v in string.split(delimiter)]
SentryOption = collections.namedtuple(
'SentryOption', ['key', 'default', 'converter'])
SentryOption = collections.namedtuple("SentryOption", ["key", "default", "converter"])
# Mapping of Odoo logging level -> Python stdlib logging library log level.
LOG_LEVEL_MAP = dict([
(getattr(odoo.loglevels, 'LOG_%s' % x), getattr(logging, x))
for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET')
])
DEFAULT_LOG_LEVEL = 'warn'
LOG_LEVEL_MAP = {
getattr(odoo.loglevels, "LOG_%s" % x): getattr(logging, x)
for x in ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "NOTSET")
}
DEFAULT_LOG_LEVEL = "warn"
ODOO_USER_EXCEPTIONS = [
'odoo.exceptions.AccessDenied',
'odoo.exceptions.AccessError',
'odoo.exceptions.DeferredException',
'odoo.exceptions.MissingError',
'odoo.exceptions.RedirectWarning',
'odoo.exceptions.UserError',
'odoo.exceptions.ValidationError',
'odoo.exceptions.Warning',
'odoo.exceptions.except_orm',
"odoo.exceptions.AccessDenied",
"odoo.exceptions.AccessError",
"odoo.exceptions.DeferredException",
"odoo.exceptions.MissingError",
"odoo.exceptions.RedirectWarning",
"odoo.exceptions.UserError",
"odoo.exceptions.ValidationError",
"odoo.exceptions.Warning",
"odoo.exceptions.except_orm",
]
DEFAULT_IGNORED_EXCEPTIONS = ','.join(ODOO_USER_EXCEPTIONS)
DEFAULT_IGNORED_EXCEPTIONS = ",".join(ODOO_USER_EXCEPTIONS)
PROCESSORS = (
'raven.processors.SanitizePasswordsProcessor',
'odoo.addons.sentry.logutils.SanitizeOdooCookiesProcessor',
"raven.processors.SanitizePasswordsProcessor",
"odoo.addons.sentry.logutils.SanitizeOdooCookiesProcessor",
)
DEFAULT_PROCESSORS = ','.join(PROCESSORS)
DEFAULT_PROCESSORS = ",".join(PROCESSORS)
EXCLUDE_LOGGERS = (
'werkzeug',
)
DEFAULT_EXCLUDE_LOGGERS = ','.join(EXCLUDE_LOGGERS)
EXCLUDE_LOGGERS = ("werkzeug",)
DEFAULT_EXCLUDE_LOGGERS = ",".join(EXCLUDE_LOGGERS)
DEFAULT_TRANSPORT = 'threaded'
DEFAULT_TRANSPORT = "threaded"
def select_transport(name=DEFAULT_TRANSPORT):
return {
'requests_synchronous': raven.transport.RequestsHTTPTransport,
'requests_threaded': raven.transport.ThreadedRequestsHTTPTransport,
'synchronous': raven.transport.HTTPTransport,
'threaded': raven.transport.ThreadedHTTPTransport,
"requests_synchronous": raven.transport.RequestsHTTPTransport,
"requests_threaded": raven.transport.ThreadedRequestsHTTPTransport,
"synchronous": raven.transport.HTTPTransport,
"threaded": raven.transport.ThreadedHTTPTransport,
}.get(name, DEFAULT_TRANSPORT)
def get_sentry_options():
return [
SentryOption('dsn', '', str.strip),
SentryOption('install_sys_hook', False, None),
SentryOption('transport', DEFAULT_TRANSPORT, select_transport),
SentryOption('include_paths', '', split_multiple),
SentryOption('exclude_paths', '', split_multiple),
SentryOption('machine', defaults.NAME, None),
SentryOption('auto_log_stacks', defaults.AUTO_LOG_STACKS, None),
SentryOption('capture_locals', defaults.CAPTURE_LOCALS, None),
SentryOption('string_max_length', defaults.MAX_LENGTH_STRING, None),
SentryOption('list_max_length', defaults.MAX_LENGTH_LIST, None),
SentryOption('site', None, None),
SentryOption('include_versions', True, None),
SentryOption(
'ignore_exceptions', DEFAULT_IGNORED_EXCEPTIONS, split_multiple),
SentryOption('processors', DEFAULT_PROCESSORS, split_multiple),
SentryOption('environment', None, None),
SentryOption('release', None, None),
SentryOption("dsn", "", str.strip),
SentryOption("install_sys_hook", False, None),
SentryOption("transport", DEFAULT_TRANSPORT, select_transport),
SentryOption("include_paths", "", split_multiple),
SentryOption("exclude_paths", "", split_multiple),
SentryOption("machine", defaults.NAME, None),
SentryOption("auto_log_stacks", defaults.AUTO_LOG_STACKS, None),
SentryOption("capture_locals", defaults.CAPTURE_LOCALS, None),
SentryOption("string_max_length", defaults.MAX_LENGTH_STRING, None),
SentryOption("list_max_length", defaults.MAX_LENGTH_LIST, None),
SentryOption("site", None, None),
SentryOption("include_versions", True, None),
SentryOption("ignore_exceptions", DEFAULT_IGNORED_EXCEPTIONS, split_multiple),
SentryOption("processors", DEFAULT_PROCESSORS, split_multiple),
SentryOption("environment", None, None),
SentryOption("release", None, None),
]

View File

@ -18,56 +18,50 @@ except ImportError:
def get_request_info(request):
'''
"""
Returns context data extracted from :param:`request`.
Heavily based on flask integration for Sentry: https://git.io/vP4i9.
'''
"""
urlparts = urllib.parse.urlsplit(request.url)
return {
'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path),
'query_string': urlparts.query,
'method': request.method,
'headers': dict(get_headers(request.environ)),
'env': dict(get_environ(request.environ)),
"url": "{}://{}{}".format(urlparts.scheme, urlparts.netloc, urlparts.path),
"query_string": urlparts.query,
"method": request.method,
"headers": dict(get_headers(request.environ)),
"env": dict(get_environ(request.environ)),
}
def get_extra_context():
'''
"""
Extracts additional context from the current request (if such is set).
'''
"""
request = odoo.http.request
try:
session = getattr(request, 'session', {})
session = getattr(request, "session", {})
except RuntimeError:
ctx = {}
else:
ctx = {
'tags': {
'database': session.get('db', None),
},
'user': {
'login': session.get('login', None),
'uid': session.get('uid', None),
},
'extra': {
'context': session.get('context', {}),
"tags": {"database": session.get("db", None)},
"user": {
"login": session.get("login", None),
"uid": session.get("uid", None),
},
"extra": {"context": session.get("context", {})},
}
if request.httprequest:
ctx.update({
'request': get_request_info(request.httprequest),
})
ctx.update({"request": get_request_info(request.httprequest)})
return ctx
class LoggerNameFilter(logging.Filter):
'''
"""
Custom :class:`logging.Filter` which allows to filter loggers by name.
'''
"""
def __init__(self, loggers, name=''):
def __init__(self, loggers, name=""):
super(LoggerNameFilter, self).__init__(name=name)
self._exclude_loggers = set(loggers)
@ -76,12 +70,12 @@ class LoggerNameFilter(logging.Filter):
class OdooSentryHandler(SentryHandler):
'''
"""
Customized :class:`raven.handlers.logging.SentryHandler`.
Allows to add additional Odoo and HTTP request data to the event which is
sent to Sentry.
'''
"""
def __init__(self, include_extra_context, *args, **kwargs):
super(OdooSentryHandler, self).__init__(*args, **kwargs)
@ -94,12 +88,10 @@ class OdooSentryHandler(SentryHandler):
class SanitizeOdooCookiesProcessor(SanitizePasswordsProcessor):
'''
"""
Custom :class:`raven.processors.Processor`.
Allows to sanitize sensitive Odoo cookies, namely the "session_id" cookie.
'''
"""
KEYS = FIELDS = frozenset([
'session_id',
])
KEYS = FIELDS = frozenset(["session_id"])

View File

@ -1,7 +1,4 @@
# Copyright 2016-2017 Versada <https://versada.eu/>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import (
test_client,
test_logutils,
)
from . import test_client, test_logutils

View File

@ -20,25 +20,25 @@ def log_handler_by_class(logger, handler_cls):
def remove_logging_handler(logger_name, handler_cls):
'''Removes handlers of specified classes from a :class:`logging.Logger`
"""Removes handlers of specified classes from a :class:`logging.Logger`
with a given name.
:param string logger_name: name of the logger
:param handler_cls: class of the handler to remove. You can pass a tuple of
classes to catch several classes
'''
"""
logger = logging.getLogger(logger_name)
for handler in log_handler_by_class(logger, handler_cls):
logger.removeHandler(handler)
class InMemoryClient(raven.Client):
'''A :class:`raven.Client` subclass which simply stores events in a list.
"""A :class:`raven.Client` subclass which simply stores events in a list.
Extended based on the one found in raven-python to avoid additional testing
dependencies: https://git.io/vyGO3
'''
"""
def __init__(self, **kwargs):
self.events = []
@ -52,14 +52,12 @@ class InMemoryClient(raven.Client):
def has_event(self, event_level, event_msg):
for event in self.events:
if (event.get('level') == event_level and
event.get('message') == event_msg):
if event.get("level") == event_level and event.get("message") == event_msg:
return True
return False
class TestClientSetup(unittest.TestCase):
def setUp(self):
super(TestClientSetup, self).setUp()
self.logger = logging.getLogger(__name__)
@ -68,57 +66,54 @@ class TestClientSetup(unittest.TestCase):
# when the module is loaded. After that, subsequent calls to
# setup_logging will not re-add our handler. We explicitly remove
# OdooSentryHandler handler so we can test with our in-memory client.
remove_logging_handler('', OdooSentryHandler)
remove_logging_handler("", OdooSentryHandler)
def assertEventCaptured(self, client, event_level, event_msg):
self.assertTrue(
client.has_event(event_level, event_msg),
msg='Event: "%s" was not captured' % event_msg
msg='Event: "%s" was not captured' % event_msg,
)
def assertEventNotCaptured(self, client, event_level, event_msg):
self.assertFalse(
client.has_event(event_level, event_msg),
msg='Event: "%s" was captured' % event_msg
msg='Event: "%s" was captured' % event_msg,
)
def test_initialize_raven_sets_dsn(self):
config = {
'sentry_enabled': True,
'sentry_dsn': 'http://public:secret@example.com/1',
"sentry_enabled": True,
"sentry_dsn": "http://public:secret@example.com/1",
}
client = initialize_raven(config, client_cls=InMemoryClient)
self.assertEqual(client.remote.base_url, 'http://example.com')
self.assertEqual(client.remote.base_url, "http://example.com")
def test_capture_event(self):
config = {
'sentry_enabled': True,
'sentry_dsn': 'http://public:secret@example.com/1',
"sentry_enabled": True,
"sentry_dsn": "http://public:secret@example.com/1",
}
level, msg = logging.WARNING, 'Test event, can be ignored'
level, msg = logging.WARNING, "Test event, can be ignored"
client = initialize_raven(config, client_cls=InMemoryClient)
self.logger.log(level, msg)
self.assertEventCaptured(client, level, msg)
def test_ignore_exceptions(self):
config = {
'sentry_enabled': True,
'sentry_dsn': 'http://public:secret@example.com/1',
'sentry_ignore_exceptions': 'odoo.exceptions.UserError',
"sentry_enabled": True,
"sentry_dsn": "http://public:secret@example.com/1",
"sentry_ignore_exceptions": "odoo.exceptions.UserError",
}
level, msg = logging.WARNING, 'Test UserError'
level, msg = logging.WARNING, "Test UserError"
client = initialize_raven(config, client_cls=InMemoryClient)
handlers = list(
log_handler_by_class(logging.getLogger(), OdooSentryHandler)
)
handlers = list(log_handler_by_class(logging.getLogger(), OdooSentryHandler))
self.assertTrue(handlers)
handler = handlers[0]
try:
raise exceptions.UserError(msg)
except exceptions.UserError:
exc_info = sys.exc_info()
record = logging.LogRecord(
__name__, level, __file__, 42, msg, (), exc_info)
record = logging.LogRecord(__name__, level, __file__, 42, msg, (), exc_info)
handler.emit(record)
self.assertEventNotCaptured(client, level, msg)

View File

@ -9,69 +9,62 @@ from ..logutils import SanitizeOdooCookiesProcessor
class TestOdooCookieSanitizer(unittest.TestCase):
def test_cookie_as_string(self):
data = {
'request': {
'cookies': 'website_lang=en_us;'
'session_id=hello;'
'Session_ID=hello;'
'foo=bar',
},
"request": {
"cookies": "website_lang=en_us;"
"session_id=hello;"
"Session_ID=hello;"
"foo=bar"
}
}
proc = SanitizeOdooCookiesProcessor(mock.Mock())
result = proc.process(data)
self.assertTrue('request' in result)
http = result['request']
self.assertTrue("request" in result)
http = result["request"]
self.assertEqual(
http['cookies'],
'website_lang=en_us;'
'session_id={m};'
'Session_ID={m};'
'foo=bar'.format(
m=proc.MASK,
),
http["cookies"],
"website_lang=en_us;"
"session_id={m};"
"Session_ID={m};"
"foo=bar".format(m=proc.MASK),
)
def test_cookie_as_string_with_partials(self):
data = {
'request': {
'cookies': 'website_lang=en_us;session_id;foo=bar',
},
}
data = {"request": {"cookies": "website_lang=en_us;session_id;foo=bar"}}
proc = SanitizeOdooCookiesProcessor(mock.Mock())
result = proc.process(data)
self.assertTrue('request' in result)
http = result['request']
self.assertTrue("request" in result)
http = result["request"]
self.assertEqual(
http['cookies'],
'website_lang=en_us;session_id;foo=bar'.format(m=proc.MASK),
http["cookies"], "website_lang=en_us;session_id;foo=bar".format(m=proc.MASK)
)
def test_cookie_header(self):
data = {
'request': {
'headers': {
'Cookie': 'foo=bar;'
'session_id=hello;'
'Session_ID=hello;'
'a_session_id_here=hello',
},
},
"request": {
"headers": {
"Cookie": "foo=bar;"
"session_id=hello;"
"Session_ID=hello;"
"a_session_id_here=hello"
}
}
}
proc = SanitizeOdooCookiesProcessor(mock.Mock())
result = proc.process(data)
self.assertTrue('request' in result)
http = result['request']
self.assertTrue("request" in result)
http = result["request"]
self.assertEqual(
http['headers']['Cookie'],
'foo=bar;'
'session_id={m};'
'Session_ID={m};'
'a_session_id_here={m}'.format(m=proc.MASK))
http["headers"]["Cookie"],
"foo=bar;"
"session_id={m};"
"Session_ID={m};"
"a_session_id_here={m}".format(m=proc.MASK),
)