+
Sentry
+
+
+
+
This module allows painless Sentry integration with
+Odoo.
+
+
+
+
+
+
+
+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://<public_key>:<secret_key>@sentry.example.com/<project id>
+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.
+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_loggers |
+A string of comma-separated logger names which should be excluded
+from Sentry. |
+werkzeug |
+
+sentry_ignored_exceptions |
+A 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_include_context |
+If 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_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: 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
+
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_include_context = true
+sentry_environment = production
+sentry_release = 1.3.2
+sentry_odoo_dir = /home/odoo/odoo/
+
+
Table of contents
+
+
+
+
The module can be installed just like any other Odoo module, by adding the
+module’s directory to Odoo addons_path. In order for the module to correctly
+wrap the Odoo WSGI application, it also needs to be loaded as a server-wide
+module. This can be done with the server_wide_modules parameter in your
+Odoo config file or with the --load command-line parameter.
+
This module additionally requires the sentry-sdk Python package to be available on
+the system. It can be installed using pip:
+
+pip install sentry-sdk
+
+
+
+
+
The following additional configuration options can be added to your Odoo
+configuration file:
+
+
+
+
Once configured and installed, the module will report any logging event at and
+above the configured Sentry logging level, no additional actions are necessary.
+
+
+
+
+
+- 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.
+
+
+
+
+
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.
+
+
+
+
+
+
+- Mohammed Barsi
+- Versada
+- Nicolas JEUDY
+- Vauxoo
+
+
+
+
+
+
+
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.
+
Current maintainers:
+
+
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/tests/test_client.py b/sentry/tests/test_client.py
index aafe9d0e7..71c71b752 100644
--- a/sentry/tests/test_client.py
+++ b/sentry/tests/test_client.py
@@ -3,122 +3,120 @@
import logging
import sys
-import unittest
-
-import raven
-
+from odoo.tests import TransactionCase
+from odoo.tools import config
from odoo import exceptions
-from .. import initialize_raven
-from ..logutils import OdooSentryHandler
-
+from ..hooks import initialize_sentry
+from sentry_sdk.transport import HttpTransport
+from sentry_sdk.integrations.logging import _IGNORED_LOGGERS
-def log_handler_by_class(logger, handler_cls):
- for handler in logger.handlers:
- if isinstance(handler, handler_cls):
- yield handler
+def remove_handler_ignore(handler_name):
+ """Removes handlers of handlers ignored list.
+ """
+ _IGNORED_LOGGERS.discard(handler_name)
-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
+class TestException(exceptions.UserError):
+ pass
- :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.
+class InMemoryTransport(HttpTransport):
+ """A :class:`sentry_sdk.Hub.transport` 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):
+ def __init__(self, *args, **kwargs):
self.events = []
- super(InMemoryClient, self).__init__(**kwargs)
-
- def is_enabled(self):
- return True
- def send(self, **kwargs):
- self.events.append(kwargs)
+ def capture_event(self, event, *args, **kwargs):
+ self.events.append(event)
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):
+ event.get('logentry', {}).get('message') == event_msg):
return True
return False
+ def flush(self, *args, **kwargs):
+ pass
-class TestClientSetup(unittest.TestCase):
+ def kill(self, *args, **kwargs):
+ pass
+
+
+class TestClientSetup(TransactionCase):
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)
+ self.dsn = 'http://public:secret@example.com/1'
+ config.options['sentry_enabled'] = True
+ config.options['sentry_dsn'] = self.dsn
+ self.client = initialize_sentry(config)._client
+ self.client.transport = InMemoryTransport({'dsn': self.dsn})
+ self.handler = self.client.integrations['logging']._handler
+
+ def log(self, level, msg, exc_info=None):
+ record = logging.LogRecord(
+ __name__, level, __file__, 42, msg, (), exc_info)
+ self.handler.emit(record)
def assertEventCaptured(self, client, event_level, event_msg):
self.assertTrue(
- client.has_event(event_level, event_msg),
+ client.transport.has_event(event_level, 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),
+ client.transport.has_event(event_level, 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',
- }
- client = initialize_raven(config, client_cls=InMemoryClient)
- self.assertEqual(client.remote.base_url, 'http://example.com')
+ self.assertEqual(self.client.dsn, self.dsn)
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)
+ self.log(level, msg)
+ level = "warning"
+ self.assertEventCaptured(self.client, level, msg)
+
+ def test_capture_event_exc(self):
+ level, msg = logging.WARNING, 'Test event, can be ignored exception'
+ try:
+ raise TestException(msg)
+ except TestException:
+ exc_info = sys.exc_info()
+ self.log(level, msg, exc_info)
+ level = "warning"
+ self.assertEventCaptured(self.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]
+ config.options['sentry_ignore_exceptions'] = "odoo.exceptions.UserError"
+ client = initialize_sentry(config)._client
+ client.transport = InMemoryTransport({'dsn': self.dsn})
+ level, msg = logging.WARNING, 'Test exception'
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.log(level, msg, exc_info)
+ level = "warning"
+ self.assertEventNotCaptured(client, level, msg)
+
+ def test_exclude_logger(self):
+ config.options['sentry_enabled'] = True
+ config.options['sentry_exclude_loggers'] = __name__
+ client = initialize_sentry(config)._client
+ client.transport = InMemoryTransport({'dsn': self.dsn})
+ level, msg = logging.WARNING, 'Test exclude logger %s' % __name__
+ self.log(level, msg)
+ level = "warning"
+ # Revert ignored logger so it doesn't affect other tests
+ remove_handler_ignore(__name__)
self.assertEventNotCaptured(client, level, msg)
diff --git a/sentry/tests/test_logutils.py b/sentry/tests/test_logutils.py
index bcabdcb6a..91fab2108 100644
--- a/sentry/tests/test_logutils.py
+++ b/sentry/tests/test_logutils.py
@@ -1,14 +1,12 @@
# Copyright 2016-2017 Versada