diff --git a/swh/core/api/tests/test_gunicorn.py b/swh/core/api/tests/test_gunicorn.py index cb8b470..1f3cacb 100644 --- a/swh/core/api/tests/test_gunicorn.py +++ b/swh/core/api/tests/test_gunicorn.py @@ -1,101 +1,107 @@ # Copyright (C) 2019 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 os import pkg_resources from unittest.mock import patch import swh.core.api.gunicorn_config as gunicorn_config def test_post_fork_default(): with patch('sentry_sdk.init') as sentry_sdk_init: gunicorn_config.post_fork(None, None) sentry_sdk_init.assert_not_called() def test_post_fork_with_dsn_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'}): gunicorn_config.post_fork(None, None) sentry_sdk_init.assert_called_once_with( dsn='test_dsn', integrations=[flask_integration], debug=False, release=None, + environment=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_SENTRY_ENVIRONMENT': 'tests', '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, + environment='tests', ) def test_post_fork_debug(): 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_SENTRY_DEBUG': '1'}): gunicorn_config.post_fork(None, None) sentry_sdk_init.assert_called_once_with( dsn='test_dsn', integrations=[flask_integration], debug=True, release=None, + environment=None, ) def test_post_fork_no_flask(): with patch('sentry_sdk.init') as sentry_sdk_init: with patch.dict(os.environ, {'SWH_SENTRY_DSN': 'test_dsn'}): gunicorn_config.post_fork(None, None, flask=False) sentry_sdk_init.assert_called_once_with( dsn='test_dsn', integrations=[], debug=False, release=None, + environment=None, ) def test_post_fork_extras(): 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'}): gunicorn_config.post_fork( None, None, sentry_integrations=['foo'], extra_sentry_kwargs={'bar': 'baz'}) sentry_sdk_init.assert_called_once_with( dsn='test_dsn', integrations=['foo', flask_integration], debug=False, bar='baz', release=None, + environment=None, ) diff --git a/swh/core/sentry.py b/swh/core/sentry.py index 50ebaa2..536bc4b 100644 --- a/swh/core/sentry.py +++ b/swh/core/sentry.py @@ -1,35 +1,37 @@ # 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') + environment = os.environ.get('SWH_SENTRY_ENVIRONMENT') if sentry_dsn: import sentry_sdk sentry_sdk.init( release=get_sentry_release(), + environment=environment, 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 index 662f3a7..1c7ec19 100644 --- a/swh/core/tests/test_cli.py +++ b/swh/core/tests/test_cli.py @@ -1,293 +1,298 @@ # Copyright (C) 2019 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 logging import pkg_resources import textwrap from unittest.mock import patch import click from click.testing import CliRunner import pytest help_msg = '''Usage: swh [OPTIONS] COMMAND [ARGS]... Command line interface for Software Heritage. Options: -l, --log-level [NOTSET|DEBUG|INFO|WARNING|ERROR|CRITICAL] Log level (defaults to INFO). --log-config FILENAME Python yaml logging configuration file. --sentry-dsn TEXT DSN of the Sentry instance to report to -h, --help Show this message and exit. Notes: If both options are present, --log-level will override the root logger configuration set in --log-config. The --log-config YAML must conform to the logging.config.dictConfig schema documented at https://docs.python.org/3/library/logging.config.html. ''' def test_swh_help(swhmain): runner = CliRunner() result = runner.invoke(swhmain, ['-h']) assert result.exit_code == 0 assert result.output.startswith(help_msg) result = runner.invoke(swhmain, ['--help']) assert result.exit_code == 0 assert result.output.startswith(help_msg) def test_command(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: result = runner.invoke(swhmain, ['test']) sentry_sdk_init.assert_not_called() assert result.exit_code == 0 assert result.output.strip() == 'Hello SWH!' def test_loglevel_default(caplog, swhmain): @swhmain.command(name='test') @click.pass_context def swhtest(ctx): assert logging.root.level == 20 click.echo('Hello SWH!') runner = CliRunner() result = runner.invoke(swhmain, ['test']) assert result.exit_code == 0 print(result.output) assert result.output.strip() == '''Hello SWH!''' def test_loglevel_error(caplog, swhmain): @swhmain.command(name='test') @click.pass_context def swhtest(ctx): assert logging.root.level == 40 click.echo('Hello SWH!') runner = CliRunner() result = runner.invoke(swhmain, ['-l', 'ERROR', 'test']) assert result.exit_code == 0 assert result.output.strip() == '''Hello SWH!''' def test_loglevel_debug(caplog, swhmain): @swhmain.command(name='test') @click.pass_context def swhtest(ctx): assert logging.root.level == 10 click.echo('Hello SWH!') runner = CliRunner() result = runner.invoke(swhmain, ['-l', 'DEBUG', 'test']) assert result.exit_code == 0 assert result.output.strip() == '''Hello SWH!''' def test_sentry(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: result = runner.invoke(swhmain, ['--sentry-dsn', 'test_dsn', 'test']) assert result.exit_code == 0 assert result.output.strip() == '''Hello SWH!''' sentry_sdk_init.assert_called_once_with( dsn='test_dsn', debug=False, integrations=[], release=None, + environment=None, ) def test_sentry_debug(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: result = runner.invoke( swhmain, ['--sentry-dsn', 'test_dsn', '--sentry-debug', 'test']) assert result.exit_code == 0 assert result.output.strip() == '''Hello SWH!''' sentry_sdk_init.assert_called_once_with( dsn='test_dsn', debug=True, integrations=[], release=None, + environment=None, ) def test_sentry_env(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_SENTRY_DEBUG': '1', } result = runner.invoke( swhmain, ['test'], env=env, auto_envvar_prefix='SWH') assert result.exit_code == 0 assert result.output.strip() == '''Hello SWH!''' sentry_sdk_init.assert_called_once_with( dsn='test_dsn', debug=True, integrations=[], release=None, + environment=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', + 'SWH_SENTRY_ENVIRONMENT': 'tests', } 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, + environment='tests', ) @pytest.fixture def log_config_path(tmp_path): log_config = textwrap.dedent('''\ --- version: 1 formatters: formatter: format: 'custom format:%(name)s:%(levelname)s:%(message)s' handlers: console: class: logging.StreamHandler stream: ext://sys.stdout formatter: formatter level: DEBUG root: level: DEBUG handlers: - console loggers: dontshowdebug: level: INFO ''') (tmp_path / 'log_config.yml').write_text(log_config) yield str(tmp_path / 'log_config.yml') def test_log_config(caplog, log_config_path, swhmain): @swhmain.command(name='test') @click.pass_context def swhtest(ctx): logging.debug('Root log debug') logging.info('Root log info') logging.getLogger('dontshowdebug').debug('Not shown') logging.getLogger('dontshowdebug').info('Shown') runner = CliRunner() result = runner.invoke( swhmain, [ '--log-config', log_config_path, 'test', ], ) assert result.exit_code == 0 assert result.output.strip() == '\n'.join([ 'custom format:root:DEBUG:Root log debug', 'custom format:root:INFO:Root log info', 'custom format:dontshowdebug:INFO:Shown', ]) def test_log_config_log_level_interaction(caplog, log_config_path, swhmain): @swhmain.command(name='test') @click.pass_context def swhtest(ctx): logging.debug('Root log debug') logging.info('Root log info') logging.getLogger('dontshowdebug').debug('Not shown') logging.getLogger('dontshowdebug').info('Shown') runner = CliRunner() result = runner.invoke( swhmain, [ '--log-config', log_config_path, '--log-level', 'INFO', 'test', ], ) assert result.exit_code == 0 assert result.output.strip() == '\n'.join([ 'custom format:root:INFO:Root log info', 'custom format:dontshowdebug:INFO:Shown', ]) def test_aliased_command(swhmain): @swhmain.command(name='canonical-test') @click.pass_context def swhtest(ctx): 'A test command.' click.echo('Hello SWH!') swhmain.add_alias(swhtest, 'othername') runner = CliRunner() # check we have only 'canonical-test' listed in the usage help msg result = runner.invoke(swhmain, ['-h']) assert result.exit_code == 0 assert 'canonical-test A test command.' in result.output assert 'othername' not in result.output # check we can execute the cmd with 'canonical-test' result = runner.invoke(swhmain, ['canonical-test']) assert result.exit_code == 0 assert result.output.strip() == '''Hello SWH!''' # check we can also execute the cmd with the alias 'othername' result = runner.invoke(swhmain, ['othername']) assert result.exit_code == 0 assert result.output.strip() == '''Hello SWH!'''