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

Sentry

+ + +

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

+

This module allows painless Sentry integration with +Odoo.

+

Table of contents

+ +
+

Configuration

+

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

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

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

+
+

Example Odoo configuration

+

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

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

Usage

+

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

+Try me on Runbot +
+
+

Known issues / Roadmap

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

Bug Tracker

+

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

+

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

+
+
+

Credits

+
+

Authors

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

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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

+

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

+

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

+
+
+
+ + From 1fa497825fb034332914d6066197531eb5af37f7 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 8 Jun 2020 18:48:12 +0000 Subject: [PATCH 13/42] sentry 13.0.1.1.0 --- sentry/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 304d6c37151..2857fcce2c2 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Sentry", "summary": "Report Odoo errors to Sentry", - "version": "13.0.1.0.0", + "version": "13.0.1.1.0", "category": "Extra Tools", "website": "https://odoo-community.org/", "author": "Mohammed Barsi," From 9a59df33e33a2ad74d3bb7a45a0f497e804141eb Mon Sep 17 00:00:00 2001 From: Travis Waelbroeck Date: Mon, 16 Nov 2020 23:14:16 -0600 Subject: [PATCH 14/42] [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 2857fcce2c2..90582d46ade 100644 --- a/sentry/__manifest__.py +++ b/sentry/__manifest__.py @@ -3,9 +3,9 @@ { "name": "Sentry", "summary": "Report Odoo errors to Sentry", - "version": "13.0.1.1.0", + "version": "14.0.1.0.0", "category": "Extra Tools", - "website": "https://odoo-community.org/", + "website": "https://github.com/OCA/server-tools", "author": "Mohammed Barsi," "Versada," "Nicolas JEUDY," diff --git a/sentry/tests/test_logutils.py b/sentry/tests/test_logutils.py index 1dd20e2de0a..16caf90a835 100644 --- a/sentry/tests/test_logutils.py +++ b/sentry/tests/test_logutils.py @@ -41,7 +41,8 @@ def test_cookie_as_string_with_partials(self): self.assertTrue("request" in result) http = result["request"] self.assertEqual( - http["cookies"], "website_lang=en_us;session_id;foo=bar".format(m=proc.MASK) + http["cookies"], + "website_lang=en_us;session_id={m};foo=bar".format(m=proc.MASK), ) def test_cookie_header(self): From 4cfdcd8715243e5849535980165363b14eebb2c8 Mon Sep 17 00:00:00 2001 From: OCA Transbot Date: Sun, 31 Jan 2021 03:57:20 +0000 Subject: [PATCH 15/42] 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 4159be77e05..e69de29bb2d 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 623f20a10b582b52fe3627feff7c0d659cf90df9 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sun, 31 Jan 2021 05:31:32 +0000 Subject: [PATCH 16/42] [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 3e5025488be..113892e0ba0 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 e191be25548..93a70b0f5b9 100644 --- a/sentry/static/description/index.html +++ b/sentry/static/description/index.html @@ -367,7 +367,7 @@

Sentry

!! 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 @@

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.

@@ -560,7 +560,7 @@

Maintainers

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 51b5532fde6d3cb935a5a3c3043d80f7bf670dcb Mon Sep 17 00:00:00 2001 From: oca-travis Date: Sun, 31 Jan 2021 06:21:02 +0000 Subject: [PATCH 17/42] [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 cc93d01ee54..4d8b20f912f 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 8988cd60e55e2f566223fc28f6c860c0c5e05c50 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/42] [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 ++-- sentry/__manifest__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry/__init__.py b/sentry/__init__.py index ccee682e4f6..20516b97072 100644 --- a/sentry/__init__.py +++ b/sentry/__init__.py @@ -9,7 +9,7 @@ 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 diff --git a/sentry/__manifest__.py b/sentry/__manifest__.py index 90582d46ade..5b1fbf37cab 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 a369f8837df964e53bf2817e76d1876b7d823dc4 Mon Sep 17 00:00:00 2001 From: Travis Waelbroeck Date: Fri, 24 Sep 2021 22:24:27 -0500 Subject: [PATCH 19/42] [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/__manifest__.py | 2 +- sentry/i18n/ca.po | 14 ++++++++++++++ sentry/tests/test_client.py | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 sentry/i18n/ca.po diff --git a/sentry/__init__.py b/sentry/__init__.py index 20516b97072..916ea8885e5 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/__manifest__.py b/sentry/__manifest__.py index 5b1fbf37cab..2239a0df78c 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," diff --git a/sentry/i18n/ca.po b/sentry/i18n/ca.po new file mode 100644 index 00000000000..c038a7eecbd --- /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" diff --git a/sentry/tests/test_client.py b/sentry/tests/test_client.py index eb68c71378d..49bfe8f613c 100644 --- a/sentry/tests/test_client.py +++ b/sentry/tests/test_client.py @@ -6,12 +6,16 @@ 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 @@ def test_ignore_exceptions(self): 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 cff56b38f3ec240ae20eeea6aeb40f39a8ec3783 Mon Sep 17 00:00:00 2001 From: Fernanda Hernandez Date: Mon, 3 Jan 2022 18:15:37 +0100 Subject: [PATCH 20/42] [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 | 123 ++++++++++++-------- 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 | 164 +++++++++++++-------------- sentry/tests/test_logutils.py | 14 +-- 16 files changed, 688 insertions(+), 348 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 113892e0ba0..bbe9e61f0b8 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 916ea8885e5..7001103db4d 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 2239a0df78c..1755aee0271 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 8ea20aebb4c..f8cf9c7285a 100644 --- a/sentry/const.py +++ b/sentry/const.py @@ -1,17 +1,14 @@ # Copyright 2016-2017 Versada # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - import collections import logging +import warnings -import odoo.loglevels +from sentry_sdk import HttpTransport +from sentry_sdk.consts import DEFAULT_OPTIONS +from sentry_sdk.integrations.logging import LoggingIntegration -_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.') +import odoo.loglevels def split_multiple(string, delimiter=",", strip_chars=None): @@ -43,43 +40,67 @@ def split_multiple(string, delimiter=",", strip_chars=None): ] 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 00000000000..d361a0f6816 --- /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 00000000000..ecea39479d5 --- /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 93709c9ce4f..465641d3d97 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)}, + "tags": { + "database": session.get("db", None), + }, "user": { - "login": session.get("login", None), - "uid": session.get("uid", None), + "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. +class SanitizeOdooCookiesProcessor(SanitizePasswordsProcessor): + """Custom :class:`raven.processors.Processor`. + Allows to sanitize sensitive Odoo cookies, namely the "session_id" cookie. """ - def __init__(self, include_extra_context, *args, **kwargs): - super(OdooSentryHandler, self).__init__(*args, **kwargs) - self.include_extra_context = include_extra_context + KEYS = frozenset( + [ + "session_id", + ] + ) - def emit(self, record): - if self.include_extra_context: - self.client.context.merge(get_extra_context()) - return super(OdooSentryHandler, self).emit(record) +class InvalidGitRepository(Exception): + pass -class SanitizeOdooCookiesProcessor(SanitizePasswordsProcessor): - """ - Custom :class:`raven.processors.Processor`. - Allows to sanitize sensitive Odoo cookies, namely the "session_id" cookie. +def fetch_git_sha(path, head=None): + """>>> fetch_git_sha(os.path.dirname(__file__)) + Taken from https://git.io/JITmC """ - - KEYS = FIELDS = frozenset(["session_id"]) + 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 00000000000..8afb7d75483 --- /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 ac71b087600..25de942df51 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 059d6b3a6ab..7281929d25f 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 00000000000..7f8b9f7ab7e --- /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 00000000000..6ccb9e34199 --- /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 6656ddeb507..e50ec6d6145 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 93a70b0f5b9..7a34cca981a 100644 --- a/sentry/static/description/index.html +++ b/sentry/static/description/index.html @@ -3,7 +3,7 @@ - + Sentry