diff --git a/swh/core/api/gunicorn_config.py b/swh/core/api/gunicorn_config.py --- a/swh/core/api/gunicorn_config.py +++ b/swh/core/api/gunicorn_config.py @@ -11,34 +11,20 @@ May be imported by gunicorn using `--config 'python:swh.core.api.gunicorn_config'`.""" -import os - -def _init_sentry( - sentry_dsn, *, flask=True, integrations=None, extra_kwargs={}): - import sentry_sdk - - integrations = integrations or [] - - if flask: - from sentry_sdk.integrations.flask import FlaskIntegration - integrations.append(FlaskIntegration()) - - sentry_sdk.init( - dsn=sentry_dsn, - integrations=integrations, - debug=bool(os.environ.get('SWH_SENTRY_DEBUG')), - **extra_kwargs, - ) +from ..sentry import init_sentry def post_fork( server, worker, *, default_sentry_dsn=None, flask=True, sentry_integrations=None, extra_sentry_kwargs={}): - # Initializes sentry as soon as possible in gunicorn's worker processes. - sentry_dsn = os.environ.get('SWH_SENTRY_DSN', default_sentry_dsn) - if sentry_dsn: - _init_sentry( - sentry_dsn, flask=flask, integrations=sentry_integrations, - extra_kwargs=extra_sentry_kwargs) + + sentry_integrations = sentry_integrations or [] + if flask: + from sentry_sdk.integrations.flask import FlaskIntegration + sentry_integrations.append(FlaskIntegration()) + + init_sentry( + default_sentry_dsn, integrations=sentry_integrations, + extra_kwargs=extra_sentry_kwargs) diff --git a/swh/core/api/tests/test_gunicorn.py b/swh/core/api/tests/test_gunicorn.py --- a/swh/core/api/tests/test_gunicorn.py +++ b/swh/core/api/tests/test_gunicorn.py @@ -4,6 +4,7 @@ # See top-level LICENSE file for more information import os +import pkg_resources from unittest.mock import patch import swh.core.api.gunicorn_config as gunicorn_config @@ -28,6 +29,26 @@ dsn='test_dsn', integrations=[flask_integration], debug=False, + release=None, + ) + + +def test_post_fork_with_package_env(): + flask_integration = object() # unique object to check for equality + with patch('sentry_sdk.integrations.flask.FlaskIntegration', + new=lambda: flask_integration): + with patch('sentry_sdk.init') as sentry_sdk_init: + with patch.dict(os.environ, {'SWH_SENTRY_DSN': 'test_dsn', + 'SWH_MAIN_PACKAGE': 'swh.core'}): + gunicorn_config.post_fork(None, None) + + version = pkg_resources.get_distribution('swh.core').version + + sentry_sdk_init.assert_called_once_with( + dsn='test_dsn', + integrations=[flask_integration], + debug=False, + release='swh.core@' + version, ) @@ -44,6 +65,7 @@ dsn='test_dsn', integrations=[flask_integration], debug=True, + release=None, ) @@ -56,6 +78,7 @@ dsn='test_dsn', integrations=[], debug=False, + release=None, ) @@ -74,4 +97,5 @@ integrations=['foo', flask_integration], debug=False, bar='baz', + release=None, ) diff --git a/swh/core/cli/__init__.py b/swh/core/cli/__init__.py --- a/swh/core/cli/__init__.py +++ b/swh/core/cli/__init__.py @@ -9,9 +9,10 @@ import click import pkg_resources -import sentry_sdk import yaml +from ..sentry import init_sentry + LOG_LEVEL_NAMES = ['NOTSET', 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) @@ -78,8 +79,7 @@ signal.signal(signal.SIGTERM, clean_exit_on_signal) signal.signal(signal.SIGINT, clean_exit_on_signal) - if sentry_dsn: - sentry_sdk.init(dsn=sentry_dsn, debug=sentry_debug) + init_sentry(sentry_dsn, debug=sentry_debug) if log_level is None and log_config is None: log_level = 'INFO' diff --git a/swh/core/sentry.py b/swh/core/sentry.py new file mode 100644 --- /dev/null +++ b/swh/core/sentry.py @@ -0,0 +1,35 @@ +# Copyright (C) 2019-2020 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import pkg_resources +import os + + +def get_sentry_release(): + main_package = os.environ.get('SWH_MAIN_PACKAGE') + if main_package: + version = pkg_resources.get_distribution(main_package).version + return f'{main_package}@{version}' + else: + return None + + +def init_sentry( + sentry_dsn, *, debug=None, integrations=[], + extra_kwargs={}): + if debug is None: + debug = bool(os.environ.get('SWH_SENTRY_DEBUG')) + sentry_dsn = sentry_dsn or os.environ.get('SWH_SENTRY_DSN') + + if sentry_dsn: + import sentry_sdk + + sentry_sdk.init( + release=get_sentry_release(), + dsn=sentry_dsn, + integrations=integrations, + debug=debug, + **extra_kwargs, + ) diff --git a/swh/core/tests/test_cli.py b/swh/core/tests/test_cli.py --- a/swh/core/tests/test_cli.py +++ b/swh/core/tests/test_cli.py @@ -4,6 +4,7 @@ # See top-level LICENSE file for more information import logging +import pkg_resources import textwrap from unittest.mock import patch @@ -111,6 +112,8 @@ sentry_sdk_init.assert_called_once_with( dsn='test_dsn', debug=False, + integrations=[], + release=None, ) @@ -129,6 +132,8 @@ sentry_sdk_init.assert_called_once_with( dsn='test_dsn', debug=True, + integrations=[], + release=None, ) @@ -151,6 +156,35 @@ sentry_sdk_init.assert_called_once_with( dsn='test_dsn', debug=True, + integrations=[], + release=None, + ) + + +def test_sentry_env_main_package(swhmain): + @swhmain.command(name='test') + @click.pass_context + def swhtest(ctx): + click.echo('Hello SWH!') + + runner = CliRunner() + with patch('sentry_sdk.init') as sentry_sdk_init: + env = { + 'SWH_SENTRY_DSN': 'test_dsn', + 'SWH_MAIN_PACKAGE': 'swh.core', + } + result = runner.invoke( + swhmain, ['test'], env=env, auto_envvar_prefix='SWH') + assert result.exit_code == 0 + + version = pkg_resources.get_distribution('swh.core').version + + assert result.output.strip() == '''Hello SWH!''' + sentry_sdk_init.assert_called_once_with( + dsn='test_dsn', + debug=False, + integrations=[], + release='swh.core@' + version, )