From 6f059218559e0bcf2aa01401ac566a97f1abeb63 Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis Date: Thu, 29 Jun 2017 17:55:53 +0300 Subject: [PATCH 01/14] [ADD] sentry module (#761) * [ADD] sentry module * [FIX] updated sentry module according to PR comments --- sentry/README.rst | 168 +++++++++++++++++++++++++++++ sentry/__init__.py | 75 +++++++++++++ sentry/__manifest__.py | 24 +++++ sentry/const.py | 84 +++++++++++++++ sentry/logutils.py | 104 ++++++++++++++++++ sentry/static/description/icon.png | Bin 0 -> 2220 bytes sentry/tests/__init__.py | 8 ++ sentry/tests/test_client.py | 125 +++++++++++++++++++++ sentry/tests/test_logutils.py | 78 ++++++++++++++ 9 files changed, 666 insertions(+) create mode 100644 sentry/README.rst create mode 100644 sentry/__init__.py create mode 100644 sentry/__manifest__.py create mode 100644 sentry/const.py create mode 100644 sentry/logutils.py create mode 100644 sentry/static/description/icon.png create mode 100644 sentry/tests/__init__.py create mode 100644 sentry/tests/test_client.py create mode 100644 sentry/tests/test_logutils.py diff --git a/sentry/README.rst b/sentry/README.rst new file mode 100644 index 00000000000..4c41dfb0f6a --- /dev/null +++ b/sentry/README.rst @@ -0,0 +1,168 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +====== +Sentry +====== + +This module allows painless `Sentry `__ integration with +Odoo. + +Installation +============ + +The module can be installed just like any other Odoo module, by adding the +module's directory to Odoo *addons_path*. In order for the module to correctly +wrap the Odoo WSGI application, it also needs to be loaded as a server-wide +module. This can be done with the ``server_wide_modules`` parameter in your +Odoo config file or with the ``--load`` command-line parameter. + +This module additionally requires the raven_ Python package to be available on +the system. It can be installed using pip:: + + pip install raven + +Configuration +============= + +The following additional configuration options can be added to your Odoo +configuration file: + +============================= ==================================================================== ========================================================== + Option Description Default +============================= ==================================================================== ========================================================== +``sentry_dsn`` Sentry *Data Source Name*. You can find this value in your Sentry ``''`` + project configuration. Typically it looks something like this: + *https://:@sentry.example.com/* + This is the only required option in order to use the module. + +``sentry_enabled`` Whether or not Sentry logging is enabled. ``True`` + +``sentry_logging_level`` The minimal logging level for which to send reports to Sentry. ``warn`` + Possible values: *notset*, *debug*, *info*, *warn*, *error*, + *critical*. It is recommended to have this set to at least *warn*, + to avoid spamming yourself with Sentry events. + +``sentry_exclude_loggers`` A string of comma-separated logger names which should be excluded ``werkzeug`` + from Sentry. + +``sentry_ignored_exceptions`` A string of comma-separated exceptions which should be ignored. ``odoo.exceptions.AccessDenied, + You can use a star symbol (*) at the end, to ignore all exceptions odoo.exceptions.AccessError, + from a module, eg.: *odoo.exceptions.**. odoo.exceptions.DeferredException, + odoo.exceptions.MissingError, + odoo.exceptions.RedirectWarning, + odoo.exceptions.UserError, + odoo.exceptions.ValidationError, + odoo.exceptions.Warning, + odoo.exceptions.except_orm`` + +``sentry_processors`` A string of comma-separated processor classes which will be applied ``raven.processors.SanitizePasswordsProcessor, + on an event before sending it to Sentry. odoo.addons.sentry.logutils.SanitizeOdooCookiesProcessor`` + +``sentry_transport`` Transport class which will be used to send events to Sentry. ``threaded`` + Possible values: *threaded*: spawns an async worker for processing + messages, *synchronous*: a synchronous blocking transport; + *requests_threaded*: an asynchronous transport using the *requests* + library; *requests_synchronous* - blocking transport using the + *requests* library. + +``sentry_include_context`` If enabled, additional context data will be extracted from current ``True`` + HTTP request and user session (if available). This has no effect + for Cron jobs, as no request/session is available inside a Cron job. + +``sentry_odoo_dir`` Absolute path to your Odoo installation directory. This is optional + and will only be used to extract the Odoo Git commit, which will be + sent to Sentry, to allow to distinguish between Odoo updates. +============================= ==================================================================== ========================================================== + +Other `client arguments +`_ can be +configured by prepending the argument name with *sentry_* in your Odoo config +file. Currently supported additional client arguments are: ``install_sys_hook, +include_paths, exclude_paths, machine, auto_log_stacks, capture_locals, +string_max_length, list_max_length, site, include_versions, environment``. + +Example Odoo configuration +-------------------------- + +Below is an example of Odoo configuration file with *Odoo Sentry* options:: + + [options] + sentry_dsn = https://:@sentry.example.com/ + sentry_enabled = true + sentry_logging_level = warn + sentry_exclude_loggers = werkzeug + sentry_ignore_exceptions = odoo.exceptions.AccessDenied,odoo.exceptions.AccessError,odoo.exceptions.MissingError,odoo.exceptions.RedirectWarning,odoo.exceptions.UserError,odoo.exceptions.ValidationError,odoo.exceptions.Warning,odoo.exceptions.except_orm + sentry_processors = raven.processors.SanitizePasswordsProcessor,odoo.addons.sentry.logutils.SanitizeOdooCookiesProcessor + sentry_transport = threaded + sentry_include_context = true + sentry_environment = production + sentry_auto_log_stacks = false + sentry_odoo_dir = /home/odoo/odoo/ + +Usage +===== + +Once configured and installed, the module will report any logging event at and +above the configured Sentry logging level, no additional actions are necessary. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/149/10.0 + +Known issues / Roadmap +====================== + +* **No database separation** -- This module functions by intercepting all Odoo + logging records in a running Odoo process. This means that once installed in + one database, it will intercept and report errors for all Odoo databases, + which are used on that Odoo server. + +* **Frontend integration** -- In the future, it would be nice to add + Odoo client-side error reporting to this module as well, by integrating + `raven-js `_. Additionally, `Sentry user + feedback form `_ could be + integrated into the Odoo client error dialog window to allow users shortly + describe what they were doing when things went wrong. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* `Module Icon `_ + +Contributors +------------ + +* Mohammed Barsi +* Andrius Preimantas +* Naglis Jonaitis + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. + + +.. _raven: https://github.com/getsentry/raven-python diff --git a/sentry/__init__.py b/sentry/__init__.py new file mode 100644 index 00000000000..0d2f0a4c2f1 --- /dev/null +++ b/sentry/__init__.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Copyright 2016-2017 Versada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo.service import wsgi_server +from odoo.tools import config as odoo_config + +from . import const +from .logutils import LoggerNameFilter, OdooSentryHandler + +_logger = logging.getLogger(__name__) +try: + import raven + from raven.middleware import Sentry +except ImportError: + _logger.debug('Cannot import "raven". Please make sure it is installed.') + + +def get_odoo_commit(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( + u'Odoo directory: "%s" not a valid git repository', odoo_dir) + + +def initialize_raven(config, client_cls=raven.Client): + ''' + Setup an instance of :class:`raven.Client`. + + :param config: Sentry configuration + :param client: class used to instantiate the raven client. + ''' + options = { + 'release': get_odoo_commit(config.get('sentry_odoo_dir')), + } + for option in const.SENTRY_OPTIONS: + value = config.get('sentry_%s' % option.key, option.default) + if callable(option.converter): + value = option.converter(value) + options[option.key] = value + + client = client_cls(**options) + + enabled = config.get('sentry_enabled', True) + level = config.get('sentry_logging_level', const.DEFAULT_LOG_LEVEL) + exclude_loggers = const.split_multiple( + config.get('sentry_exclude_loggers', const.DEFAULT_EXCLUDE_LOGGERS) + ) + if level not in const.LOG_LEVEL_MAP: + level = const.DEFAULT_LOG_LEVEL + + if enabled: + handler = OdooSentryHandler( + 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')) + raven.conf.setup_logging(handler) + wsgi_server.application = Sentry( + wsgi_server.application, client=client) + + return client + + +sentry_client = initialize_raven(odoo_config) +sentry_client.captureMessage('Starting Odoo Server') diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py new file mode 100644 index 00000000000..1ef7acfdb8b --- /dev/null +++ b/sentry/__manifest__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Copyright 2016-2017 Versada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + 'name': 'Sentry', + 'summary': 'Report Odoo errors to Sentry', + 'version': '10.0.1.0.0', + 'category': 'Extra Tools', + 'website': 'https://odoo-community.org/', + 'author': 'Mohammed Barsi,' + 'Versada,' + 'Odoo Community Association (OCA)', + 'license': 'AGPL-3', + 'application': False, + 'installable': True, + 'external_dependencies': { + 'python': [ + 'raven', + ] + }, + 'depends': [ + 'base', + ], +} diff --git a/sentry/const.py b/sentry/const.py new file mode 100644 index 00000000000..26c14117056 --- /dev/null +++ b/sentry/const.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# Copyright 2016-2017 Versada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import collections +import logging + +import odoo.loglevels + +_logger = logging.getLogger(__name__) +try: + import raven + from raven.conf import defaults +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.''' + if not string: + return [] + return [v.strip(strip_chars) for v in string.split(delimiter)] + + +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' + +DEFAULT_TRANSPORT = 'threaded' +TRANSPORT_CLASS_MAP = { + 'requests_synchronous': raven.transport.RequestsHTTPTransport, + 'requests_threaded': raven.transport.ThreadedRequestsHTTPTransport, + 'synchronous': raven.transport.HTTPTransport, + 'threaded': raven.transport.ThreadedHTTPTransport, +} + +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', +] +DEFAULT_IGNORED_EXCEPTIONS = ','.join(ODOO_USER_EXCEPTIONS) + +PROCESSORS = ( + 'raven.processors.SanitizePasswordsProcessor', + 'odoo.addons.sentry.logutils.SanitizeOdooCookiesProcessor', +) +DEFAULT_PROCESSORS = ','.join(PROCESSORS) + +EXCLUDE_LOGGERS = ( + 'werkzeug', +) +DEFAULT_EXCLUDE_LOGGERS = ','.join(EXCLUDE_LOGGERS) + +SENTRY_OPTIONS = [ + SentryOption('dsn', '', str.strip), + SentryOption('install_sys_hook', False, None), + SentryOption('transport', DEFAULT_TRANSPORT, TRANSPORT_CLASS_MAP.get), + 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), +] diff --git a/sentry/logutils.py b/sentry/logutils.py new file mode 100644 index 00000000000..ad9f65efd5c --- /dev/null +++ b/sentry/logutils.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# Copyright 2016-2017 Versada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +import urlparse + +import odoo.http + +_logger = logging.getLogger(__name__) +try: + from raven.handlers.logging import SentryHandler + from raven.processors import SanitizePasswordsProcessor + from raven.utils.wsgi import get_environ, get_headers +except ImportError: + _logger.debug('Cannot import "raven". Please make sure it is installed.') + + +def get_request_info(request): + ''' + Returns context data extracted from :param:`request`. + + Heavily based on flask integration for Sentry: https://git.io/vP4i9. + ''' + urlparts = urlparse.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)), + } + + +def get_extra_context(): + ''' + Extracts additional context from the current request (if such is set). + ''' + request = odoo.http.request + try: + 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', {}), + }, + } + if 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=''): + super(LoggerNameFilter, self).__init__(name=name) + self._exclude_loggers = set(loggers) + + def filter(self, event): + return event.name not in self._exclude_loggers + + +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) + self.include_extra_context = include_extra_context + + def emit(self, record): + if self.include_extra_context: + self.client.context.merge(get_extra_context()) + return super(OdooSentryHandler, self).emit(record) + + +class SanitizeOdooCookiesProcessor(SanitizePasswordsProcessor): + ''' + Custom :class:`raven.processors.Processor`. + + Allows to sanitize sensitive Odoo cookies, namely the "session_id" cookie. + ''' + + FIELDS = frozenset([ + 'session_id', + ]) diff --git a/sentry/static/description/icon.png b/sentry/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..134c89f93b226aba995199d3e250758fdcd205c0 GIT binary patch literal 2220 zcmeH{`#aMM9LK-bu!gzLofdYo$685|l4E7rj$1B^BH|3EwoxvLvbl5^qBS&UhUv(| zCQ8UE#iFcGh>ScW_s&)*a@le8<9W`XaQZyY`}2NYpWi<3AKsbAJY3YEhEMGTg)U>@#M7ZoKK@@t|AFZ3+{x5rQ!I|hY zVckaes_@ri$-F1=xaLY1I8|#qCvBqoH!YSi>^xWBg=`A3W~)v{GX^Rk=(8A!Z;J6x zADOLiKIRO?&P%JRUpwa-KKjc>k}Z~LaqucciT!4LiSBugxdk;@U&wVWU%CM+iN`BW zGnSY?&)uBV!jTCn5K9K{qlP!8r@E|zIg^^$yg^UJj0kc9NPUU?hR|SBz#13+5plf-Ne66*8cSTpS02keGHOL#A$iIc^(#K!`Ph=rfbvDYHEIKy@W&O zh&ZD*1P>o>G_`0I{3-vXj$#yzUZk)GB=v8q2$bhoAQM*()%HyfmAEw1?6zYc&ov-JYU#aa`RjwZ+b8j$&#SLs-HN z3V=Q+AI0vGkdoc2*Mn>ihEpk8Bio5m$-aAsp!g?qEfqb=sZk8HW%T*61Ph&2uE&Vpn?lc#;$_0>Jf#ffwS$uS5iyq?+9K zS8-JTe8Q}J*6AfGd#v~IlYn~HE8AFc_Y^)PYQ5S4cVWA6xThC0SRwtl_cCP*wpqeU%~KbbcXoJsDuvGnmRoX_&u1>gKdw^uu^O`0970ktWXqXG z3yK4wSK;P13o zh!;29Ok~7)R$dXRk>$1OEx5(chQ?0e*~$boHb`e$WA@--O#}>6l{8;>eWfCB;i(FK zT(Pa|kpZ@3Tp5+-gmG5Vr0zVhvlWD=p}Od^Omv2GFS7cU zbb)$((0J6=quyx7;R$Nyf5kS*7g mG#phHHkRJ`Z%?&n)r>~us~29!g9w{73J`D}&NYWmr~U(OaGt;b literal 0 HcmV?d00001 diff --git a/sentry/tests/__init__.py b/sentry/tests/__init__.py new file mode 100644 index 00000000000..50cb7981405 --- /dev/null +++ b/sentry/tests/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Copyright 2016-2017 Versada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ( + test_client, + test_logutils, +) diff --git a/sentry/tests/test_client.py b/sentry/tests/test_client.py new file mode 100644 index 00000000000..64b952ec05c --- /dev/null +++ b/sentry/tests/test_client.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# Copyright 2016-2017 Versada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +import sys +import unittest + +import raven + +from odoo import exceptions + +from .. import initialize_raven +from ..logutils import OdooSentryHandler + + +def log_handler_by_class(logger, handler_cls): + for handler in logger.handlers: + if isinstance(handler, handler_cls): + yield handler + + +def remove_logging_handler(logger_name, handler_cls): + '''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. + + Extended based on the one found in raven-python to avoid additional testing + dependencies: https://git.io/vyGO3 + ''' + + def __init__(self, **kwargs): + self.events = [] + super(InMemoryClient, self).__init__(**kwargs) + + def is_enabled(self): + return True + + def send(self, **kwargs): + self.events.append(kwargs) + + 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): + return True + return False + + +class TestClientSetup(unittest.TestCase): + + def setUp(self): + super(TestClientSetup, self).setUp() + self.logger = logging.getLogger(__name__) + + # Sentry is enabled by default, so the default handler will be added + # 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) + + def assertEventCaptured(self, client, event_level, event_msg): + self.assertTrue( + client.has_event(event_level, event_msg), + msg=u'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=u'Event: "%s" was captured' % event_msg + ) + + def test_initialize_raven_sets_dsn(self): + config = { + 'sentry_enabled': False, + 'sentry_dsn': 'http://public:secret@example.com/1', + } + client = initialize_raven(config, client_cls=InMemoryClient) + 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', + } + 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', + } + level, msg = logging.WARNING, 'Test UserError' + client = initialize_raven(config, client_cls=InMemoryClient) + + 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) + handler.emit(record) + self.assertEventNotCaptured(client, level, msg) diff --git a/sentry/tests/test_logutils.py b/sentry/tests/test_logutils.py new file mode 100644 index 00000000000..b81b6916247 --- /dev/null +++ b/sentry/tests/test_logutils.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Copyright 2016-2017 Versada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import unittest + +import mock + +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', + }, + } + + proc = SanitizeOdooCookiesProcessor(mock.Mock()) + result = proc.process(data) + + 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, + ), + ) + + def test_cookie_as_string_with_partials(self): + 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.assertEqual( + 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', + }, + }, + } + + proc = SanitizeOdooCookiesProcessor(mock.Mock()) + result = proc.process(data) + + 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)) From 61022fef98c0d45d222bda77f3c591e917c9d55d Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis Date: Fri, 30 Jun 2017 11:25:27 +0300 Subject: [PATCH 02/14] [FIX] sentry: fixes missing `raven` library preventing loading of modules Related: #761 #879 #881 --- sentry/README.rst | 2 +- sentry/__init__.py | 39 +++++++++++++------------ sentry/const.py | 57 ++++++++++++++++++++----------------- sentry/logutils.py | 2 ++ sentry/tests/test_client.py | 2 +- 5 files changed, 56 insertions(+), 46 deletions(-) diff --git a/sentry/README.rst b/sentry/README.rst index 4c41dfb0f6a..f7f61e14dfe 100644 --- a/sentry/README.rst +++ b/sentry/README.rst @@ -37,7 +37,7 @@ configuration file: *https://:@sentry.example.com/* This is the only required option in order to use the module. -``sentry_enabled`` Whether or not Sentry logging is enabled. ``True`` +``sentry_enabled`` Whether or not Sentry logging is enabled. ``False`` ``sentry_logging_level`` The minimal logging level for which to send reports to Sentry. ``warn`` Possible values: *notset*, *debug*, *info*, *warn*, *error*, diff --git a/sentry/__init__.py b/sentry/__init__.py index 0d2f0a4c2f1..fe841dc4abc 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -11,10 +11,12 @@ from .logutils import LoggerNameFilter, OdooSentryHandler _logger = logging.getLogger(__name__) +HAS_RAVEN = True try: import raven from raven.middleware import Sentry except ImportError: + HAS_RAVEN = False _logger.debug('Cannot import "raven". Please make sure it is installed.') @@ -29,25 +31,25 @@ def get_odoo_commit(odoo_dir): u'Odoo directory: "%s" not a valid git repository', odoo_dir) -def initialize_raven(config, client_cls=raven.Client): +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) + if not (HAS_RAVEN and enabled): + return options = { 'release': get_odoo_commit(config.get('sentry_odoo_dir')), } - for option in const.SENTRY_OPTIONS: + for option in const.get_sentry_options(): value = config.get('sentry_%s' % option.key, option.default) if callable(option.converter): value = option.converter(value) options[option.key] = value - client = client_cls(**options) - - enabled = config.get('sentry_enabled', True) level = config.get('sentry_logging_level', const.DEFAULT_LOG_LEVEL) exclude_loggers = const.split_multiple( config.get('sentry_exclude_loggers', const.DEFAULT_EXCLUDE_LOGGERS) @@ -55,21 +57,22 @@ def initialize_raven(config, client_cls=raven.Client): if level not in const.LOG_LEVEL_MAP: level = const.DEFAULT_LOG_LEVEL - if enabled: - handler = OdooSentryHandler( - 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')) - raven.conf.setup_logging(handler) - wsgi_server.application = Sentry( - wsgi_server.application, client=client) + client_cls = client_cls or raven.Client + client = client_cls(**options) + handler = OdooSentryHandler( + 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')) + raven.conf.setup_logging(handler) + wsgi_server.application = Sentry( + wsgi_server.application, client=client) + client.captureMessage('Starting Odoo Server') return client sentry_client = initialize_raven(odoo_config) -sentry_client.captureMessage('Starting Odoo Server') diff --git a/sentry/const.py b/sentry/const.py index 26c14117056..5ceb3e2f2ac 100644 --- a/sentry/const.py +++ b/sentry/const.py @@ -32,14 +32,6 @@ def split_multiple(string, delimiter=',', strip_chars=None): ]) DEFAULT_LOG_LEVEL = 'warn' -DEFAULT_TRANSPORT = 'threaded' -TRANSPORT_CLASS_MAP = { - 'requests_synchronous': raven.transport.RequestsHTTPTransport, - 'requests_threaded': raven.transport.ThreadedRequestsHTTPTransport, - 'synchronous': raven.transport.HTTPTransport, - 'threaded': raven.transport.ThreadedHTTPTransport, -} - ODOO_USER_EXCEPTIONS = [ 'odoo.exceptions.AccessDenied', 'odoo.exceptions.AccessError', @@ -64,21 +56,34 @@ def split_multiple(string, delimiter=',', strip_chars=None): ) DEFAULT_EXCLUDE_LOGGERS = ','.join(EXCLUDE_LOGGERS) -SENTRY_OPTIONS = [ - SentryOption('dsn', '', str.strip), - SentryOption('install_sys_hook', False, None), - SentryOption('transport', DEFAULT_TRANSPORT, TRANSPORT_CLASS_MAP.get), - 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), -] +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, + }.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), + ] diff --git a/sentry/logutils.py b/sentry/logutils.py index ad9f65efd5c..178a518db8d 100644 --- a/sentry/logutils.py +++ b/sentry/logutils.py @@ -14,6 +14,8 @@ from raven.utils.wsgi import get_environ, get_headers except ImportError: _logger.debug('Cannot import "raven". Please make sure it is installed.') + SentryHandler = object + SanitizePasswordsProcessor = object def get_request_info(request): diff --git a/sentry/tests/test_client.py b/sentry/tests/test_client.py index 64b952ec05c..69bc1819f32 100644 --- a/sentry/tests/test_client.py +++ b/sentry/tests/test_client.py @@ -85,7 +85,7 @@ def assertEventNotCaptured(self, client, event_level, event_msg): def test_initialize_raven_sets_dsn(self): config = { - 'sentry_enabled': False, + 'sentry_enabled': True, 'sentry_dsn': 'http://public:secret@example.com/1', } client = initialize_raven(config, client_cls=InMemoryClient) From c449b2af569b14387964b3666d8fcff635c34eac Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis Date: Thu, 29 Jun 2017 17:55:53 +0300 Subject: [PATCH 03/14] [MIG] sentry to V11 - [FIX] sentry: fixes missing `raven` library preventing loading of modules - [FIX] 2to3 script on py file - [FIX] add requirements.txt --- sentry/__init__.py | 6 ++++-- sentry/__manifest__.py | 3 ++- sentry/logutils.py | 6 +++--- sentry/tests/test_client.py | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/sentry/__init__.py b/sentry/__init__.py index fe841dc4abc..8e3ce87d2e9 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -10,6 +10,8 @@ from . import const from .logutils import LoggerNameFilter, OdooSentryHandler +import collections + _logger = logging.getLogger(__name__) HAS_RAVEN = True try: @@ -28,7 +30,7 @@ def get_odoo_commit(odoo_dir): return raven.fetch_git_sha(odoo_dir) except raven.exceptions.InvalidGitRepository: _logger.debug( - u'Odoo directory: "%s" not a valid git repository', odoo_dir) + 'Odoo directory: "%s" not a valid git repository', odoo_dir) def initialize_raven(config, client_cls=None): @@ -46,7 +48,7 @@ def initialize_raven(config, client_cls=None): } for option in const.get_sentry_options(): value = config.get('sentry_%s' % option.key, option.default) - if callable(option.converter): + if isinstance(option.converter, collections.Callable): value = option.converter(value) options[option.key] = value diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 1ef7acfdb8b..288b880cbcc 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -4,11 +4,12 @@ { 'name': 'Sentry', 'summary': 'Report Odoo errors to Sentry', - 'version': '10.0.1.0.0', + 'version': '11.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, diff --git a/sentry/logutils.py b/sentry/logutils.py index 178a518db8d..1d3a69fca5f 100644 --- a/sentry/logutils.py +++ b/sentry/logutils.py @@ -3,7 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -import urlparse +import urllib.parse import odoo.http @@ -24,7 +24,7 @@ def get_request_info(request): Heavily based on flask integration for Sentry: https://git.io/vP4i9. ''' - urlparts = urlparse.urlsplit(request.url) + urlparts = urllib.parse.urlsplit(request.url) return { 'url': '%s://%s%s' % (urlparts.scheme, urlparts.netloc, urlparts.path), 'query_string': urlparts.query, @@ -101,6 +101,6 @@ class SanitizeOdooCookiesProcessor(SanitizePasswordsProcessor): Allows to sanitize sensitive Odoo cookies, namely the "session_id" cookie. ''' - FIELDS = frozenset([ + KEYS = frozenset([ 'session_id', ]) diff --git a/sentry/tests/test_client.py b/sentry/tests/test_client.py index 69bc1819f32..f75a0908964 100644 --- a/sentry/tests/test_client.py +++ b/sentry/tests/test_client.py @@ -74,13 +74,13 @@ def setUp(self): def assertEventCaptured(self, client, event_level, event_msg): self.assertTrue( client.has_event(event_level, event_msg), - msg=u'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=u'Event: "%s" was captured' % event_msg + msg='Event: "%s" was captured' % event_msg ) def test_initialize_raven_sets_dsn(self): From 11629f9f53d6778a5616b699b4178d8a15dcb335 Mon Sep 17 00:00:00 2001 From: Nicolas JEUDY Date: Fri, 5 Jan 2018 20:48:08 +0100 Subject: [PATCH 04/14] [FIX] Better manage KEYS - FIELDS conversion --- sentry/logutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/logutils.py b/sentry/logutils.py index 1d3a69fca5f..ae42af36564 100644 --- a/sentry/logutils.py +++ b/sentry/logutils.py @@ -101,6 +101,6 @@ class SanitizeOdooCookiesProcessor(SanitizePasswordsProcessor): Allows to sanitize sensitive Odoo cookies, namely the "session_id" cookie. ''' - KEYS = frozenset([ + KEYS = FIELDS = frozenset([ 'session_id', ]) From f1cf03f0b644b16136123f51e03f5066977ff059 Mon Sep 17 00:00:00 2001 From: Atte Isopuro Date: Fri, 12 Jan 2018 12:50:37 +0200 Subject: [PATCH 05/14] Enable setting a release option directly sentry: It is not always possible to read commit information from a production environment. In those cases it is useful to be able to set a release version manually. [UPD] Update sentry.pot --- sentry/README.rst | 9 +++++++++ sentry/__init__.py | 5 ++++- sentry/__manifest__.py | 2 +- sentry/const.py | 1 + sentry/i18n/sentry.pot | 14 ++++++++++++++ 5 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 sentry/i18n/sentry.pot diff --git a/sentry/README.rst b/sentry/README.rst index f7f61e14dfe..46adbbea8df 100644 --- a/sentry/README.rst +++ b/sentry/README.rst @@ -71,9 +71,16 @@ configuration file: HTTP request and user session (if available). This has no effect for Cron jobs, as no request/session is available inside a Cron job. +``sentry_release`` Explicitly define a version to be sent as the release version to + Sentry. Useful in conjuntion with Sentry's "Resolve in the next + release"-functionality. Also useful if your production deployment + does not include any Git context from which a commit might be read. + Overrides *sentry_odoo_dir*. + ``sentry_odoo_dir`` Absolute path to your Odoo installation directory. This is optional and will only be used to extract the Odoo Git commit, which will be sent to Sentry, to allow to distinguish between Odoo updates. + Overridden by *sentry_release* ============================= ==================================================================== ========================================================== Other `client arguments @@ -100,6 +107,7 @@ Below is an example of Odoo configuration file with *Odoo Sentry* options:: sentry_environment = production sentry_auto_log_stacks = false sentry_odoo_dir = /home/odoo/odoo/ + sentry_release = 1.3.2 Usage ===== @@ -148,6 +156,7 @@ Contributors * Mohammed Barsi * Andrius Preimantas * Naglis Jonaitis +* Atte Isopuro Maintainer ---------- diff --git a/sentry/__init__.py b/sentry/__init__.py index 8e3ce87d2e9..97da0a4b423 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -43,8 +43,11 @@ def initialize_raven(config, client_cls=None): 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') options = { - '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) diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 288b880cbcc..08a534483a7 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -4,7 +4,7 @@ { 'name': 'Sentry', 'summary': 'Report Odoo errors to Sentry', - 'version': '11.0.1.0.0', + 'version': '11.0.1.1.0', 'category': 'Extra Tools', 'website': 'https://odoo-community.org/', 'author': 'Mohammed Barsi,' diff --git a/sentry/const.py b/sentry/const.py index 5ceb3e2f2ac..b6cacf5f0c7 100644 --- a/sentry/const.py +++ b/sentry/const.py @@ -86,4 +86,5 @@ def get_sentry_options(): 'ignore_exceptions', DEFAULT_IGNORED_EXCEPTIONS, split_multiple), SentryOption('processors', DEFAULT_PROCESSORS, split_multiple), SentryOption('environment', None, None), + SentryOption('release', None, None), ] diff --git a/sentry/i18n/sentry.pot b/sentry/i18n/sentry.pot new file mode 100644 index 00000000000..447d3bb3ca4 --- /dev/null +++ b/sentry/i18n/sentry.pot @@ -0,0 +1,14 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + From 4dba152f19f737d1c9f974f69e54c75df2003c36 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 3 Sep 2018 10:00:27 +0200 Subject: [PATCH 06/14] Global pylint cleanup --- sentry/__init__.py | 1 - sentry/__manifest__.py | 1 - sentry/const.py | 1 - sentry/logutils.py | 1 - sentry/tests/__init__.py | 1 - sentry/tests/test_client.py | 1 - sentry/tests/test_logutils.py | 1 - 7 files changed, 7 deletions(-) diff --git a/sentry/__init__.py b/sentry/__init__.py index 97da0a4b423..968f6ae3131 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 08a534483a7..ea73d423dee 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { diff --git a/sentry/const.py b/sentry/const.py index b6cacf5f0c7..0092d75447b 100644 --- a/sentry/const.py +++ b/sentry/const.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/sentry/logutils.py b/sentry/logutils.py index ae42af36564..cef132bd715 100644 --- a/sentry/logutils.py +++ b/sentry/logutils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/sentry/tests/__init__.py b/sentry/tests/__init__.py index 50cb7981405..6955e47d58c 100644 --- a/sentry/tests/__init__.py +++ b/sentry/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/sentry/tests/test_client.py b/sentry/tests/test_client.py index f75a0908964..aafe9d0e7be 100644 --- a/sentry/tests/test_client.py +++ b/sentry/tests/test_client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). diff --git a/sentry/tests/test_logutils.py b/sentry/tests/test_logutils.py index b81b6916247..bcabdcb6ae3 100644 --- a/sentry/tests/test_logutils.py +++ b/sentry/tests/test_logutils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). From 1534a7feeaae7488b501e54cb4bc19801ad30e22 Mon Sep 17 00:00:00 2001 From: jeffery chen fan Date: Sat, 3 Nov 2018 14:53:34 +0800 Subject: [PATCH 07/14] migrate to 12.0 [UPD] Update sentry.pot --- sentry/__manifest__.py | 2 +- sentry/i18n/sentry.pot | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index ea73d423dee..d4471b69e74 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -3,7 +3,7 @@ { 'name': 'Sentry', 'summary': 'Report Odoo errors to Sentry', - 'version': '11.0.1.1.0', + 'version': '12.0.1.0.0', 'category': 'Extra Tools', 'website': 'https://odoo-community.org/', 'author': 'Mohammed Barsi,' diff --git a/sentry/i18n/sentry.pot b/sentry/i18n/sentry.pot index 447d3bb3ca4..d2e396f159e 100644 --- a/sentry/i18n/sentry.pot +++ b/sentry/i18n/sentry.pot @@ -3,7 +3,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 11.0\n" +"Project-Id-Version: Odoo Server 12.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: <>\n" "Language-Team: \n" From cfc263e2aa751d47d3e3a850d5f357c32976e325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=8E=E4=BC=9F=E6=9D=B0?= <674416404@qq.com> Date: Sat, 31 Aug 2019 07:02:35 +0000 Subject: [PATCH 08/14] Added translation using Weblate (Chinese (Simplified)) --- sentry/i18n/zh_CN.po | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 sentry/i18n/zh_CN.po diff --git a/sentry/i18n/zh_CN.po b/sentry/i18n/zh_CN.po new file mode 100644 index 00000000000..4159be77e05 --- /dev/null +++ b/sentry/i18n/zh_CN.po @@ -0,0 +1,14 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=1; plural=0;\n" From 57e2c85b3ab9ceb528752cb329a7bd3e501b4100 Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 28 Feb 2020 02:07:12 +0500 Subject: [PATCH 09/14] [IMP] sentry: black, isort --- sentry/__init__.py | 39 ++++++++------- sentry/__manifest__.py | 34 ++++++------- sentry/const.py | 92 +++++++++++++++++------------------ sentry/logutils.py | 56 +++++++++------------ sentry/tests/__init__.py | 5 +- sentry/tests/test_client.py | 45 ++++++++--------- sentry/tests/test_logutils.py | 73 +++++++++++++-------------- 7 files changed, 157 insertions(+), 187 deletions(-) diff --git a/sentry/__init__.py b/sentry/__init__.py index 968f6ae3131..ccee682e4f6 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -22,41 +22,44 @@ 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 diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index d4471b69e74..65048bc8bef 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -1,24 +1,18 @@ # Copyright 2016-2017 Versada # 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"], } diff --git a/sentry/const.py b/sentry/const.py index 0092d75447b..8ea20aebb4c 100644 --- a/sentry/const.py +++ b/sentry/const.py @@ -14,76 +14,72 @@ _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), ] diff --git a/sentry/logutils.py b/sentry/logutils.py index cef132bd715..93709c9ce4f 100644 --- a/sentry/logutils.py +++ b/sentry/logutils.py @@ -18,56 +18,50 @@ 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 @@ def filter(self, event): 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 @@ def emit(self, record): 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"]) diff --git a/sentry/tests/__init__.py b/sentry/tests/__init__.py index 6955e47d58c..21926387ae5 100644 --- a/sentry/tests/__init__.py +++ b/sentry/tests/__init__.py @@ -1,7 +1,4 @@ # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import ( - test_client, - test_logutils, -) +from . import test_client, test_logutils diff --git a/sentry/tests/test_client.py b/sentry/tests/test_client.py index aafe9d0e7be..eb68c71378d 100644 --- a/sentry/tests/test_client.py +++ b/sentry/tests/test_client.py @@ -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 @@ def send(self, **kwargs): 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 @@ def setUp(self): # 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) diff --git a/sentry/tests/test_logutils.py b/sentry/tests/test_logutils.py index bcabdcb6ae3..1dd20e2de0a 100644 --- a/sentry/tests/test_logutils.py +++ b/sentry/tests/test_logutils.py @@ -9,69 +9,62 @@ 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), + ) From 7af117f55c1d35e6ed7e7eb1a2d007c14fe8ee02 Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 28 Feb 2020 02:07:35 +0500 Subject: [PATCH 10/14] [MIG] sentry: Migration to 13.0 --- sentry/__manifest__.py | 2 +- sentry/readme/CONFIGURE.rst | 82 ++++++++++++++++++++++++++++++++++ sentry/readme/CONTRIBUTORS.rst | 4 ++ sentry/readme/DESCRIPTION.rst | 2 + sentry/readme/ROADMAP.rst | 11 +++++ sentry/readme/USAGE.rst | 6 +++ 6 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 sentry/readme/CONFIGURE.rst create mode 100644 sentry/readme/CONTRIBUTORS.rst create mode 100644 sentry/readme/DESCRIPTION.rst create mode 100644 sentry/readme/ROADMAP.rst create mode 100644 sentry/readme/USAGE.rst diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 65048bc8bef..304d6c37151 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Sentry", "summary": "Report Odoo errors to Sentry", - "version": "12.0.1.0.0", + "version": "13.0.1.0.0", "category": "Extra Tools", "website": "https://odoo-community.org/", "author": "Mohammed Barsi," diff --git a/sentry/readme/CONFIGURE.rst b/sentry/readme/CONFIGURE.rst new file mode 100644 index 00000000000..b25069628be --- /dev/null +++ b/sentry/readme/CONFIGURE.rst @@ -0,0 +1,82 @@ +The following additional configuration options can be added to your Odoo +configuration file: + +============================= ==================================================================== ========================================================== + Option Description Default +============================= ==================================================================== ========================================================== +``sentry_dsn`` Sentry *Data Source Name*. You can find this value in your Sentry ``''`` + project configuration. Typically it looks something like this: + *https://:@sentry.example.com/* + This is the only required option in order to use the module. + +``sentry_enabled`` Whether or not Sentry logging is enabled. ``False`` + +``sentry_logging_level`` The minimal logging level for which to send reports to Sentry. ``warn`` + Possible values: *notset*, *debug*, *info*, *warn*, *error*, + *critical*. It is recommended to have this set to at least *warn*, + to avoid spamming yourself with Sentry events. + +``sentry_exclude_loggers`` A string of comma-separated logger names which should be excluded ``werkzeug`` + from Sentry. + +``sentry_ignored_exceptions`` A string of comma-separated exceptions which should be ignored. ``odoo.exceptions.AccessDenied, + You can use a star symbol (*) at the end, to ignore all exceptions odoo.exceptions.AccessError, + from a module, eg.: *odoo.exceptions.**. odoo.exceptions.DeferredException, + odoo.exceptions.MissingError, + odoo.exceptions.RedirectWarning, + odoo.exceptions.UserError, + odoo.exceptions.ValidationError, + odoo.exceptions.Warning, + odoo.exceptions.except_orm`` + +``sentry_processors`` A string of comma-separated processor classes which will be applied ``raven.processors.SanitizePasswordsProcessor, + on an event before sending it to Sentry. odoo.addons.sentry.logutils.SanitizeOdooCookiesProcessor`` + +``sentry_transport`` Transport class which will be used to send events to Sentry. ``threaded`` + Possible values: *threaded*: spawns an async worker for processing + messages, *synchronous*: a synchronous blocking transport; + *requests_threaded*: an asynchronous transport using the *requests* + library; *requests_synchronous* - blocking transport using the + *requests* library. + +``sentry_include_context`` If enabled, additional context data will be extracted from current ``True`` + HTTP request and user session (if available). This has no effect + for Cron jobs, as no request/session is available inside a Cron job. + +``sentry_release`` Explicitly define a version to be sent as the release version to + Sentry. Useful in conjuntion with Sentry's "Resolve in the next + release"-functionality. Also useful if your production deployment + does not include any Git context from which a commit might be read. + Overrides *sentry_odoo_dir*. + +``sentry_odoo_dir`` Absolute path to your Odoo installation directory. This is optional + and will only be used to extract the Odoo Git commit, which will be + sent to Sentry, to allow to distinguish between Odoo updates. + Overridden by *sentry_release* +============================= ==================================================================== ========================================================== + +Other `client arguments +`_ can be +configured by prepending the argument name with *sentry_* in your Odoo config +file. Currently supported additional client arguments are: ``install_sys_hook, +include_paths, exclude_paths, machine, auto_log_stacks, capture_locals, +string_max_length, list_max_length, site, include_versions, environment``. + +Example Odoo configuration +-------------------------- + +Below is an example of Odoo configuration file with *Odoo Sentry* options:: + + [options] + sentry_dsn = https://:@sentry.example.com/ + sentry_enabled = true + sentry_logging_level = warn + sentry_exclude_loggers = werkzeug + sentry_ignore_exceptions = odoo.exceptions.AccessDenied,odoo.exceptions.AccessError,odoo.exceptions.MissingError,odoo.exceptions.RedirectWarning,odoo.exceptions.UserError,odoo.exceptions.ValidationError,odoo.exceptions.Warning,odoo.exceptions.except_orm + sentry_processors = raven.processors.SanitizePasswordsProcessor,odoo.addons.sentry.logutils.SanitizeOdooCookiesProcessor + sentry_transport = threaded + sentry_include_context = true + sentry_environment = production + sentry_auto_log_stacks = false + sentry_odoo_dir = /home/odoo/odoo/ + sentry_release = 1.3.2 diff --git a/sentry/readme/CONTRIBUTORS.rst b/sentry/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000000..059d6b3a6ab --- /dev/null +++ b/sentry/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Mohammed Barsi +* Andrius Preimantas +* Naglis Jonaitis +* Atte Isopuro diff --git a/sentry/readme/DESCRIPTION.rst b/sentry/readme/DESCRIPTION.rst new file mode 100644 index 00000000000..531a0507996 --- /dev/null +++ b/sentry/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module allows painless `Sentry `__ integration with +Odoo. diff --git a/sentry/readme/ROADMAP.rst b/sentry/readme/ROADMAP.rst new file mode 100644 index 00000000000..92e100a5bd9 --- /dev/null +++ b/sentry/readme/ROADMAP.rst @@ -0,0 +1,11 @@ +* **No database separation** -- This module functions by intercepting all Odoo + logging records in a running Odoo process. This means that once installed in + one database, it will intercept and report errors for all Odoo databases, + which are used on that Odoo server. + +* **Frontend integration** -- In the future, it would be nice to add + Odoo client-side error reporting to this module as well, by integrating + `raven-js `_. Additionally, `Sentry user + feedback form `_ could be + integrated into the Odoo client error dialog window to allow users shortly + describe what they were doing when things went wrong. diff --git a/sentry/readme/USAGE.rst b/sentry/readme/USAGE.rst new file mode 100644 index 00000000000..6656ddeb507 --- /dev/null +++ b/sentry/readme/USAGE.rst @@ -0,0 +1,6 @@ +Once configured and installed, the module will report any logging event at and +above the configured Sentry logging level, no additional actions are necessary. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/149/13.0 From 25c3db9325e06c17a389c0b31a92bc8177bc2b11 Mon Sep 17 00:00:00 2001 From: "Moises Lopez - https://www.vauxoo.com/" Date: Mon, 8 Jun 2020 13:18:48 -0500 Subject: [PATCH 11/14] [REF] sentry: README.rst using gen_addon_readme.py locally because https://github.com/OCA/maintainer-tools/issues/459 [UPD] Update sentry.pot --- sentry/README.rst | 75 +++++++++++++++++++++++++----------------- sentry/i18n/sentry.pot | 5 ++- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/sentry/README.rst b/sentry/README.rst index 46adbbea8df..586b8b70b04 100644 --- a/sentry/README.rst +++ b/sentry/README.rst @@ -1,27 +1,37 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html - :alt: License: AGPL-3 - ====== Sentry ====== +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github + :target: https://github.com/OCA/server-tools/tree/13.0/sentry + :alt: OCA/server-tools +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/server-tools-13-0/server-tools-13-0-sentry + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/149/13.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + This module allows painless `Sentry `__ integration with Odoo. -Installation -============ - -The module can be installed just like any other Odoo module, by adding the -module's directory to Odoo *addons_path*. In order for the module to correctly -wrap the Odoo WSGI application, it also needs to be loaded as a server-wide -module. This can be done with the ``server_wide_modules`` parameter in your -Odoo config file or with the ``--load`` command-line parameter. +**Table of contents** -This module additionally requires the raven_ Python package to be available on -the system. It can be installed using pip:: - - pip install raven +.. contents:: + :local: Configuration ============= @@ -117,7 +127,7 @@ above the configured Sentry logging level, no additional actions are necessary. .. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas :alt: Try me on Runbot - :target: https://runbot.odoo-community.org/runbot/149/10.0 + :target: https://runbot.odoo-community.org/runbot/149/13.0 Known issues / Roadmap ====================== @@ -137,18 +147,22 @@ Known issues / Roadmap Bug Tracker =========== -Bugs are tracked on `GitHub Issues -`_. In case of trouble, please -check there if your issue has already been reported. If you spotted it first, -help us smash it by providing detailed and welcomed feedback. +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. Credits ======= -Images ------- +Authors +------- -* `Module Icon `_ +* Mohammed Barsi +* Versada +* Nicolas JEUDY Contributors ------------ @@ -158,20 +172,19 @@ Contributors * Naglis Jonaitis * Atte Isopuro -Maintainer ----------- +Maintainers +----------- + +This module is maintained by the OCA. .. image:: https://odoo-community.org/logo.png :alt: Odoo Community Association :target: https://odoo-community.org -This module is maintained by the OCA. - OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -To contribute to this module, please visit https://odoo-community.org. - +This module is part of the `OCA/server-tools `_ project on GitHub. -.. _raven: https://github.com/getsentry/raven-python +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/sentry/i18n/sentry.pot b/sentry/i18n/sentry.pot index d2e396f159e..cc93d01ee54 100644 --- a/sentry/i18n/sentry.pot +++ b/sentry/i18n/sentry.pot @@ -3,12 +3,11 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 12.0\n" +"Project-Id-Version: Odoo Server 13.0\n" "Report-Msgid-Bugs-To: \n" -"Last-Translator: <>\n" +"Last-Translator: \n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: \n" - From 2b7c99c7c10a142cbf506c3cb3f31fcc223a6b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20L=C3=B3pez?= Date: Mon, 8 Jun 2020 13:36:44 -0500 Subject: [PATCH 12/14] [REF] sentry: Fix sentry title level fix https://github.com/OCA/maintainer-tools/issues/459 https://github.com/OCA/server-tools/pull/1776#discussion_r436904679 Credits sbidoul [UPD] README.rst --- sentry/README.rst | 8 +- sentry/readme/CONFIGURE.rst | 2 +- sentry/static/description/index.html | 569 +++++++++++++++++++++++++++ 3 files changed, 574 insertions(+), 5 deletions(-) create mode 100644 sentry/static/description/index.html diff --git a/sentry/README.rst b/sentry/README.rst index 586b8b70b04..3e5025488be 100644 --- a/sentry/README.rst +++ b/sentry/README.rst @@ -101,7 +101,7 @@ include_paths, exclude_paths, machine, auto_log_stacks, capture_locals, string_max_length, list_max_length, site, include_versions, environment``. Example Odoo configuration --------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~ Below is an example of Odoo configuration file with *Odoo Sentry* options:: @@ -158,14 +158,14 @@ Credits ======= Authors -------- +~~~~~~~ * Mohammed Barsi * Versada * Nicolas JEUDY Contributors ------------- +~~~~~~~~~~~~ * Mohammed Barsi * Andrius Preimantas @@ -173,7 +173,7 @@ Contributors * Atte Isopuro Maintainers ------------ +~~~~~~~~~~~ This module is maintained by the OCA. diff --git a/sentry/readme/CONFIGURE.rst b/sentry/readme/CONFIGURE.rst index b25069628be..ac71b087600 100644 --- a/sentry/readme/CONFIGURE.rst +++ b/sentry/readme/CONFIGURE.rst @@ -63,7 +63,7 @@ include_paths, exclude_paths, machine, auto_log_stacks, capture_locals, string_max_length, list_max_length, site, include_versions, environment``. Example Odoo configuration --------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~ Below is an example of Odoo configuration file with *Odoo Sentry* options:: diff --git a/sentry/static/description/index.html b/sentry/static/description/index.html new file mode 100644 index 00000000000..e191be25548 --- /dev/null +++ b/sentry/static/description/index.html @@ -0,0 +1,569 @@ + + + + + + +Sentry + + + +
+

Sentry

+ + +

Beta License: AGPL-3 OCA/server-tools Translate me on Weblate Try me on Runbot

+

This module allows painless Sentry integration with +Odoo.

+

Table of contents

+ +
+

Configuration

+

The following additional configuration options can be added to your Odoo +configuration file:

+ +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescriptionDefault
sentry_dsnSentry Data Source Name. You can find this value in your Sentry +project configuration. Typically it looks something like this: +https://<public_key>:<secret_key>@sentry.example.com/<project id> +This is the only required option in order to use the module.''
sentry_enabledWhether or not Sentry logging is enabled.False
sentry_logging_levelThe minimal logging level for which to send reports to Sentry. +Possible values: notset, debug, info, warn, error, +critical. It is recommended to have this set to at least warn, +to avoid spamming yourself with Sentry events.warn
sentry_exclude_loggersA string of comma-separated logger names which should be excluded +from Sentry.werkzeug
sentry_ignored_exceptionsA string of comma-separated exceptions which should be ignored. +You can use a star symbol (*) at the end, to ignore all exceptions +from a module, eg.: odoo.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
sentry_processorsA string of comma-separated processor classes which will be applied +on an event before sending it to Sentry.raven.processors.SanitizePasswordsProcessor, +odoo.addons.sentry.logutils.SanitizeOdooCookiesProcessor
sentry_transportTransport class which will be used to send events to Sentry. +Possible values: threaded: spawns an async worker for processing +messages, synchronous: a synchronous blocking transport; +requests_threaded: an asynchronous transport using the requests +library; requests_synchronous - blocking transport using the +requests library.threaded
sentry_include_contextIf enabled, additional context data will be extracted from current +HTTP request and user session (if available). This has no effect +for Cron jobs, as no request/session is available inside a Cron job.True
sentry_releaseExplicitly define a version to be sent as the release version to +Sentry. Useful in conjuntion with Sentry’s “Resolve in the next +release”-functionality. Also useful if your production deployment +does not include any Git context from which a commit might be read. +Overrides sentry_odoo_dir. 
sentry_odoo_dirAbsolute path to your Odoo installation directory. This is optional +and will only be used to extract the Odoo Git commit, which will be +sent to Sentry, to allow to distinguish between Odoo updates. +Overridden by sentry_release 
+

Other client arguments can be +configured by prepending the argument name with sentry_ in your Odoo config +file. Currently supported additional client arguments are: install_sys_hook, +include_paths, exclude_paths, machine, auto_log_stacks, capture_locals, +string_max_length, list_max_length, site, include_versions, environment.

+
+

Example Odoo configuration

+

Below is an example of Odoo configuration file with Odoo Sentry options:

+
+[options]
+sentry_dsn = https://<public_key>:<secret_key>@sentry.example.com/<project id>
+sentry_enabled = true
+sentry_logging_level = warn
+sentry_exclude_loggers = werkzeug
+sentry_ignore_exceptions = odoo.exceptions.AccessDenied,odoo.exceptions.AccessError,odoo.exceptions.MissingError,odoo.exceptions.RedirectWarning,odoo.exceptions.UserError,odoo.exceptions.ValidationError,odoo.exceptions.Warning,odoo.exceptions.except_orm
+sentry_processors = raven.processors.SanitizePasswordsProcessor,odoo.addons.sentry.logutils.SanitizeOdooCookiesProcessor
+sentry_transport = threaded
+sentry_include_context = true
+sentry_environment = production
+sentry_auto_log_stacks = false
+sentry_odoo_dir = /home/odoo/odoo/
+sentry_release = 1.3.2
+
+
+
+
+

Usage

+

Once configured and installed, the module will report any logging event at and +above the configured Sentry logging level, no additional actions are necessary.

+Try me on Runbot +
+
+

Known issues / Roadmap

+
    +
  • No database separation – This module functions by intercepting all Odoo +logging records in a running Odoo process. This means that once installed in +one database, it will intercept and report errors for all Odoo databases, +which are used on that Odoo server.
  • +
  • Frontend integration – In the future, it would be nice to add +Odoo client-side error reporting to this module as well, by integrating +raven-js. Additionally, Sentry user +feedback form could be +integrated into the Odoo client error dialog window to allow users shortly +describe what they were doing when things went wrong.
  • +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Mohammed Barsi
  • +
  • Versada
  • +
  • Nicolas JEUDY
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/server-tools project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + From 931008e0a811ef22fc2a6e9dbc597bea4c2bee53 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 8 Jun 2020 18:48:12 +0000 Subject: [PATCH 13/14] sentry 13.0.1.1.0 --- sentry/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 304d6c37151..2857fcce2c2 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Sentry", "summary": "Report Odoo errors to Sentry", - "version": "13.0.1.0.0", + "version": "13.0.1.1.0", "category": "Extra Tools", "website": "https://odoo-community.org/", "author": "Mohammed Barsi," From 8c31d5f1c32a92b998277c7018c31de799eae59c Mon Sep 17 00:00:00 2001 From: Travis Waelbroeck Date: Mon, 16 Nov 2020 23:14:16 -0600 Subject: [PATCH 14/14] [MIG] sentry: Migration to 14.0 --- .pre-commit-config.yaml | 16 ++++++++-------- requirements.txt | 2 ++ sentry/__manifest__.py | 4 ++-- sentry/tests/test_logutils.py | 3 ++- setup/sentry/odoo/addons/sentry | 1 + setup/sentry/setup.py | 6 ++++++ 6 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 requirements.txt create mode 120000 setup/sentry/odoo/addons/sentry create mode 100644 setup/sentry/setup.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 17b26890165..73fdd58c02d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: language: fail files: "\\.rej$" - repo: https://github.com/oca/maintainer-tools - rev: 1b5c7ad + rev: 0290f3971ed057ff868731ddac36a28cf4b1bc97 hooks: # update the NOT INSTALLABLE ADDONS section above - id: oca-update-pre-commit-excluded-addons @@ -41,7 +41,7 @@ repos: rev: 20.8b1 hooks: - id: black - - repo: https://github.com/prettier/pre-commit + - repo: https://github.com/pre-commit/mirrors-prettier rev: v2.1.2 hooks: - id: prettier @@ -52,7 +52,7 @@ repos: args: - --plugin=@prettier/plugin-xml - repo: https://github.com/pre-commit/mirrors-eslint - rev: v7.8.1 + rev: v7.13.0-1 hooks: - id: eslint verbose: true @@ -60,7 +60,7 @@ repos: - --color - --fix - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v3.3.0 hooks: - id: trailing-whitespace # exclude autogenerated files @@ -82,11 +82,11 @@ repos: - id: mixed-line-ending args: ["--fix=lf"] - repo: https://github.com/asottile/pyupgrade - rev: v2.7.2 + rev: v2.7.4 hooks: - id: pyupgrade - repo: https://github.com/PyCQA/isort - rev: 5.5.1 + rev: 5.6.4 hooks: - id: isort name: isort except __init__.py @@ -104,7 +104,7 @@ repos: - --header - "# generated from manifests external_dependencies" - repo: https://gitlab.com/PyCQA/flake8 - rev: 3.8.3 + rev: 3.8.4 hooks: - id: flake8 name: flake8 except __init__.py @@ -116,7 +116,7 @@ repos: files: /__init__\.py$ additional_dependencies: ["flake8-bugbear==20.1.4"] - repo: https://github.com/PyCQA/pylint - rev: pylint-2.5.3 + rev: pylint-2.6.0 hooks: - id: pylint name: pylint with optional checks diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000000..4b24aaf37cb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +# generated from manifests external_dependencies +raven diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 2857fcce2c2..90582d46ade 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -3,9 +3,9 @@ { "name": "Sentry", "summary": "Report Odoo errors to Sentry", - "version": "13.0.1.1.0", + "version": "14.0.1.0.0", "category": "Extra Tools", - "website": "https://odoo-community.org/", + "website": "https://github.com/OCA/server-tools", "author": "Mohammed Barsi," "Versada," "Nicolas JEUDY," diff --git a/sentry/tests/test_logutils.py b/sentry/tests/test_logutils.py index 1dd20e2de0a..16caf90a835 100644 --- a/sentry/tests/test_logutils.py +++ b/sentry/tests/test_logutils.py @@ -41,7 +41,8 @@ def test_cookie_as_string_with_partials(self): 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={m};foo=bar".format(m=proc.MASK), ) def test_cookie_header(self): diff --git a/setup/sentry/odoo/addons/sentry b/setup/sentry/odoo/addons/sentry new file mode 120000 index 00000000000..40ffa7e91a0 --- /dev/null +++ b/setup/sentry/odoo/addons/sentry @@ -0,0 +1 @@ +../../../../sentry \ No newline at end of file diff --git a/setup/sentry/setup.py b/setup/sentry/setup.py new file mode 100644 index 00000000000..28c57bb6403 --- /dev/null +++ b/setup/sentry/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)