[PATCH sentry v2] Switch to sentry-sdk

Denis Laxalde denis.laxalde at logilab.fr
Wed Sep 4 11:46:17 CEST 2019


# HG changeset patch
# User Denis Laxalde <denis.laxalde at logilab.fr>
# Date 1565701647 -7200
#      Tue Aug 13 15:07:27 2019 +0200
# Node ID 5224fc8ddc3a3b06df5a6b088ae166d0ada8b323
# Parent  626f9b09772d8d3774e85d6256b0611d8ddca8ea
# Available At http://hg.logilab.org/review/cubes/sentry
#              hg pull http://hg.logilab.org/review/cubes/sentry -r 5224fc8ddc3a
Switch to sentry-sdk

We switch to the "new" Sentry Python SDK instead of the deprecated Raven
client.

Instead of migrating the existing code steps by steps I chose to rewrite
the plugin altogether since the end result is very simple. In
particular, we now use the Pyramid integration explicitly instead of
monkey-patching cubicweb's publisher. By doing so, we no longer need to
retrieve versions of all cubes used by the application because the new
SDK does this by default (not just for cubes, it retrieves all
dependencies); we only keep the "release" key computation which is
specific to the cubicweb application (first cube listed by cwconfig).
Similarly, the logging is just a integration and we configure it when
the option is defined (otherwise, it'll be enabled as well by the SDK,
but with default configuration).

We remove tests as this cube now only contain declarative configuration
that is expected to just work™.

diff --git a/cubicweb_sentry/__init__.py b/cubicweb_sentry/__init__.py
--- a/cubicweb_sentry/__init__.py
+++ b/cubicweb_sentry/__init__.py
@@ -0,0 +1,31 @@
+import logging
+
+import sentry_sdk
+from sentry_sdk.integrations.logging import LoggingIntegration
+from sentry_sdk.integrations.pyramid import PyramidIntegration
+
+
+def init_sdk(cwconfig, *integrations):
+    """Initialize `sentry_sdk` from cubicweb configuration."""
+    dsn = cwconfig.get('sentry-dsn')
+    if not dsn:
+        return None
+    release = cwconfig.cube_version(cwconfig.cubes()[0])
+    log_level = cwconfig['sentry-log-level']
+    integrations = list(integrations)
+    if log_level is not None:
+        integrations.append(LoggingIntegration(
+            level=logging.DEBUG,  # Capture debug and above as breadcrumbs
+            event_level=log_level.upper(),
+        ))
+    sentry_sdk.init(
+        dsn=cwconfig['sentry-dsn'],
+        release=release,
+        integrations=integrations,
+    )
+    return sentry_sdk
+
+
+def includeme(config):
+    cwconfig = config.registry['cubicweb.config']
+    init_sdk(cwconfig, PyramidIntegration())
diff --git a/cubicweb_sentry/__pkginfo__.py b/cubicweb_sentry/__pkginfo__.py
--- a/cubicweb_sentry/__pkginfo__.py
+++ b/cubicweb_sentry/__pkginfo__.py
@@ -21,6 +21,6 @@ classifiers = [
 ]
 
 __depends__ = {'cubicweb': '>= 3.24.0',
-               'raven': None}
+               'sentry-sdk': None}
 
 __recommends__ = {}
diff --git a/cubicweb_sentry/ccplugin.py b/cubicweb_sentry/ccplugin.py
--- a/cubicweb_sentry/ccplugin.py
+++ b/cubicweb_sentry/ccplugin.py
@@ -5,6 +5,9 @@ from logilab.common.clcommands import Co
 from cubicweb import ConfigurationError
 from cubicweb.cwconfig import CubicWebConfiguration
 
+from cubicweb_sentry import init_sdk
+
+
 logger = logging.getLogger(__name__)
 
 original_main_run = Command.main_run
@@ -14,7 +17,7 @@ original_main_run = Command.main_run
 def sentry_main_run(self, args, rcfile=None):
     try:
         return original_main_run(self, args, rcfile=rcfile)
-    except Exception:
+    except Exception as exc:
         cmd_args = self.load_command_line_configuration(args)
         if cmd_args:
             try:
@@ -22,13 +25,9 @@ def sentry_main_run(self, args, rcfile=N
             except ConfigurationError:
                 pass
             else:
-                sentry_dsn = config.get('sentry-dsn')
-                if sentry_dsn:
-                    from raven import Client
-                    client = Client(sentry_dsn)
-                    extra = {'args': args}
-                    ident = client.get_ident(
-                        client.captureException(extra=extra))
-                    msg = "Exception caught; reference is %s" % ident
-                    logger.critical(msg)
+                sentry_sdk = init_sdk(config)
+                if sentry_sdk:
+                    with sentry_sdk.push_scope() as scope:
+                        scope.set_extra('args', args)
+                        sentry_sdk.capture_exception(exc)
         raise
diff --git a/cubicweb_sentry/patches.py b/cubicweb_sentry/patches.py
deleted file mode 100644
--- a/cubicweb_sentry/patches.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# -*- coding: utf-8 -*-
-# copyright 2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""cubicweb-sentry monkey patches"""
-
-import logging
-
-from logilab.common.decorators import monkeypatch
-from cubicweb.web.application import CubicWebPublisher
-
-
-logger = logging.getLogger(__name__)
-_SENTRY_DSN = None
-
-
-def add_versions(req, extra):
-    if hasattr(req, 'get_versions'):
-        versions = req.get_versions()
-    elif hasattr(req, 'registry'):
-        versions = req.registry['cubicweb.repository'].get_versions()
-    else:
-        return
-    for k, v in versions.items():
-        version = '.'.join([str(x) for x in v])
-        if k == 'cubicweb':
-            extra['release'] = version
-        else:
-            extra.setdefault('modules', {})[k] = version
-
-
-def add_sentry_logging_handler(client, log_level):
-    from raven.handlers.logging import SentryHandler
-    from raven.conf import setup_logging
-    handler = SentryHandler(client)
-    handler.setLevel(log_level)
-    setup_logging(handler, exclude=(__name__,))
-
-
-def add_sentry_support(dsn, log_level, release):
-    """ Add support for Sentry (getsentry.com)
-    """
-    global _SENTRY_DSN
-    if _SENTRY_DSN:
-        assert dsn == _SENTRY_DSN
-        return
-    _SENTRY_DSN = dsn
-
-    from raven import Client
-    client = Client(_SENTRY_DSN, release=release)
-
-    if log_level:
-        add_sentry_logging_handler(client, log_level)
-
-    def handle_error(req):
-        extra = {'user': req.user.dc_title(),
-                 'useragent': req.useragent(),
-                 'url': req.url(),
-                 'form': req.form,
-                 }
-        try:
-            extra['referer'] = req.get_header('Referer')
-        except KeyError:
-            pass
-        try:
-            add_versions(req, extra)
-        except AttributeError:
-            pass
-        ident = client.get_ident(client.captureException(extra=extra))
-        msg = "Exception caught; reference is %s" % ident
-        logger.critical(msg)
-
-    cw_error_handler = CubicWebPublisher.error_handler
-
-    @monkeypatch(CubicWebPublisher, 'error_handler')
-    def error_handler(self, req, ex, tb=False):
-        if not req.ajax_request:
-            # else ajax_error_handler will be called, don't send the error
-            # twice
-            handle_error(req)
-        return cw_error_handler(self, req, ex, tb)
-
-    cw_ajax_error_handler = CubicWebPublisher.ajax_error_handler
-
-    @monkeypatch(CubicWebPublisher, 'ajax_error_handler')
-    def ajax_error_handler(self, req, ex):
-        handle_error(req)
-        return cw_ajax_error_handler(self, req, ex)
diff --git a/cubicweb_sentry/views.py b/cubicweb_sentry/views.py
deleted file mode 100644
--- a/cubicweb_sentry/views.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-# copyright 2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""cubicweb-sentry views/forms/actions/components for web ui"""
-
-
-from cubicweb_sentry.patches import add_sentry_support
-import logging
-
-
-def registration_callback(vreg):
-    config = vreg.config
-    sentry_dsn = config['sentry-dsn']
-    log_level_choice = config['sentry-log-level']
-    if log_level_choice:
-        log_level = getattr(logging, log_level_choice.upper())
-    else:
-        log_level = None
-
-    released_cube = config.cubes()[0]
-    release = '.'.join(map(str, config.cube_version(released_cube)))
-
-    if sentry_dsn:
-        add_sentry_support(sentry_dsn, log_level, release)
diff --git a/debian/py3dist-overrides b/debian/py3dist-overrides
--- a/debian/py3dist-overrides
+++ b/debian/py3dist-overrides
@@ -1,2 +1,2 @@
 cubicweb python3-cubicweb
-raven python3-raven
+sentry-sdk python3-sentry
diff --git a/debian/pydist-overrides b/debian/pydist-overrides
--- a/debian/pydist-overrides
+++ b/debian/pydist-overrides
@@ -1,2 +1,2 @@
 cubicweb python-cubicweb
-raven python-raven
+sentry-sdk python-sentry
diff --git a/test/data/bootstrap_cubes b/test/data/bootstrap_cubes
deleted file mode 100644
--- a/test/data/bootstrap_cubes
+++ /dev/null
@@ -1,1 +0,0 @@
-sentry
diff --git a/test/pytestconf.py b/test/pytestconf.py
deleted file mode 100644
--- a/test/pytestconf.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact at logilab.fr
-#
-# This file is part of CubicWeb.
-#
-# CubicWeb is free software: you can redistribute it and/or modify it under the
-# terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-"""
-import os
-import sys
-
-from logilab.common.pytest import PyTester
-
-
-def getlogin():
-    """avoid usinng os.getlogin() because of strange tty / stdin problems
-    (man 3 getlogin)
-    Another solution would be to use $LOGNAME, $USER or $USERNAME
-    """
-    if sys.platform == 'win32':
-        return os.environ.get('USERNAME') or 'cubicweb'
-    import pwd
-    return pwd.getpwuid(os.getuid())[0]
-
-
-def update_parser(parser):
-    login = getlogin()
-    parser.add_option('-r', '--rebuild-database', dest='rebuild_db',
-                      default=False, action="store_true",
-                      help="remove tmpdb and rebuilds the test database")
-    parser.add_option('-u', '--dbuser', dest='dbuser', action='store',
-                      default=login, help="database user")
-    parser.add_option('-w', '--dbpassword', dest='dbpassword', action='store',
-                      default=login, help="database user's password")
-    parser.add_option('-n', '--dbname', dest='dbname', action='store',
-                      default=None, help="database name")
-    parser.add_option('--euser', dest='euser', action='store',
-                      default=login, help="euser name")
-    parser.add_option('--epassword', dest='epassword', action='store',
-                      default=login, help="euser's password' name")
-    return parser
-
-
-class CustomPyTester(PyTester):
-    def __init__(self, cvg, options):
-        super(CustomPyTester, self).__init__(cvg, options)
-        if options.rebuild_db:
-            os.unlink('tmpdb')
-            os.unlink('tmpdb-template')
diff --git a/test/test_sentry.py b/test/test_sentry.py
deleted file mode 100644
--- a/test/test_sentry.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# copyright 2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr -- mailto:contact at logilab.fr
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
-
-
-import logging
-
-import mock
-
-from cubicweb.devtools import testlib
-
-from cubicweb_sentry.patches import (
-    add_sentry_support,
-    logger as sentry_cube_logger,
-)
-
-
-class LoggerConfigTC(testlib.CubicWebTC):
-
-    @classmethod
-    def setUpClass(cls):
-        super(LoggerConfigTC, cls).setUpClass()
-        add_sentry_support('https://public:secret@example.com/123',
-                           logging.ERROR, '1.2.3')
-
-    def test_emit_on_exception(self):
-        logger = logging.getLogger('test_logger')
-        logger.setLevel(logging.ERROR)
-        try:
-            raise Exception("Excepted")
-        except Exception:
-            with mock.patch(
-                    'raven.handlers.logging.SentryHandler.emit') as emit:
-                logger.exception("Catched")
-
-                emit.assert_called_once()
-                self.assertEqual(emit.call_args[0][0].msg, "Catched")
-
-    def test_do_not_emit_on_sentry_cube_logs(self):
-        try:
-            raise Exception("Excepted")
-        except Exception:
-            with mock.patch(
-                    'raven.handlers.logging.SentryHandler.emit') as emit:
-                sentry_cube_logger.exception("Not emitted")
-
-                emit.assert_not_called()
diff --git a/tox.ini b/tox.ini
--- a/tox.ini
+++ b/tox.ini
@@ -1,13 +1,5 @@
 [tox]
-envlist = py27,py3,flake8,check-manifest
-
-[testenv]
-sitepackages = true
-deps =
-  pytest
-  mock
-commands =
-  {envpython} -m pytest {posargs:test}
+envlist = flake8,check-manifest
 
 [testenv:check-manifest]
 skip_install = true


More information about the cubicweb-devel mailing list