mirror of https://github.com/rq/rq.git
Delete legacy.py (#2083)
* Delete legacy.py * Deleted Sentry's built in integration. * Fixed ruff warnings.
This commit is contained in:
parent
87af9b51e9
commit
4c28ec617a
|
@ -34,4 +34,4 @@ jobs:
|
||||||
- name: Lint with ruff
|
- name: Lint with ruff
|
||||||
run: |
|
run: |
|
||||||
# stop the build if there are Python syntax errors.
|
# stop the build if there are Python syntax errors.
|
||||||
ruff check --show-source rq tests
|
ruff check --output-format=full rq tests
|
||||||
|
|
|
@ -1,49 +1,8 @@
|
||||||
---
|
---
|
||||||
title: "RQ: Sending exceptions to Sentry"
|
title: "RQ: Sending Exceptions to Sentry"
|
||||||
layout: patterns
|
layout: patterns
|
||||||
---
|
---
|
||||||
|
|
||||||
## Sending Exceptions to Sentry
|
## Sending Exceptions to Sentry
|
||||||
|
|
||||||
[Sentry](https://www.getsentry.com/) is a popular exception gathering service.
|
Please visit Sentry's [RQ integration page](https://docs.sentry.io/platforms/python/integrations/rq/).
|
||||||
RQ allows you to very easily send job exceptions to Sentry. To do this, you'll
|
|
||||||
need to have [sentry-sdk](https://pypi.org/project/sentry-sdk/) installed.
|
|
||||||
|
|
||||||
There are a few ways to start sending job exceptions to Sentry.
|
|
||||||
|
|
||||||
|
|
||||||
### Configuring Sentry Through CLI
|
|
||||||
|
|
||||||
Simply invoke the `rqworker` script using the ``--sentry-dsn`` argument.
|
|
||||||
|
|
||||||
```console
|
|
||||||
rq worker --sentry-dsn https://my-dsn@sentry.io/123
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Configuring Sentry Through a Config File
|
|
||||||
|
|
||||||
Declare `SENTRY_DSN` in RQ's config file like this:
|
|
||||||
|
|
||||||
```python
|
|
||||||
SENTRY_DSN = 'https://my-dsn@sentry.io/123'
|
|
||||||
```
|
|
||||||
|
|
||||||
And run RQ's worker with your config file:
|
|
||||||
|
|
||||||
```console
|
|
||||||
rq worker -c my_settings
|
|
||||||
```
|
|
||||||
|
|
||||||
Visit [this page](https://python-rq.org/docs/workers/#using-a-config-file)
|
|
||||||
to read more about running RQ using a config file.
|
|
||||||
|
|
||||||
|
|
||||||
### Configuring Sentry Through Environment Variable
|
|
||||||
|
|
||||||
Simple set `RQ_SENTRY_DSN` in your environment variable and RQ will
|
|
||||||
automatically start Sentry integration for you.
|
|
||||||
|
|
||||||
```console
|
|
||||||
RQ_SENTRY_DSN="https://my-dsn@sentry.io/123" rq worker
|
|
||||||
```
|
|
|
@ -85,7 +85,6 @@ dependencies = [
|
||||||
"pytest",
|
"pytest",
|
||||||
"pytest-cov",
|
"pytest-cov",
|
||||||
"ruff",
|
"ruff",
|
||||||
"sentry-sdk<2",
|
|
||||||
"tox",
|
"tox",
|
||||||
]
|
]
|
||||||
[tool.hatch.envs.test.scripts]
|
[tool.hatch.envs.test.scripts]
|
||||||
|
@ -99,7 +98,7 @@ skip-string-normalization = true
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
# Set what ruff should check for.
|
# Set what ruff should check for.
|
||||||
# See https://beta.ruff.rs/docs/rules/ for a list of rules.
|
# See https://beta.ruff.rs/docs/rules/ for a list of rules.
|
||||||
select = [
|
lint.select = [
|
||||||
"E", # pycodestyle errors
|
"E", # pycodestyle errors
|
||||||
"F", # pyflakes errors
|
"F", # pyflakes errors
|
||||||
"I", # import sorting
|
"I", # import sorting
|
||||||
|
@ -108,7 +107,7 @@ select = [
|
||||||
line-length = 120 # To match black.
|
line-length = 120 # To match black.
|
||||||
target-version = "py38"
|
target-version = "py38"
|
||||||
|
|
||||||
[tool.ruff.isort]
|
[tool.ruff.lint.isort]
|
||||||
known-first-party = ["rq"]
|
known-first-party = ["rq"]
|
||||||
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
|
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
|
||||||
|
|
||||||
|
|
|
@ -182,9 +182,6 @@ def info(cli_config, interval, raw, only_queues, only_workers, by_queue, queues,
|
||||||
@click.option('--disable-job-desc-logging', is_flag=True, help='Turn off description logging.')
|
@click.option('--disable-job-desc-logging', is_flag=True, help='Turn off description logging.')
|
||||||
@click.option('--verbose', '-v', is_flag=True, help='Show more output')
|
@click.option('--verbose', '-v', is_flag=True, help='Show more output')
|
||||||
@click.option('--quiet', '-q', is_flag=True, help='Show less output')
|
@click.option('--quiet', '-q', is_flag=True, help='Show less output')
|
||||||
@click.option('--sentry-ca-certs', envvar='RQ_SENTRY_CA_CERTS', help='Path to CRT file for Sentry DSN')
|
|
||||||
@click.option('--sentry-debug', envvar='RQ_SENTRY_DEBUG', help='Enable debug')
|
|
||||||
@click.option('--sentry-dsn', envvar='RQ_SENTRY_DSN', help='Report exceptions to this Sentry DSN')
|
|
||||||
@click.option('--exception-handler', help='Exception handler(s) to use', multiple=True)
|
@click.option('--exception-handler', help='Exception handler(s) to use', multiple=True)
|
||||||
@click.option('--pid', help='Write the process ID number to a file at the specified path')
|
@click.option('--pid', help='Write the process ID number to a file at the specified path')
|
||||||
@click.option('--disable-default-exception-handler', '-d', is_flag=True, help='Disable RQ\'s default exception handler')
|
@click.option('--disable-default-exception-handler', '-d', is_flag=True, help='Disable RQ\'s default exception handler')
|
||||||
|
@ -209,9 +206,6 @@ def worker(
|
||||||
disable_job_desc_logging,
|
disable_job_desc_logging,
|
||||||
verbose,
|
verbose,
|
||||||
quiet,
|
quiet,
|
||||||
sentry_ca_certs,
|
|
||||||
sentry_debug,
|
|
||||||
sentry_dsn,
|
|
||||||
exception_handler,
|
exception_handler,
|
||||||
pid,
|
pid,
|
||||||
disable_default_exception_handler,
|
disable_default_exception_handler,
|
||||||
|
@ -229,9 +223,6 @@ def worker(
|
||||||
settings = read_config_file(cli_config.config) if cli_config.config else {}
|
settings = read_config_file(cli_config.config) if cli_config.config else {}
|
||||||
# Worker specific default arguments
|
# Worker specific default arguments
|
||||||
queues = queues or settings.get('QUEUES', ['default'])
|
queues = queues or settings.get('QUEUES', ['default'])
|
||||||
sentry_ca_certs = sentry_ca_certs or settings.get('SENTRY_CA_CERTS')
|
|
||||||
sentry_debug = sentry_debug or settings.get('SENTRY_DEBUG')
|
|
||||||
sentry_dsn = sentry_dsn or settings.get('SENTRY_DSN')
|
|
||||||
name = name or settings.get('NAME')
|
name = name or settings.get('NAME')
|
||||||
dict_config = settings.get('DICT_CONFIG')
|
dict_config = settings.get('DICT_CONFIG')
|
||||||
|
|
||||||
|
@ -288,13 +279,6 @@ def worker(
|
||||||
serializer=serializer,
|
serializer=serializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Should we configure Sentry?
|
|
||||||
if sentry_dsn:
|
|
||||||
sentry_opts = {"ca_certs": sentry_ca_certs, "debug": sentry_debug}
|
|
||||||
from rq.contrib.sentry import register_sentry
|
|
||||||
|
|
||||||
register_sentry(sentry_dsn, **sentry_opts)
|
|
||||||
|
|
||||||
# if --verbose or --quiet, override --logging_level
|
# if --verbose or --quiet, override --logging_level
|
||||||
if verbose or quiet:
|
if verbose or quiet:
|
||||||
logging_level = None
|
logging_level = None
|
||||||
|
@ -445,9 +429,6 @@ def enqueue(
|
||||||
@main.command()
|
@main.command()
|
||||||
@click.option('--burst', '-b', is_flag=True, help='Run in burst mode (quit after all work is done)')
|
@click.option('--burst', '-b', is_flag=True, help='Run in burst mode (quit after all work is done)')
|
||||||
@click.option('--logging-level', '-l', type=str, default="INFO", help='Set logging level')
|
@click.option('--logging-level', '-l', type=str, default="INFO", help='Set logging level')
|
||||||
@click.option('--sentry-ca-certs', envvar='RQ_SENTRY_CA_CERTS', help='Path to CRT file for Sentry DSN')
|
|
||||||
@click.option('--sentry-debug', envvar='RQ_SENTRY_DEBUG', help='Enable debug')
|
|
||||||
@click.option('--sentry-dsn', envvar='RQ_SENTRY_DSN', help='Report exceptions to this Sentry DSN')
|
|
||||||
@click.option('--verbose', '-v', is_flag=True, help='Show more output')
|
@click.option('--verbose', '-v', is_flag=True, help='Show more output')
|
||||||
@click.option('--quiet', '-q', is_flag=True, help='Show less output')
|
@click.option('--quiet', '-q', is_flag=True, help='Show less output')
|
||||||
@click.option('--log-format', type=str, default=DEFAULT_LOGGING_FORMAT, help='Set the format of the logs')
|
@click.option('--log-format', type=str, default=DEFAULT_LOGGING_FORMAT, help='Set the format of the logs')
|
||||||
|
@ -462,9 +443,6 @@ def worker_pool(
|
||||||
logging_level,
|
logging_level,
|
||||||
queues,
|
queues,
|
||||||
serializer,
|
serializer,
|
||||||
sentry_ca_certs,
|
|
||||||
sentry_debug,
|
|
||||||
sentry_dsn,
|
|
||||||
verbose,
|
verbose,
|
||||||
quiet,
|
quiet,
|
||||||
log_format,
|
log_format,
|
||||||
|
@ -478,9 +456,6 @@ def worker_pool(
|
||||||
settings = read_config_file(cli_config.config) if cli_config.config else {}
|
settings = read_config_file(cli_config.config) if cli_config.config else {}
|
||||||
# Worker specific default arguments
|
# Worker specific default arguments
|
||||||
queue_names: List[str] = queues or settings.get('QUEUES', ['default'])
|
queue_names: List[str] = queues or settings.get('QUEUES', ['default'])
|
||||||
sentry_ca_certs = sentry_ca_certs or settings.get('SENTRY_CA_CERTS')
|
|
||||||
sentry_debug = sentry_debug or settings.get('SENTRY_DEBUG')
|
|
||||||
sentry_dsn = sentry_dsn or settings.get('SENTRY_DSN')
|
|
||||||
|
|
||||||
setup_loghandlers_from_args(verbose, quiet, date_format, log_format)
|
setup_loghandlers_from_args(verbose, quiet, date_format, log_format)
|
||||||
|
|
||||||
|
@ -513,13 +488,6 @@ def worker_pool(
|
||||||
)
|
)
|
||||||
pool.start(burst=burst, logging_level=logging_level)
|
pool.start(burst=burst, logging_level=logging_level)
|
||||||
|
|
||||||
# Should we configure Sentry?
|
|
||||||
if sentry_dsn:
|
|
||||||
sentry_opts = {"ca_certs": sentry_ca_certs, "debug": sentry_debug}
|
|
||||||
from rq.contrib.sentry import register_sentry
|
|
||||||
|
|
||||||
register_sentry(sentry_dsn, **sentry_opts)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
from rq import Worker, get_current_connection
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup_ghosts(conn=None, worker_class=Worker):
|
|
||||||
"""
|
|
||||||
RQ versions < 0.3.6 suffered from a race condition where workers, when
|
|
||||||
abruptly terminated, did not have a chance to clean up their worker
|
|
||||||
registration, leading to reports of ghosted workers in `rqinfo`. Since
|
|
||||||
0.3.6, new worker registrations automatically expire, and the worker will
|
|
||||||
make sure to refresh the registrations as long as it's alive.
|
|
||||||
|
|
||||||
This function will clean up any of such legacy ghosted workers.
|
|
||||||
"""
|
|
||||||
conn = conn if conn else get_current_connection()
|
|
||||||
for worker in worker_class.all(connection=conn):
|
|
||||||
if conn.ttl(worker.key) == -1:
|
|
||||||
ttl = worker.worker_ttl
|
|
||||||
conn.expire(worker.key, ttl)
|
|
||||||
logger.info('Marked ghosted worker {0} to expire in {1} seconds.'.format(worker.name, ttl))
|
|
|
@ -1,8 +0,0 @@
|
||||||
def register_sentry(sentry_dsn, **opts):
|
|
||||||
"""Given a Raven client and an RQ worker, registers exception handlers
|
|
||||||
with the worker so exceptions are logged to Sentry.
|
|
||||||
"""
|
|
||||||
import sentry_sdk
|
|
||||||
from sentry_sdk.integrations.rq import RqIntegration
|
|
||||||
|
|
||||||
sentry_sdk.init(sentry_dsn, integrations=[RqIntegration()], **opts)
|
|
|
@ -1,5 +1,4 @@
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .intermediate_queue import IntermediateQueue
|
from .intermediate_queue import IntermediateQueue
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
REDIS_HOST = "testhost.example.com"
|
|
||||||
SENTRY_DSN = 'https://123@sentry.io/123'
|
|
|
@ -1,54 +0,0 @@
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from click.testing import CliRunner
|
|
||||||
|
|
||||||
from rq import Queue
|
|
||||||
from rq.cli import main
|
|
||||||
from rq.cli.helpers import read_config_file
|
|
||||||
from rq.contrib.sentry import register_sentry
|
|
||||||
from rq.worker import SimpleWorker
|
|
||||||
from tests import RQTestCase
|
|
||||||
from tests.fixtures import div_by_zero
|
|
||||||
|
|
||||||
|
|
||||||
class FakeSentry:
|
|
||||||
servers = []
|
|
||||||
|
|
||||||
def captureException(self, *args, **kwds): # noqa
|
|
||||||
pass # we cannot check this, because worker forks
|
|
||||||
|
|
||||||
|
|
||||||
class TestSentry(RQTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
db_num = self.connection.connection_pool.connection_kwargs['db']
|
|
||||||
self.redis_url = 'redis://127.0.0.1:6379/%d' % db_num
|
|
||||||
|
|
||||||
def test_reading_dsn_from_file(self):
|
|
||||||
settings = read_config_file('tests.config_files.sentry')
|
|
||||||
self.assertIn('SENTRY_DSN', settings)
|
|
||||||
self.assertEqual(settings['SENTRY_DSN'], 'https://123@sentry.io/123')
|
|
||||||
|
|
||||||
@mock.patch('rq.contrib.sentry.register_sentry')
|
|
||||||
def test_cli_flag(self, mocked):
|
|
||||||
"""rq worker -u <url> -b --exception-handler <handler>"""
|
|
||||||
# connection = Redis.from_url(self.redis_url)
|
|
||||||
runner = CliRunner()
|
|
||||||
runner.invoke(main, ['worker', '-u', self.redis_url, '-b', '--sentry-dsn', 'https://1@sentry.io/1'])
|
|
||||||
self.assertEqual(mocked.call_count, 1)
|
|
||||||
|
|
||||||
runner.invoke(main, ['worker-pool', '-u', self.redis_url, '-b', '--sentry-dsn', 'https://1@sentry.io/1'])
|
|
||||||
self.assertEqual(mocked.call_count, 2)
|
|
||||||
|
|
||||||
def test_failure_capture(self):
|
|
||||||
"""Test failure is captured by Sentry SDK"""
|
|
||||||
from sentry_sdk import Hub
|
|
||||||
|
|
||||||
hub = Hub.current
|
|
||||||
self.assertIsNone(hub.last_event_id())
|
|
||||||
queue = Queue(connection=self.connection)
|
|
||||||
queue.enqueue(div_by_zero)
|
|
||||||
worker = SimpleWorker(queues=[queue], connection=self.connection)
|
|
||||||
register_sentry('https://123@sentry.io/123')
|
|
||||||
worker.work(burst=True)
|
|
||||||
self.assertIsNotNone(hub.last_event_id())
|
|
2
tox.ini
2
tox.ini
|
@ -8,7 +8,6 @@ deps=
|
||||||
psutil
|
psutil
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
sentry-sdk
|
|
||||||
passenv=
|
passenv=
|
||||||
RUN_SSL_TESTS
|
RUN_SSL_TESTS
|
||||||
|
|
||||||
|
@ -51,7 +50,6 @@ skipdist = True
|
||||||
basepython = python3.10
|
basepython = python3.10
|
||||||
deps=
|
deps=
|
||||||
pytest
|
pytest
|
||||||
sentry-sdk
|
|
||||||
psutil
|
psutil
|
||||||
passenv=
|
passenv=
|
||||||
RUN_SSL_TESTS
|
RUN_SSL_TESTS
|
||||||
|
|
Loading…
Reference in New Issue