From 3510909ba2a96dc2f12ceb88b917ca1a992ce133 Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis Date: Thu, 29 Jun 2017 17:55:53 +0300 Subject: [PATCH 01/36] [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 000000000..4c41dfb0f --- /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 000000000..0d2f0a4c2 --- /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 000000000..1ef7acfdb --- /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 000000000..26c141170 --- /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 000000000..ad9f65efd --- /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 000000000..50cb79814 --- /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 000000000..64b952ec0 --- /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 000000000..b81b69162 --- /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 953f75103832c5d7136c043fb4442a4a7670497b Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis Date: Fri, 30 Jun 2017 11:25:27 +0300 Subject: [PATCH 02/36] [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 4c41dfb0f..f7f61e14d 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 0d2f0a4c2..fe841dc4a 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -11,10 +11,12 @@ from . import const 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 26c141170..5ceb3e2f2 100644 --- a/sentry/const.py +++ b/sentry/const.py @@ -32,14 +32,6 @@ LOG_LEVEL_MAP = dict([ ]) 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 @@ EXCLUDE_LOGGERS = ( ) 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 ad9f65efd..178a518db 100644 --- a/sentry/logutils.py +++ b/sentry/logutils.py @@ -14,6 +14,8 @@ try: 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 64b952ec0..69bc1819f 100644 --- a/sentry/tests/test_client.py +++ b/sentry/tests/test_client.py @@ -85,7 +85,7 @@ class TestClientSetup(unittest.TestCase): 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 dc7ce73744f0cd173c4abeca8b0dd0e9a699c4c1 Mon Sep 17 00:00:00 2001 From: Naglis Jonaitis Date: Thu, 29 Jun 2017 17:55:53 +0300 Subject: [PATCH 03/36] [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 fe841dc4a..8e3ce87d2 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -10,6 +10,8 @@ from odoo.tools import config as odoo_config 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 1ef7acfdb..288b880cb 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 178a518db..1d3a69fca 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 69bc1819f..f75a09089 100644 --- a/sentry/tests/test_client.py +++ b/sentry/tests/test_client.py @@ -74,13 +74,13 @@ class TestClientSetup(unittest.TestCase): 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 44d39be74095679fd1fe898c406a7dcf3f6efab9 Mon Sep 17 00:00:00 2001 From: Nicolas JEUDY Date: Fri, 5 Jan 2018 20:48:08 +0100 Subject: [PATCH 04/36] [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 1d3a69fca..ae42af365 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 bcdfca304b7e5ec58e5eaa16de09a18acba18bae Mon Sep 17 00:00:00 2001 From: Atte Isopuro Date: Fri, 12 Jan 2018 12:50:37 +0200 Subject: [PATCH 05/36] 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 f7f61e14d..46adbbea8 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 8e3ce87d2..97da0a4b4 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 288b880cb..08a534483 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 5ceb3e2f2..b6cacf5f0 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 000000000..447d3bb3c --- /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 0a307b16bdb89c950b61d172c904f3ef564d53c4 Mon Sep 17 00:00:00 2001 From: Simone Orsi Date: Mon, 3 Sep 2018 10:00:27 +0200 Subject: [PATCH 06/36] 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 97da0a4b4..968f6ae31 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 08a534483..ea73d423d 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 b6cacf5f0..0092d7544 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 ae42af365..cef132bd7 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 50cb79814..6955e47d5 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 f75a09089..aafe9d0e7 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 b81b69162..bcabdcb6a 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 6824e60500c1ce28a6963ad6d9f89067d498c8e4 Mon Sep 17 00:00:00 2001 From: jeffery chen fan Date: Sat, 3 Nov 2018 14:53:34 +0800 Subject: [PATCH 07/36] 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 ea73d423d..d4471b69e 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 447d3bb3c..d2e396f15 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 165715b747e4ba45a5ef6fdc103d4989a1ee797b 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/36] 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 000000000..4159be77e --- /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 532bdbdbfe3371381541e016e2be75ee33388be3 Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 28 Feb 2020 02:07:12 +0500 Subject: [PATCH 09/36] [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 968f6ae31..ccee682e4 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -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 diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index d4471b69e..65048bc8b 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 0092d7544..8ea20aebb 100644 --- a/sentry/const.py +++ b/sentry/const.py @@ -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), ] diff --git a/sentry/logutils.py b/sentry/logutils.py index cef132bd7..93709c9ce 100644 --- a/sentry/logutils.py +++ b/sentry/logutils.py @@ -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"]) diff --git a/sentry/tests/__init__.py b/sentry/tests/__init__.py index 6955e47d5..21926387a 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 aafe9d0e7..eb68c7137 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 @@ 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) diff --git a/sentry/tests/test_logutils.py b/sentry/tests/test_logutils.py index bcabdcb6a..1dd20e2de 100644 --- a/sentry/tests/test_logutils.py +++ b/sentry/tests/test_logutils.py @@ -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), + ) From 71fccc65a08f04cf8404199eb064a60cbce19bcc Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 28 Feb 2020 02:07:35 +0500 Subject: [PATCH 10/36] [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 65048bc8b..304d6c371 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 000000000..b25069628 --- /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 000000000..059d6b3a6 --- /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 000000000..531a05079 --- /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 000000000..92e100a5b --- /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 000000000..6656ddeb5 --- /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 5dc1677f1f26003ec2347a7e09afdc47155ff107 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/36] [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 46adbbea8..586b8b70b 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 -============ +**Table of contents** -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 +.. 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 d2e396f15..cc93d01ee 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 7bbe60b37cb37506d01fb9ac5e26030de2ea71ba 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/36] [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 586b8b70b..3e5025488 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 b25069628..ac71b0876 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 000000000..e191be255 --- /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 fd159a96b7ec8dfe8edb7381e663b85dca63ad7f Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 8 Jun 2020 18:48:12 +0000 Subject: [PATCH 13/36] 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 304d6c371..2857fcce2 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 51ec40e854a87af827dfc11194d84e5d3fd33fa5 Mon Sep 17 00:00:00 2001 From: Travis Waelbroeck Date: Mon, 16 Nov 2020 23:14:16 -0600 Subject: [PATCH 14/36] [MIG] sentry: Migration to 14.0 --- sentry/__manifest__.py | 4 ++-- sentry/tests/test_logutils.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 2857fcce2..90582d46a 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 1dd20e2de..16caf90a8 100644 --- a/sentry/tests/test_logutils.py +++ b/sentry/tests/test_logutils.py @@ -41,7 +41,8 @@ class TestOdooCookieSanitizer(unittest.TestCase): 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): From e4640dc0408767e7282e88019ec11626a147cc43 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sun, 31 Jan 2021 03:57:20 +0000 Subject: [PATCH 15/36] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translation: server-tools-14.0/server-tools-14.0-sentry Translate-URL: https://translation.odoo-community.org/projects/server-tools-14-0/server-tools-14-0-sentry/ --- sentry/i18n/zh_CN.po | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/sentry/i18n/zh_CN.po b/sentry/i18n/zh_CN.po index 4159be77e..e69de29bb 100644 --- a/sentry/i18n/zh_CN.po +++ b/sentry/i18n/zh_CN.po @@ -1,14 +0,0 @@ -# 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 c213184449dfc7d3414eaedb65389173ff58e869 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sun, 31 Jan 2021 05:31:32 +0000 Subject: [PATCH 16/36] [UPD] README.rst --- sentry/README.rst | 10 +++++----- sentry/static/description/index.html | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sentry/README.rst b/sentry/README.rst index 3e5025488..113892e0b 100644 --- a/sentry/README.rst +++ b/sentry/README.rst @@ -14,13 +14,13 @@ Sentry :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 + :target: https://github.com/OCA/server-tools/tree/14.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 + :target: https://translation.odoo-community.org/projects/server-tools-14-0/server-tools-14-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 + :target: https://runbot.odoo-community.org/runbot/149/14.0 :alt: Try me on Runbot |badge1| |badge2| |badge3| |badge4| |badge5| @@ -150,7 +150,7 @@ 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 `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -185,6 +185,6 @@ 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. +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. diff --git a/sentry/static/description/index.html b/sentry/static/description/index.html index e191be255..93a70b0f5 100644 --- a/sentry/static/description/index.html +++ b/sentry/static/description/index.html @@ -367,7 +367,7 @@ ul.auto-toc { !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

@@ -531,7 +531,7 @@ describe what they were doing when things went wrong.

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.

+feedback.

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

@@ -560,7 +560,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

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.

+

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 ab25f50c7b46e0e8c0b2052e017a72b3618ff797 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Sun, 31 Jan 2021 06:21:02 +0000 Subject: [PATCH 17/36] [UPD] Update sentry.pot --- sentry/i18n/sentry.pot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/i18n/sentry.pot b/sentry/i18n/sentry.pot index cc93d01ee..4d8b20f91 100644 --- a/sentry/i18n/sentry.pot +++ b/sentry/i18n/sentry.pot @@ -3,7 +3,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 13.0\n" +"Project-Id-Version: Odoo Server 14.0\n" "Report-Msgid-Bugs-To: \n" "Last-Translator: \n" "Language-Team: \n" From 8e5c37b2aa2551091b8505c3a728c6c243008761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20Gonz=C3=A1lez?= Date: Thu, 13 May 2021 15:05:49 +0000 Subject: [PATCH 18/36] [FIX] sentry: Warning when using classes from collections.abc The following warning is fixed: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.9 it will stop working --- sentry/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry/__init__.py b/sentry/__init__.py index ccee682e4..20516b970 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -9,7 +9,7 @@ from odoo.tools import config as odoo_config from . import const from .logutils import LoggerNameFilter, OdooSentryHandler -import collections +from collections import abc _logger = logging.getLogger(__name__) HAS_RAVEN = True @@ -53,7 +53,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 isinstance(option.converter, collections.Callable): + if isinstance(option.converter, abc.Callable): value = option.converter(value) options[option.key] = value From d4cab37f13b83c6ea22268044001371b7884241b Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 26 May 2021 14:31:40 +0000 Subject: [PATCH 19/36] sentry 14.0.1.0.1 --- sentry/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 90582d46a..5b1fbf37c 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Sentry", "summary": "Report Odoo errors to Sentry", - "version": "14.0.1.0.0", + "version": "14.0.1.0.1", "category": "Extra Tools", "website": "https://github.com/OCA/server-tools", "author": "Mohammed Barsi," From 93b758bf11f7ef82cd5be040e1a91054972ec7d8 Mon Sep 17 00:00:00 2001 From: Travis Waelbroeck Date: Fri, 24 Sep 2021 22:24:27 -0500 Subject: [PATCH 20/36] [FIX] sentry: enable use of "sentry_odoo_dir" config parameter Allow using `sentry_release` or `sentry_odoo_dir` in the Odoo configuration file. Previously, the `sentry_odoo_dir` was never actually respected. It would always be overridden by `sentry_release`. Even if `sentry_release` is not set, it will use an empty value instead of using `sentry_odoo_dir` to find the Git commit hash. After this commit, the `sentry_release` parameter still takes precedence. However, if `sentry_release` is not set and `sentry_odoo_dir` is set, then `sentry_odoo_dir` will be used to find the appropriate Git commit hash, which will be used as the `release` value. Both cases are covered by the added unit tests. --- sentry/__init__.py | 11 ++++++----- sentry/tests/test_client.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/sentry/__init__.py b/sentry/__init__.py index 20516b970..916ea8885 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -46,11 +46,7 @@ def initialize_raven(config, client_cls=None): _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")) - ) - } + options = {} for option in const.get_sentry_options(): value = config.get("sentry_%s" % option.key, option.default) if isinstance(option.converter, abc.Callable): @@ -64,6 +60,11 @@ def initialize_raven(config, client_cls=None): if level not in const.LOG_LEVEL_MAP: level = const.DEFAULT_LOG_LEVEL + if not options.get("release"): + options["release"] = config.get( + "sentry_release", get_odoo_commit(config.get("sentry_odoo_dir")) + ) + client_cls = client_cls or raven.Client client = client_cls(**options) handler = OdooSentryHandler( diff --git a/sentry/tests/test_client.py b/sentry/tests/test_client.py index eb68c7137..49bfe8f61 100644 --- a/sentry/tests/test_client.py +++ b/sentry/tests/test_client.py @@ -6,12 +6,16 @@ import sys import unittest import raven +from mock import patch from odoo import exceptions from .. import initialize_raven from ..logutils import OdooSentryHandler +GIT_SHA = "d670460b4b4aece5915caf5c68d12f560a9fe3e4" +RELEASE = "test@1.2.3" + def log_handler_by_class(logger, handler_cls): for handler in logger.handlers: @@ -117,3 +121,32 @@ class TestClientSetup(unittest.TestCase): record = logging.LogRecord(__name__, level, __file__, 42, msg, (), exc_info) handler.emit(record) self.assertEventNotCaptured(client, level, msg) + + @patch("odoo.addons.sentry.get_odoo_commit", return_value=GIT_SHA) + def test_config_odoo_dir(self, get_odoo_commit): + config = { + "sentry_enabled": True, + "sentry_dsn": "http://public:secret@example.com/1", + "sentry_odoo_dir": "/opt/odoo/core", + } + client = initialize_raven(config, client_cls=InMemoryClient) + self.assertEqual( + client.release, + GIT_SHA, + "Failed to use 'sentry_odoo_dir' parameter appropriately", + ) + + @patch("odoo.addons.sentry.get_odoo_commit", return_value=GIT_SHA) + def test_config_release(self, get_odoo_commit): + config = { + "sentry_enabled": True, + "sentry_dsn": "http://public:secret@example.com/1", + "sentry_odoo_dir": "/opt/odoo/core", + "sentry_release": RELEASE, + } + client = initialize_raven(config, client_cls=InMemoryClient) + self.assertEqual( + client.release, + RELEASE, + "Failed to use 'sentry_release' parameter appropriately", + ) From c950f6cb6fd326d7ebf3c32cf49700a3e0a080d6 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sat, 4 Dec 2021 14:38:03 +0000 Subject: [PATCH 21/36] sentry 14.0.1.0.2 --- sentry/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 5b1fbf37c..2239a0df7 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Sentry", "summary": "Report Odoo errors to Sentry", - "version": "14.0.1.0.1", + "version": "14.0.1.0.2", "category": "Extra Tools", "website": "https://github.com/OCA/server-tools", "author": "Mohammed Barsi," From 760b6a370939898bdc238872ad519cfce6095213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dept=2E=20T=C3=A9cnico?= Date: Mon, 14 Feb 2022 11:28:02 +0000 Subject: [PATCH 22/36] Added translation using Weblate (Catalan) --- sentry/i18n/ca.po | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 sentry/i18n/ca.po diff --git a/sentry/i18n/ca.po b/sentry/i18n/ca.po new file mode 100644 index 000000000..c038a7eec --- /dev/null +++ b/sentry/i18n/ca.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 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" From 78f7e6057f959146e97c77e38bf9220f9cd4a663 Mon Sep 17 00:00:00 2001 From: Fernanda Hernandez Date: Mon, 3 Jan 2022 18:15:37 +0100 Subject: [PATCH 23/36] [IMP] sentry: migrate sentry-raven to new api sentry-sdk --- sentry/README.rst | 74 ++++++++---- sentry/__init__.py | 87 +------------- sentry/__manifest__.py | 17 ++- sentry/const.py | 87 ++++++++------ sentry/generalutils.py | 62 ++++++++++ sentry/hooks.py | 123 ++++++++++++++++++++ sentry/logutils.py | 131 +++++++++++++--------- sentry/processor.py | 138 +++++++++++++++++++++++ sentry/readme/CONFIGURE.rst | 32 +++--- sentry/readme/CONTRIBUTORS.rst | 1 + sentry/readme/CREDITS.rst | 1 + sentry/readme/INSTALL.rst | 10 ++ sentry/readme/USAGE.rst | 2 +- sentry/static/description/index.html | 101 +++++++++-------- sentry/tests/test_client.py | 162 +++++++++++++-------------- sentry/tests/test_logutils.py | 14 +-- 16 files changed, 691 insertions(+), 351 deletions(-) create mode 100644 sentry/generalutils.py create mode 100644 sentry/hooks.py create mode 100644 sentry/processor.py create mode 100644 sentry/readme/CREDITS.rst create mode 100644 sentry/readme/INSTALL.rst diff --git a/sentry/README.rst b/sentry/README.rst index 113892e0b..bbe9e61f0 100644 --- a/sentry/README.rst +++ b/sentry/README.rst @@ -33,6 +33,20 @@ Odoo. .. contents:: :local: +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 sentry-sdk Python package to be available on +the system. It can be installed using pip:: + + pip install sentry-sdk + Configuration ============= @@ -67,16 +81,6 @@ configuration file: 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. @@ -94,11 +98,14 @@ configuration file: ============================= ==================================================================== ========================================================== Other `client arguments -`_ can be +`_ 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``. +file. Currently supported additional client arguments are: ``with_locals, +max_breadcrumbs, release, environment, server_name, shutdown_timeout, +in_app_include, in_app_exclude, default_integrations, dist, sample_rate, +send_default_pii, http_proxy, https_proxy, request_bodies, debug, +attach_stacktrace, ca_certs, propagate_traces, traces_sample_rate, +auto_enabling_integrations``. Example Odoo configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -110,14 +117,15 @@ Below is an example of Odoo configuration file with *Odoo Sentry* options:: 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_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_include_context = true sentry_environment = production - sentry_auto_log_stacks = false - sentry_odoo_dir = /home/odoo/odoo/ sentry_release = 1.3.2 + sentry_odoo_dir = /home/odoo/odoo/ Usage ===== @@ -127,7 +135,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/13.0 + :target: https://runbot.odoo-community.org/runbot/149/14.0 Known issues / Roadmap ====================== @@ -163,6 +171,7 @@ Authors * Mohammed Barsi * Versada * Nicolas JEUDY +* Vauxoo Contributors ~~~~~~~~~~~~ @@ -172,6 +181,11 @@ Contributors * Naglis Jonaitis * Atte Isopuro +Other credits +~~~~~~~~~~~~~ + +* Vauxoo + Maintainers ~~~~~~~~~~~ @@ -185,6 +199,26 @@ 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. +.. |maintainer-barsi| image:: https://github.com/barsi.png?size=40px + :target: https://github.com/barsi + :alt: barsi +.. |maintainer-naglis| image:: https://github.com/naglis.png?size=40px + :target: https://github.com/naglis + :alt: naglis +.. |maintainer-versada| image:: https://github.com/versada.png?size=40px + :target: https://github.com/versada + :alt: versada +.. |maintainer-moylop260| image:: https://github.com/moylop260.png?size=40px + :target: https://github.com/moylop260 + :alt: moylop260 +.. |maintainer-fernandahf| image:: https://github.com/fernandahf.png?size=40px + :target: https://github.com/fernandahf + :alt: fernandahf + +Current `maintainers `__: + +|maintainer-barsi| |maintainer-naglis| |maintainer-versada| |maintainer-moylop260| |maintainer-fernandahf| + 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. diff --git a/sentry/__init__.py b/sentry/__init__.py index 916ea8885..7001103db 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -1,86 +1 @@ -# 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 - -from collections import abc - -_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.') - - -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('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) - 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 = {} - for option in const.get_sentry_options(): - value = config.get("sentry_%s" % option.key, option.default) - if isinstance(option.converter, abc.Callable): - value = option.converter(value) - options[option.key] = value - - 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 not options.get("release"): - options["release"] = config.get( - "sentry_release", get_odoo_commit(config.get("sentry_odoo_dir")) - ) - - 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) +from .hooks import post_load diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 2239a0df7..1755aee02 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -3,16 +3,25 @@ { "name": "Sentry", "summary": "Report Odoo errors to Sentry", - "version": "14.0.1.0.2", + "version": "14.0.1.0.0", "category": "Extra Tools", "website": "https://github.com/OCA/server-tools", "author": "Mohammed Barsi," "Versada," "Nicolas JEUDY," - "Odoo Community Association (OCA)", + "Odoo Community Association (OCA)," + "Vauxoo", + "maintainers": ["barsi", "naglis", "versada", "moylop260", "fernandahf"], "license": "AGPL-3", "application": False, "installable": True, - "external_dependencies": {"python": ["raven"]}, - "depends": ["base"], + "external_dependencies": { + "python": [ + "sentry_sdk", + ] + }, + "depends": [ + "base", + ], + "post_load": "post_load", } diff --git a/sentry/const.py b/sentry/const.py index 8ea20aebb..f8cf9c728 100644 --- a/sentry/const.py +++ b/sentry/const.py @@ -1,18 +1,15 @@ # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - import collections import logging +import warnings + +from sentry_sdk import HttpTransport +from sentry_sdk.consts import DEFAULT_OPTIONS +from sentry_sdk.integrations.logging import LoggingIntegration 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.""" @@ -43,43 +40,67 @@ ODOO_USER_EXCEPTIONS = [ ] 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) +DEFAULT_ENVIRONMENT = "develop" + DEFAULT_TRANSPORT = "threaded" def select_transport(name=DEFAULT_TRANSPORT): + warnings.warn( + "`sentry_transport` has been deprecated. " + "Its not neccesary send it, will use `HttpTranport` by default.", + DeprecationWarning, + ) return { - "requests_synchronous": raven.transport.RequestsHTTPTransport, - "requests_threaded": raven.transport.ThreadedRequestsHTTPTransport, - "synchronous": raven.transport.HTTPTransport, - "threaded": raven.transport.ThreadedHTTPTransport, - }.get(name, DEFAULT_TRANSPORT) + "threaded": HttpTransport, + }.get(name, HttpTransport) + + +def get_sentry_logging(level=DEFAULT_LOG_LEVEL): + if level not in LOG_LEVEL_MAP: + level = DEFAULT_LOG_LEVEL + + return LoggingIntegration(level=LOG_LEVEL_MAP[level], event_level=logging.WARNING) 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("transport", DEFAULT_OPTIONS["transport"], select_transport), + SentryOption("logging_level", DEFAULT_LOG_LEVEL, get_sentry_logging), + SentryOption("with_locals", DEFAULT_OPTIONS["with_locals"], None), + SentryOption("max_breadcrumbs", DEFAULT_OPTIONS["max_breadcrumbs"], None), + SentryOption("release", DEFAULT_OPTIONS["release"], None), + SentryOption("environment", DEFAULT_OPTIONS["environment"], None), + SentryOption("server_name", DEFAULT_OPTIONS["server_name"], None), + SentryOption("shutdown_timeout", DEFAULT_OPTIONS["shutdown_timeout"], None), + SentryOption("integrations", DEFAULT_OPTIONS["integrations"], None), + SentryOption( + "in_app_include", DEFAULT_OPTIONS["in_app_include"], split_multiple + ), + SentryOption( + "in_app_exclude", DEFAULT_OPTIONS["in_app_exclude"], split_multiple + ), + SentryOption( + "default_integrations", DEFAULT_OPTIONS["default_integrations"], None + ), + SentryOption("dist", DEFAULT_OPTIONS["dist"], None), + SentryOption("sample_rate", DEFAULT_OPTIONS["sample_rate"], None), + SentryOption("send_default_pii", DEFAULT_OPTIONS["send_default_pii"], None), + SentryOption("http_proxy", DEFAULT_OPTIONS["http_proxy"], None), + SentryOption("https_proxy", DEFAULT_OPTIONS["https_proxy"], None), SentryOption("ignore_exceptions", DEFAULT_IGNORED_EXCEPTIONS, split_multiple), - SentryOption("processors", DEFAULT_PROCESSORS, split_multiple), - SentryOption("environment", None, None), - SentryOption("release", None, None), + SentryOption("request_bodies", DEFAULT_OPTIONS["request_bodies"], None), + SentryOption("attach_stacktrace", DEFAULT_OPTIONS["attach_stacktrace"], None), + SentryOption("ca_certs", DEFAULT_OPTIONS["ca_certs"], None), + SentryOption("propagate_traces", DEFAULT_OPTIONS["propagate_traces"], None), + SentryOption("traces_sample_rate", DEFAULT_OPTIONS["traces_sample_rate"], None), + SentryOption( + "auto_enabling_integrations", + DEFAULT_OPTIONS["auto_enabling_integrations"], + None, + ), ] diff --git a/sentry/generalutils.py b/sentry/generalutils.py new file mode 100644 index 000000000..d361a0f68 --- /dev/null +++ b/sentry/generalutils.py @@ -0,0 +1,62 @@ +try: + from collections.abc import Mapping +except ImportError: # pragma: no cover + # Python < 3.3 + from collections import Mapping # pragma: no cover + + +def string_types(): + """ Taken from https://git.io/JIv5J """ + + return (str,) + + +def is_namedtuple(value): + """https://stackoverflow.com/a/2166841/1843746 + But modified to handle subclasses of namedtuples. + Taken from https://git.io/JIsfY + """ + if not isinstance(value, tuple): + return False + f = getattr(type(value), "_fields", None) + if not isinstance(f, tuple): + return False + return all(type(n) == str for n in f) + + +def iteritems(d, **kw): + """Override iteritems for support multiple versions python. + Taken from https://git.io/JIvMi + """ + return iter(d.items(**kw)) + + +def varmap(func, var, context=None, name=None): + """Executes ``func(key_name, value)`` on all values + recurisively discovering dict and list scoped + values. Taken from https://git.io/JIvMN + """ + if context is None: + context = {} + objid = id(var) + if objid in context: + return func(name, "<...>") + context[objid] = 1 + + if isinstance(var, (list, tuple)) and not is_namedtuple(var): + ret = [varmap(func, f, context, name) for f in var] + else: + ret = func(name, var) + if isinstance(ret, Mapping): + ret = {k: varmap(func, v, context, k) for k, v in iteritems(var)} + del context[objid] + return ret + + +def get_environ(environ): + """Returns our whitelisted environment variables. + Taken from https://git.io/JIsf2 + """ + for key in ("REMOTE_ADDR", "SERVER_NAME", "SERVER_PORT"): + if key in environ: + yield key, environ[key] diff --git a/sentry/hooks.py b/sentry/hooks.py new file mode 100644 index 000000000..ecea39479 --- /dev/null +++ b/sentry/hooks.py @@ -0,0 +1,123 @@ +# Copyright 2016-2017 Versada +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging +from collections import abc + +import odoo.http +from odoo.service import wsgi_server +from odoo.tools import config as odoo_config + +from . import const +from .logutils import ( + InvalidGitRepository, + SanitizeOdooCookiesProcessor, + fetch_git_sha, + get_extra_context, +) + +_logger = logging.getLogger(__name__) +HAS_SENTRY_SDK = True +try: + import sentry_sdk + from sentry_sdk.integrations.logging import ignore_logger + from sentry_sdk.integrations.threading import ThreadingIntegration + from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware +except ImportError: # pragma: no cover + HAS_SENTRY_SDK = False # pragma: no cover + _logger.debug( + "Cannot import 'sentry-sdk'.\ + Please make sure it is installed." + ) # pragma: no cover + + +def before_send(event, hint): + """Add context to event if include_context is True + and sanitize sensitive data""" + if event.setdefault("tags", {})["include_context"]: + cxtest = get_extra_context(odoo.http.request) + info_request = ["tags", "user", "extra", "request"] + + for item in info_request: + info_item = event.setdefault(item, {}) + info_item.update(cxtest.setdefault(item, {})) + + raven_processor = SanitizeOdooCookiesProcessor() + raven_processor.process(event) + + return event + + +def get_odoo_commit(odoo_dir): + """Attempts to get Odoo git commit from :param:`odoo_dir`.""" + if not odoo_dir: + return + try: + return fetch_git_sha(odoo_dir) + except InvalidGitRepository: + _logger.debug("Odoo directory: '%s' not a valid git repository", odoo_dir) + + +def initialize_sentry(config): + """Setup an instance of :class:`sentry_sdk.Client`. + :param config: Sentry configuration + :param client: class used to instantiate the sentry_sdk client. + """ + enabled = config.get("sentry_enabled", False) + if not (HAS_SENTRY_SDK and enabled): + return + _logger.info("Initializing sentry...") + 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 = {} + for option in const.get_sentry_options(): + value = config.get("sentry_%s" % option.key, option.default) + if isinstance(option.converter, abc.Callable): + value = option.converter(value) + options[option.key] = value + + exclude_loggers = const.split_multiple( + config.get("sentry_exclude_loggers", const.DEFAULT_EXCLUDE_LOGGERS) + ) + + if not options.get("release"): + options["release"] = config.get( + "sentry_release", get_odoo_commit(config.get("sentry_odoo_dir")) + ) + + # Change name `ignore_exceptions` (with raven) + # to `ignore_errors' (sentry_sdk) + options["ignore_errors"] = options["ignore_exceptions"] + del options["ignore_exceptions"] + + options["before_send"] = before_send + + options["integrations"] = [ + options["logging_level"], + ThreadingIntegration(propagate_hub=True), + ] + # Remove logging_level, since in sentry_sdk is include in 'integrations' + del options["logging_level"] + + client = sentry_sdk.init(**options) + + sentry_sdk.set_tag("include_context", config.get("sentry_include_context", True)) + + if exclude_loggers: + for item in exclude_loggers: + ignore_logger(item) + + wsgi_server.application = SentryWsgiMiddleware(wsgi_server.application) + + with sentry_sdk.push_scope() as scope: + scope.set_extra("debug", False) + sentry_sdk.capture_message("Starting Odoo Server", "info") + + return client + + +def post_load(): + initialize_sentry(odoo_config) diff --git a/sentry/logutils.py b/sentry/logutils.py index 93709c9ce..465641d3d 100644 --- a/sentry/logutils.py +++ b/sentry/logutils.py @@ -1,20 +1,14 @@ # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -import logging +import os.path import urllib.parse -import odoo.http +from sentry_sdk._compat import text_type +from werkzeug import datastructures -_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.') - SentryHandler = object - SanitizePasswordsProcessor = object +from .generalutils import get_environ +from .processor import SanitizePasswordsProcessor def get_request_info(request): @@ -28,70 +22,99 @@ def get_request_info(request): "url": "{}://{}{}".format(urlparts.scheme, urlparts.netloc, urlparts.path), "query_string": urlparts.query, "method": request.method, - "headers": dict(get_headers(request.environ)), + "headers": dict(datastructures.EnvironHeaders(request.environ)), "env": dict(get_environ(request.environ)), } -def get_extra_context(): +def get_extra_context(request): """ 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), + "tags": { + "database": session.get("db", None), + }, + "user": { + "email": session.get("login", None), + "id": session.get("uid", None), + }, + "extra": { + "context": session.get("context", {}), }, - "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`. - + """Custom :class:`raven.processors.Processor`. Allows to sanitize sensitive Odoo cookies, namely the "session_id" cookie. """ - KEYS = FIELDS = frozenset(["session_id"]) + KEYS = frozenset( + [ + "session_id", + ] + ) + + +class InvalidGitRepository(Exception): + pass + + +def fetch_git_sha(path, head=None): + """>>> fetch_git_sha(os.path.dirname(__file__)) + Taken from https://git.io/JITmC + """ + if not head: + head_path = os.path.join(path, ".git", "HEAD") + if not os.path.exists(head_path): + raise InvalidGitRepository( + "Cannot identify HEAD for git repository at %s" % (path,) + ) + + with open(head_path, "r") as fp: + head = text_type(fp.read()).strip() + + if head.startswith("ref: "): + head = head[5:] + revision_file = os.path.join(path, ".git", *head.split("/")) + else: + return head + else: + revision_file = os.path.join(path, ".git", "refs", "heads", head) + + if not os.path.exists(revision_file): + if not os.path.exists(os.path.join(path, ".git")): + raise InvalidGitRepository( + "%s does not seem to be the root of a git repository" % (path,) + ) + + # Check for our .git/packed-refs' file since a `git gc` may have run + # https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery + packed_file = os.path.join(path, ".git", "packed-refs") + if os.path.exists(packed_file): + with open(packed_file) as fh: + for line in fh: + line = line.rstrip() + if line and line[:1] not in ("#", "^"): + try: + revision, ref = line.split(" ", 1) + except ValueError: + continue + if ref == head: + return text_type(revision) + + raise InvalidGitRepository( + 'Unable to find ref to head "%s" in repository' % (head,) + ) + + with open(revision_file) as fh: + return text_type(fh.read()).strip() diff --git a/sentry/processor.py b/sentry/processor.py new file mode 100644 index 000000000..8afb7d754 --- /dev/null +++ b/sentry/processor.py @@ -0,0 +1,138 @@ +""" Custom class of raven.core.processors taken of https://git.io/JITko + This is a custom class of processor to filter and sanitize + passwords and keys from request data, it does not exist in + sentry-sdk. +""" + +from __future__ import absolute_import + +import re + +from sentry_sdk._compat import text_type + +from .generalutils import string_types, varmap + + +class SanitizeKeysProcessor(object): + """Class from raven for sanitize keys, cookies, etc + Asterisk out things that correspond to a configurable set of keys.""" + + MASK = "*" * 8 + + def process(self, data, **kwargs): + if "exception" in data: + if "values" in data["exception"]: + for value in data["exception"].get("values", []): + if "stacktrace" in value: + self.filter_stacktrace(value["stacktrace"]) + + if "request" in data: + self.filter_http(data["request"]) + + if "extra" in data: + data["extra"] = self.filter_extra(data["extra"]) + + if "level" in data: + data["level"] = self.filter_level(data["level"]) + + return data + + @property + def sanitize_keys(self): + pass + + def sanitize(self, item, value): + if value is None: + return + + if not item: # key can be a NoneType + return value + + # Just in case we have bytes here, we want to make them into text + # properly without failing so we can perform our check. + if isinstance(item, bytes): + item = item.decode("utf-8", "replace") + else: + item = text_type(item) + + item = item.lower() + for key in self.sanitize_keys: + if key in item: + # store mask as a fixed length for security + return self.MASK + return value + + def filter_stacktrace(self, data): + for frame in data.get("frames", []): + if "vars" not in frame: + continue + frame["vars"] = varmap(self.sanitize, frame["vars"]) + + def filter_http(self, data): + for n in ("data", "cookies", "headers", "env", "query_string"): + if n not in data: + continue + + # data could be provided as bytes and if it's python3 + if isinstance(data[n], bytes): + data[n] = data[n].decode("utf-8", "replace") + + if isinstance(data[n], string_types()) and "=" in data[n]: + # at this point we've assumed it's a standard HTTP query + # or cookie + if n == "cookies": + delimiter = ";" + else: + delimiter = "&" + + data[n] = self._sanitize_keyvals(data[n], delimiter) + else: + data[n] = varmap(self.sanitize, data[n]) + if n == "headers" and "Cookie" in data[n]: + data[n]["Cookie"] = self._sanitize_keyvals(data[n]["Cookie"], ";") + + def filter_extra(self, data): + return varmap(self.sanitize, data) + + def filter_level(self, data): + return re.sub(r"\x1b(\[.*?[@-~]|\].*?(\x07|\x1b\\))", "", data) + + def _sanitize_keyvals(self, keyvals, delimiter): + sanitized_keyvals = [] + for keyval in keyvals.split(delimiter): + keyval = keyval.split("=") + if len(keyval) == 2: + sanitized_keyvals.append((keyval[0], self.sanitize(*keyval))) + else: + sanitized_keyvals.append(keyval) + + return delimiter.join("=".join(keyval) for keyval in sanitized_keyvals) + + +class SanitizePasswordsProcessor(SanitizeKeysProcessor): + """Asterisk out things that look like passwords, credit card numbers, + and API keys in frames, http, and basic extra data.""" + + KEYS = frozenset( + [ + "password", + "secret", + "passwd", + "authorization", + "api_key", + "apikey", + "sentry_dsn", + "access_token", + ] + ) + VALUES_RE = re.compile(r"^(?:\d[ -]*?){13,16}$") + + @property + def sanitize_keys(self): + return self.KEYS + + def sanitize(self, item, value): + value = super(SanitizePasswordsProcessor, self).sanitize(item, value) + if isinstance(value, string_types()) and self.VALUES_RE.match(value): + return self.MASK + return value diff --git a/sentry/readme/CONFIGURE.rst b/sentry/readme/CONFIGURE.rst index ac71b0876..25de942df 100644 --- a/sentry/readme/CONFIGURE.rst +++ b/sentry/readme/CONFIGURE.rst @@ -29,16 +29,6 @@ configuration file: 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. @@ -56,11 +46,14 @@ configuration file: ============================= ==================================================================== ========================================================== Other `client arguments -`_ can be +`_ 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``. +file. Currently supported additional client arguments are: ``with_locals, +max_breadcrumbs, release, environment, server_name, shutdown_timeout, +in_app_include, in_app_exclude, default_integrations, dist, sample_rate, +send_default_pii, http_proxy, https_proxy, request_bodies, debug, +attach_stacktrace, ca_certs, propagate_traces, traces_sample_rate, +auto_enabling_integrations``. Example Odoo configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -72,11 +65,12 @@ Below is an example of Odoo configuration file with *Odoo Sentry* options:: 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_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_include_context = true sentry_environment = production - sentry_auto_log_stacks = false - sentry_odoo_dir = /home/odoo/odoo/ sentry_release = 1.3.2 + sentry_odoo_dir = /home/odoo/odoo/ diff --git a/sentry/readme/CONTRIBUTORS.rst b/sentry/readme/CONTRIBUTORS.rst index 059d6b3a6..7281929d2 100644 --- a/sentry/readme/CONTRIBUTORS.rst +++ b/sentry/readme/CONTRIBUTORS.rst @@ -2,3 +2,4 @@ * Andrius Preimantas * Naglis Jonaitis * Atte Isopuro +* Florian Mounier diff --git a/sentry/readme/CREDITS.rst b/sentry/readme/CREDITS.rst new file mode 100644 index 000000000..7f8b9f7ab --- /dev/null +++ b/sentry/readme/CREDITS.rst @@ -0,0 +1 @@ +* Vauxoo diff --git a/sentry/readme/INSTALL.rst b/sentry/readme/INSTALL.rst new file mode 100644 index 000000000..6ccb9e341 --- /dev/null +++ b/sentry/readme/INSTALL.rst @@ -0,0 +1,10 @@ +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 sentry-sdk Python package to be available on +the system. It can be installed using pip:: + + pip install sentry-sdk diff --git a/sentry/readme/USAGE.rst b/sentry/readme/USAGE.rst index 6656ddeb5..e50ec6d61 100644 --- a/sentry/readme/USAGE.rst +++ b/sentry/readme/USAGE.rst @@ -3,4 +3,4 @@ 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 + :target: https://runbot.odoo-community.org/runbot/149/14.0 diff --git a/sentry/static/description/index.html b/sentry/static/description/index.html index 93a70b0f5..7a34cca98 100644 --- a/sentry/static/description/index.html +++ b/sentry/static/description/index.html @@ -3,7 +3,7 @@ - + Sentry