From 251e27def851383beabb5a49953b9b88d5be310e Mon Sep 17 00:00:00 2001 From: Yacine Date: Wed, 15 Mar 2023 11:13:37 -0400 Subject: [PATCH] Add decorator for Sentry tracing (#1089) * Add decorator for Sentry tracing --------- Co-authored-by: Anton Pirker Co-authored-by: Daniel Griesser --- .github/workflows/test-common.yml | 34 ++++-- .../workflows/test-integration-aiohttp.yml | 2 +- .github/workflows/test-integration-arq.yml | 2 +- .github/workflows/test-integration-asgi.yml | 2 +- .../workflows/test-integration-aws_lambda.yml | 2 +- .github/workflows/test-integration-beam.yml | 2 +- .github/workflows/test-integration-boto3.yml | 2 +- .github/workflows/test-integration-bottle.yml | 2 +- .github/workflows/test-integration-celery.yml | 2 +- .../workflows/test-integration-chalice.yml | 2 +- ...est-integration-cloud_resource_context.yml | 2 +- .github/workflows/test-integration-django.yml | 2 +- .github/workflows/test-integration-falcon.yml | 2 +- .../workflows/test-integration-fastapi.yml | 2 +- .github/workflows/test-integration-flask.yml | 2 +- .github/workflows/test-integration-gcp.yml | 2 +- .github/workflows/test-integration-gevent.yml | 2 +- .github/workflows/test-integration-httpx.yml | 2 +- .github/workflows/test-integration-huey.yml | 2 +- .../test-integration-opentelemetry.yml | 2 +- .../workflows/test-integration-pure_eval.yml | 2 +- .../workflows/test-integration-pymongo.yml | 2 +- .../workflows/test-integration-pyramid.yml | 2 +- .github/workflows/test-integration-quart.yml | 2 +- .github/workflows/test-integration-redis.yml | 2 +- .../test-integration-rediscluster.yml | 2 +- .../workflows/test-integration-requests.yml | 2 +- .github/workflows/test-integration-rq.yml | 2 +- .github/workflows/test-integration-sanic.yml | 2 +- .../workflows/test-integration-sqlalchemy.yml | 2 +- .../workflows/test-integration-starlette.yml | 2 +- .../workflows/test-integration-starlite.yml | 2 +- .../workflows/test-integration-tornado.yml | 2 +- .../workflows/test-integration-trytond.yml | 2 +- scripts/split-tox-gh-actions/ci-yaml.txt | 2 +- .../split-tox-gh-actions.py | 6 +- sentry_sdk/__init__.py | 2 + sentry_sdk/tracing.py | 38 ++++++- sentry_sdk/tracing_utils_py2.py | 45 ++++++++ sentry_sdk/tracing_utils_py3.py | 72 +++++++++++++ tests/integrations/asyncio/__init__.py | 3 - .../{test_asyncio.py => test_asyncio_py3.py} | 15 ++- tests/integrations/stdlib/test_httplib.py | 7 +- tests/tracing/test_decorator_py2.py | 50 +++++++++ tests/tracing/test_decorator_py3.py | 101 ++++++++++++++++++ tox.ini | 37 ++++--- 46 files changed, 399 insertions(+), 79 deletions(-) create mode 100644 sentry_sdk/tracing_utils_py2.py create mode 100644 sentry_sdk/tracing_utils_py3.py rename tests/integrations/asyncio/{test_asyncio.py => test_asyncio_py3.py} (94%) create mode 100644 tests/tracing/test_decorator_py2.py create mode 100644 tests/tracing/test_decorator_py3.py diff --git a/.github/workflows/test-common.yml b/.github/workflows/test-common.yml index fee76bec60..a2774939dc 100644 --- a/.github/workflows/test-common.yml +++ b/.github/workflows/test-common.yml @@ -1,4 +1,4 @@ -name: Test Common +name: Test common on: push: @@ -8,6 +8,12 @@ on: pull_request: +# Cancel in progress workflows on pull_requests. +# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + permissions: contents: read @@ -18,18 +24,20 @@ env: jobs: test: - name: Test Python ${{ matrix.python-version }}, ${{ matrix.os }} + name: common, python ${{ matrix.python-version }}, ${{ matrix.os }} runs-on: ${{ matrix.os }} timeout-minutes: 45 - continue-on-error: true + strategy: + fail-fast: false matrix: + python-version: ["2.7","3.5","3.6","3.7","3.8","3.9","3.10","3.11"] # python3.6 reached EOL and is no longer being supported on # new versions of hosted runners on Github Actions # ubuntu-20.04 is the last version that supported python3.6 # see https://github.com/actions/setup-python/issues/544#issuecomment-1332535877 os: [ubuntu-20.04] - python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] + steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 @@ -38,16 +46,28 @@ jobs: - name: Setup Test Env run: | - pip install codecov tox + pip install codecov "tox>=3,<4" - - name: Run Tests + - name: Test common timeout-minutes: 45 shell: bash run: | set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "py${{ matrix.python-version }}$" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch --ignore=tests/integrations + ./scripts/runtox.sh "py${{ matrix.python-version }}-common" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml + + check_required_tests: + name: All common tests passed or skipped + needs: test + # Always run this, even if a dependent job failed + if: always() + runs-on: ubuntu-20.04 + steps: + - name: Check for failures + if: contains(needs.test.result, 'failure') + run: | + echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1 diff --git a/.github/workflows/test-integration-aiohttp.yml b/.github/workflows/test-integration-aiohttp.yml index 7ec01b12db..7d27b7ab2b 100644 --- a/.github/workflows/test-integration-aiohttp.yml +++ b/.github/workflows/test-integration-aiohttp.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-aiohttp" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-aiohttp" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-arq.yml b/.github/workflows/test-integration-arq.yml index 2eee836bc1..d4e69133f8 100644 --- a/.github/workflows/test-integration-arq.yml +++ b/.github/workflows/test-integration-arq.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-arq" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-arq" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-asgi.yml b/.github/workflows/test-integration-asgi.yml index 39f63d6e89..9d1ecd2d79 100644 --- a/.github/workflows/test-integration-asgi.yml +++ b/.github/workflows/test-integration-asgi.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-asgi" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-asgi" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-aws_lambda.yml b/.github/workflows/test-integration-aws_lambda.yml index 22ed7f4945..3f58e0a271 100644 --- a/.github/workflows/test-integration-aws_lambda.yml +++ b/.github/workflows/test-integration-aws_lambda.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-aws_lambda" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-aws_lambda" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-beam.yml b/.github/workflows/test-integration-beam.yml index 03a484537c..688ea59d98 100644 --- a/.github/workflows/test-integration-beam.yml +++ b/.github/workflows/test-integration-beam.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-beam" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-beam" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-boto3.yml b/.github/workflows/test-integration-boto3.yml index cbb4ec7db1..5ac47b11a6 100644 --- a/.github/workflows/test-integration-boto3.yml +++ b/.github/workflows/test-integration-boto3.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-boto3" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-boto3" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-bottle.yml b/.github/workflows/test-integration-bottle.yml index 60979bf5dd..ba98aa24fe 100644 --- a/.github/workflows/test-integration-bottle.yml +++ b/.github/workflows/test-integration-bottle.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-bottle" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-bottle" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-celery.yml b/.github/workflows/test-integration-celery.yml index 7042f8d493..4631d53b91 100644 --- a/.github/workflows/test-integration-celery.yml +++ b/.github/workflows/test-integration-celery.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-celery" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-celery" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-chalice.yml b/.github/workflows/test-integration-chalice.yml index d8240fe024..f9ec86e447 100644 --- a/.github/workflows/test-integration-chalice.yml +++ b/.github/workflows/test-integration-chalice.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-chalice" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-chalice" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-cloud_resource_context.yml b/.github/workflows/test-integration-cloud_resource_context.yml index d4e2a25be8..bbc99d2ffd 100644 --- a/.github/workflows/test-integration-cloud_resource_context.yml +++ b/.github/workflows/test-integration-cloud_resource_context.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-cloud_resource_context" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-cloud_resource_context" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-django.yml b/.github/workflows/test-integration-django.yml index 2e462a723a..165c99e8b0 100644 --- a/.github/workflows/test-integration-django.yml +++ b/.github/workflows/test-integration-django.yml @@ -73,7 +73,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-django" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-django" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-falcon.yml b/.github/workflows/test-integration-falcon.yml index 259006f106..07af9c87c7 100644 --- a/.github/workflows/test-integration-falcon.yml +++ b/.github/workflows/test-integration-falcon.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-falcon" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-falcon" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-fastapi.yml b/.github/workflows/test-integration-fastapi.yml index 1b6e4e24b5..a3983594fb 100644 --- a/.github/workflows/test-integration-fastapi.yml +++ b/.github/workflows/test-integration-fastapi.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-fastapi" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-fastapi" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-flask.yml b/.github/workflows/test-integration-flask.yml index 91e50a4eac..b4b37e80ab 100644 --- a/.github/workflows/test-integration-flask.yml +++ b/.github/workflows/test-integration-flask.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-flask" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-flask" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-gcp.yml b/.github/workflows/test-integration-gcp.yml index ca6275a537..5fe59bdb67 100644 --- a/.github/workflows/test-integration-gcp.yml +++ b/.github/workflows/test-integration-gcp.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-gcp" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-gcp" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-gevent.yml b/.github/workflows/test-integration-gevent.yml index ce22867c50..8c993da6df 100644 --- a/.github/workflows/test-integration-gevent.yml +++ b/.github/workflows/test-integration-gevent.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-gevent" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-gevent" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-httpx.yml b/.github/workflows/test-integration-httpx.yml index d8ac90e7bf..1154d1586e 100644 --- a/.github/workflows/test-integration-httpx.yml +++ b/.github/workflows/test-integration-httpx.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-httpx" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-httpx" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-huey.yml b/.github/workflows/test-integration-huey.yml index 4226083299..12eeb52e0b 100644 --- a/.github/workflows/test-integration-huey.yml +++ b/.github/workflows/test-integration-huey.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-huey" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-huey" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-opentelemetry.yml b/.github/workflows/test-integration-opentelemetry.yml index 7c2caa07a5..ccbe4d2a63 100644 --- a/.github/workflows/test-integration-opentelemetry.yml +++ b/.github/workflows/test-integration-opentelemetry.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-opentelemetry" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-opentelemetry" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-pure_eval.yml b/.github/workflows/test-integration-pure_eval.yml index 2f72e39bf4..813749bf98 100644 --- a/.github/workflows/test-integration-pure_eval.yml +++ b/.github/workflows/test-integration-pure_eval.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-pure_eval" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-pure_eval" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-pymongo.yml b/.github/workflows/test-integration-pymongo.yml index b65fe7f74f..49bb67e7fe 100644 --- a/.github/workflows/test-integration-pymongo.yml +++ b/.github/workflows/test-integration-pymongo.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-pymongo" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-pymongo" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-pyramid.yml b/.github/workflows/test-integration-pyramid.yml index bb8faeab84..1c1fc8d416 100644 --- a/.github/workflows/test-integration-pyramid.yml +++ b/.github/workflows/test-integration-pyramid.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-pyramid" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-pyramid" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-quart.yml b/.github/workflows/test-integration-quart.yml index b6ca340ac6..5de9f92b35 100644 --- a/.github/workflows/test-integration-quart.yml +++ b/.github/workflows/test-integration-quart.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-quart" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-quart" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-redis.yml b/.github/workflows/test-integration-redis.yml index 7d5eb18fb9..c612ca4ca3 100644 --- a/.github/workflows/test-integration-redis.yml +++ b/.github/workflows/test-integration-redis.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-redis" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-redis" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-rediscluster.yml b/.github/workflows/test-integration-rediscluster.yml index 453d4984a9..102838def1 100644 --- a/.github/workflows/test-integration-rediscluster.yml +++ b/.github/workflows/test-integration-rediscluster.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-rediscluster" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-rediscluster" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-requests.yml b/.github/workflows/test-integration-requests.yml index d07b8a7ec1..f4fcc1a170 100644 --- a/.github/workflows/test-integration-requests.yml +++ b/.github/workflows/test-integration-requests.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-requests" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-requests" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-rq.yml b/.github/workflows/test-integration-rq.yml index 78b0b44e29..132a87b35c 100644 --- a/.github/workflows/test-integration-rq.yml +++ b/.github/workflows/test-integration-rq.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-rq" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-rq" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-sanic.yml b/.github/workflows/test-integration-sanic.yml index aae23aad58..cbdfb3e142 100644 --- a/.github/workflows/test-integration-sanic.yml +++ b/.github/workflows/test-integration-sanic.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-sanic" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-sanic" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-sqlalchemy.yml b/.github/workflows/test-integration-sqlalchemy.yml index 9bdb5064ce..c9b011571d 100644 --- a/.github/workflows/test-integration-sqlalchemy.yml +++ b/.github/workflows/test-integration-sqlalchemy.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-sqlalchemy" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-sqlalchemy" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-starlette.yml b/.github/workflows/test-integration-starlette.yml index 8ebe2442d0..464e603693 100644 --- a/.github/workflows/test-integration-starlette.yml +++ b/.github/workflows/test-integration-starlette.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-starlette" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-starlette" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-starlite.yml b/.github/workflows/test-integration-starlite.yml index 8a40f7d48c..f36ec659fb 100644 --- a/.github/workflows/test-integration-starlite.yml +++ b/.github/workflows/test-integration-starlite.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-starlite" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-starlite" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-tornado.yml b/.github/workflows/test-integration-tornado.yml index 05055b1e9d..32f66a6ab3 100644 --- a/.github/workflows/test-integration-tornado.yml +++ b/.github/workflows/test-integration-tornado.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-tornado" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-tornado" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/.github/workflows/test-integration-trytond.yml b/.github/workflows/test-integration-trytond.yml index b8d6497e6d..83456a4235 100644 --- a/.github/workflows/test-integration-trytond.yml +++ b/.github/workflows/test-integration-trytond.yml @@ -55,7 +55,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-trytond" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-trytond" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/scripts/split-tox-gh-actions/ci-yaml.txt b/scripts/split-tox-gh-actions/ci-yaml.txt index b9ecdf39e7..7f3fa6b037 100644 --- a/scripts/split-tox-gh-actions/ci-yaml.txt +++ b/scripts/split-tox-gh-actions/ci-yaml.txt @@ -47,7 +47,7 @@ jobs: set -x # print commands that are executed coverage erase - ./scripts/runtox.sh "${{ matrix.python-version }}-{{ framework }}" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch + ./scripts/runtox.sh "py${{ matrix.python-version }}-{{ framework }}" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch coverage combine .coverage* coverage xml -i codecov --file coverage.xml diff --git a/scripts/split-tox-gh-actions/split-tox-gh-actions.py b/scripts/split-tox-gh-actions/split-tox-gh-actions.py index 62f79d5fb7..3cefbda695 100755 --- a/scripts/split-tox-gh-actions/split-tox-gh-actions.py +++ b/scripts/split-tox-gh-actions/split-tox-gh-actions.py @@ -71,7 +71,11 @@ def write_yaml_file( out += template_line.replace("{{ framework }}", current_framework) # write rendered template - outfile_name = OUT_DIR / f"test-integration-{current_framework}.yml" + if current_framework == "common": + outfile_name = OUT_DIR / f"test-{current_framework}.yml" + else: + outfile_name = OUT_DIR / f"test-integration-{current_framework}.yml" + print(f"Writing {outfile_name}") f = open(outfile_name, "w") f.writelines(out) diff --git a/sentry_sdk/__init__.py b/sentry_sdk/__init__.py index 7713751948..dc1ba399d1 100644 --- a/sentry_sdk/__init__.py +++ b/sentry_sdk/__init__.py @@ -7,6 +7,8 @@ from sentry_sdk.consts import VERSION # noqa +from sentry_sdk.tracing import trace # noqa + __all__ = [ # noqa "Hub", "Scope", diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 111dbe9b6a..296fe752bb 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -6,21 +6,23 @@ import sentry_sdk from sentry_sdk.consts import INSTRUMENTER from sentry_sdk.utils import is_valid_sample_rate, logger, nanosecond_time +from sentry_sdk._compat import PY2 from sentry_sdk._types import TYPE_CHECKING if TYPE_CHECKING: import typing - from typing import Optional from typing import Any from typing import Dict + from typing import Iterator from typing import List + from typing import Optional from typing import Tuple - from typing import Iterator import sentry_sdk.profiler - from sentry_sdk._types import Event, SamplingContext, MeasurementUnit + from sentry_sdk._types import Event, MeasurementUnit, SamplingContext + BAGGAGE_HEADER_NAME = "baggage" SENTRY_TRACE_HEADER_NAME = "sentry-trace" @@ -803,6 +805,36 @@ def finish(self, hub=None, end_timestamp=None): pass +def trace(func=None): + # type: (Any) -> Any + """ + Decorator to start a child span under the existing current transaction. + If there is no current transaction, than nothing will be traced. + + Usage: + import sentry_sdk + + @sentry_sdk.trace + def my_function(): + ... + + @sentry_sdk.trace + async def my_async_function(): + ... + """ + if PY2: + from sentry_sdk.tracing_utils_py2 import start_child_span_decorator + else: + from sentry_sdk.tracing_utils_py3 import start_child_span_decorator + + # This patterns allows usage of both @sentry_traced and @sentry_traced(...) + # See https://stackoverflow.com/questions/52126071/decorator-with-arguments-avoid-parenthesis-when-no-arguments/52126278 + if func: + return start_child_span_decorator(func) + else: + return start_child_span_decorator + + # Circular imports from sentry_sdk.tracing_utils import ( diff --git a/sentry_sdk/tracing_utils_py2.py b/sentry_sdk/tracing_utils_py2.py new file mode 100644 index 0000000000..738ced24d1 --- /dev/null +++ b/sentry_sdk/tracing_utils_py2.py @@ -0,0 +1,45 @@ +from functools import wraps + +import sentry_sdk +from sentry_sdk import get_current_span +from sentry_sdk._types import TYPE_CHECKING +from sentry_sdk.consts import OP +from sentry_sdk.utils import logger, qualname_from_function + + +if TYPE_CHECKING: + from typing import Any + + +def start_child_span_decorator(func): + # type: (Any) -> Any + """ + Decorator to add child spans for functions. + + This is the Python 2 compatible version of the decorator. + Duplicated code from ``sentry_sdk.tracing_utils_python3.start_child_span_decorator``. + + See also ``sentry_sdk.tracing.trace()``. + """ + + @wraps(func) + def func_with_tracing(*args, **kwargs): + # type: (*Any, **Any) -> Any + + span_or_trx = get_current_span(sentry_sdk.Hub.current) + + if span_or_trx is None: + logger.warning( + "No transaction found. Not creating a child span for %s. " + "Please start a Sentry transaction before calling this function.", + qualname_from_function(func), + ) + return func(*args, **kwargs) + + with span_or_trx.start_child( + op=OP.FUNCTION, + description=qualname_from_function(func), + ): + return func(*args, **kwargs) + + return func_with_tracing diff --git a/sentry_sdk/tracing_utils_py3.py b/sentry_sdk/tracing_utils_py3.py new file mode 100644 index 0000000000..f126d979d3 --- /dev/null +++ b/sentry_sdk/tracing_utils_py3.py @@ -0,0 +1,72 @@ +import inspect +from functools import wraps + +import sentry_sdk +from sentry_sdk import get_current_span +from sentry_sdk._types import TYPE_CHECKING +from sentry_sdk.consts import OP +from sentry_sdk.utils import logger, qualname_from_function + + +if TYPE_CHECKING: + from typing import Any + + +def start_child_span_decorator(func): + # type: (Any) -> Any + """ + Decorator to add child spans for functions. + + This is the Python 3 compatible version of the decorator. + For Python 2 there is duplicated code here: ``sentry_sdk.tracing_utils_python2.start_child_span_decorator()``. + + See also ``sentry_sdk.tracing.trace()``. + """ + + # Asynchronous case + if inspect.iscoroutinefunction(func): + + @wraps(func) + async def func_with_tracing(*args, **kwargs): + # type: (*Any, **Any) -> Any + + span_or_trx = get_current_span(sentry_sdk.Hub.current) + + if span_or_trx is None: + logger.warning( + "No transaction found. Not creating a child span for %s. " + "Please start a Sentry transaction before calling this function.", + qualname_from_function(func), + ) + return await func(*args, **kwargs) + + with span_or_trx.start_child( + op=OP.FUNCTION, + description=qualname_from_function(func), + ): + return await func(*args, **kwargs) + + # Synchronous case + else: + + @wraps(func) + def func_with_tracing(*args, **kwargs): + # type: (*Any, **Any) -> Any + + span_or_trx = get_current_span(sentry_sdk.Hub.current) + + if span_or_trx is None: + logger.warning( + "No transaction found. Not creating a child span for %s. " + "Please start a Sentry transaction before calling this function.", + qualname_from_function(func), + ) + return func(*args, **kwargs) + + with span_or_trx.start_child( + op=OP.FUNCTION, + description=qualname_from_function(func), + ): + return func(*args, **kwargs) + + return func_with_tracing diff --git a/tests/integrations/asyncio/__init__.py b/tests/integrations/asyncio/__init__.py index 1b887a03fe..e69de29bb2 100644 --- a/tests/integrations/asyncio/__init__.py +++ b/tests/integrations/asyncio/__init__.py @@ -1,3 +0,0 @@ -import pytest - -pytest.importorskip("pytest_asyncio") diff --git a/tests/integrations/asyncio/test_asyncio.py b/tests/integrations/asyncio/test_asyncio_py3.py similarity index 94% rename from tests/integrations/asyncio/test_asyncio.py rename to tests/integrations/asyncio/test_asyncio_py3.py index f29a793e04..98106ed01f 100644 --- a/tests/integrations/asyncio/test_asyncio.py +++ b/tests/integrations/asyncio/test_asyncio_py3.py @@ -2,15 +2,14 @@ import sys import pytest -import pytest_asyncio import sentry_sdk from sentry_sdk.consts import OP from sentry_sdk.integrations.asyncio import AsyncioIntegration -minimum_python_36 = pytest.mark.skipif( - sys.version_info < (3, 6), reason="ASGI is only supported in Python >= 3.6" +minimum_python_37 = pytest.mark.skipif( + sys.version_info < (3, 7), reason="Asyncio tests need Python >= 3.7" ) @@ -26,7 +25,7 @@ async def boom(): 1 / 0 -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def event_loop(request): """Create an instance of the default event loop for each test case.""" loop = asyncio.get_event_loop_policy().new_event_loop() @@ -34,7 +33,7 @@ def event_loop(request): loop.close() -@minimum_python_36 +@minimum_python_37 @pytest.mark.asyncio async def test_create_task( sentry_init, @@ -79,7 +78,7 @@ async def test_create_task( ) -@minimum_python_36 +@minimum_python_37 @pytest.mark.asyncio async def test_gather( sentry_init, @@ -122,7 +121,7 @@ async def test_gather( ) -@minimum_python_36 +@minimum_python_37 @pytest.mark.asyncio async def test_exception( sentry_init, @@ -157,7 +156,7 @@ async def test_exception( assert error_event["exception"]["values"][0]["mechanism"]["type"] == "asyncio" -@minimum_python_36 +@minimum_python_37 @pytest.mark.asyncio async def test_task_result(sentry_init): sentry_init( diff --git a/tests/integrations/stdlib/test_httplib.py b/tests/integrations/stdlib/test_httplib.py index 6998db9d7d..f6ace42ba2 100644 --- a/tests/integrations/stdlib/test_httplib.py +++ b/tests/integrations/stdlib/test_httplib.py @@ -1,6 +1,4 @@ -import platform import random -import sys import pytest @@ -67,7 +65,7 @@ def before_breadcrumb(crumb, hint): events = capture_events() url = "http://localhost:{}/some/random/url".format(PORT) - response = urlopen(url) + urlopen(url) capture_message("Testing!") @@ -85,9 +83,6 @@ def before_breadcrumb(crumb, hint): "http.query": "", } - if platform.python_implementation() != "PyPy": - assert sys.getrefcount(response) == 2 - def test_empty_realurl(sentry_init, capture_events): """ diff --git a/tests/tracing/test_decorator_py2.py b/tests/tracing/test_decorator_py2.py new file mode 100644 index 0000000000..e0e60f90e7 --- /dev/null +++ b/tests/tracing/test_decorator_py2.py @@ -0,0 +1,50 @@ +import mock + +from sentry_sdk.tracing_utils_py2 import ( + start_child_span_decorator as start_child_span_decorator_py2, +) +from sentry_sdk.utils import logger + + +def my_example_function(): + return "return_of_sync_function" + + +def test_trace_decorator_py2(): + fake_start_child = mock.MagicMock() + fake_transaction = mock.MagicMock() + fake_transaction.start_child = fake_start_child + + with mock.patch( + "sentry_sdk.tracing_utils_py2.get_current_span", + return_value=fake_transaction, + ): + result = my_example_function() + fake_start_child.assert_not_called() + assert result == "return_of_sync_function" + + result2 = start_child_span_decorator_py2(my_example_function)() + fake_start_child.assert_called_once_with( + op="function", description="test_decorator_py2.my_example_function" + ) + assert result2 == "return_of_sync_function" + + +def test_trace_decorator_py2_no_trx(): + fake_transaction = None + + with mock.patch( + "sentry_sdk.tracing_utils_py2.get_current_span", + return_value=fake_transaction, + ): + with mock.patch.object(logger, "warning", mock.Mock()) as fake_warning: + result = my_example_function() + fake_warning.assert_not_called() + assert result == "return_of_sync_function" + + result2 = start_child_span_decorator_py2(my_example_function)() + fake_warning.assert_called_once_with( + "No transaction found. Not creating a child span for %s. Please start a Sentry transaction before calling this function.", + "test_decorator_py2.my_example_function", + ) + assert result2 == "return_of_sync_function" diff --git a/tests/tracing/test_decorator_py3.py b/tests/tracing/test_decorator_py3.py new file mode 100644 index 0000000000..2c4bf779f2 --- /dev/null +++ b/tests/tracing/test_decorator_py3.py @@ -0,0 +1,101 @@ +import mock +import pytest +import sys + +from sentry_sdk.tracing_utils_py3 import ( + start_child_span_decorator as start_child_span_decorator_py3, +) +from sentry_sdk.utils import logger + +if sys.version_info < (3, 6): + pytest.skip("Async decorator only works on Python 3.6+", allow_module_level=True) + + +def my_example_function(): + return "return_of_sync_function" + + +async def my_async_example_function(): + return "return_of_async_function" + + +def test_trace_decorator_sync_py3(): + fake_start_child = mock.MagicMock() + fake_transaction = mock.MagicMock() + fake_transaction.start_child = fake_start_child + + with mock.patch( + "sentry_sdk.tracing_utils_py3.get_current_span", + return_value=fake_transaction, + ): + result = my_example_function() + fake_start_child.assert_not_called() + assert result == "return_of_sync_function" + + result2 = start_child_span_decorator_py3(my_example_function)() + fake_start_child.assert_called_once_with( + op="function", description="test_decorator_py3.my_example_function" + ) + assert result2 == "return_of_sync_function" + + +def test_trace_decorator_sync_py3_no_trx(): + fake_transaction = None + + with mock.patch( + "sentry_sdk.tracing_utils_py3.get_current_span", + return_value=fake_transaction, + ): + with mock.patch.object(logger, "warning", mock.Mock()) as fake_warning: + result = my_example_function() + fake_warning.assert_not_called() + assert result == "return_of_sync_function" + + result2 = start_child_span_decorator_py3(my_example_function)() + fake_warning.assert_called_once_with( + "No transaction found. Not creating a child span for %s. Please start a Sentry transaction before calling this function.", + "test_decorator_py3.my_example_function", + ) + assert result2 == "return_of_sync_function" + + +@pytest.mark.asyncio +async def test_trace_decorator_async_py3(): + fake_start_child = mock.MagicMock() + fake_transaction = mock.MagicMock() + fake_transaction.start_child = fake_start_child + + with mock.patch( + "sentry_sdk.tracing_utils_py3.get_current_span", + return_value=fake_transaction, + ): + result = await my_async_example_function() + fake_start_child.assert_not_called() + assert result == "return_of_async_function" + + result2 = await start_child_span_decorator_py3(my_async_example_function)() + fake_start_child.assert_called_once_with( + op="function", description="test_decorator_py3.my_async_example_function" + ) + assert result2 == "return_of_async_function" + + +@pytest.mark.asyncio +async def test_trace_decorator_async_py3_no_trx(): + fake_transaction = None + + with mock.patch( + "sentry_sdk.tracing_utils_py3.get_current_span", + return_value=fake_transaction, + ): + with mock.patch.object(logger, "warning", mock.Mock()) as fake_warning: + result = await my_async_example_function() + fake_warning.assert_not_called() + assert result == "return_of_async_function" + + result2 = await start_child_span_decorator_py3(my_async_example_function)() + fake_warning.assert_called_once_with( + "No transaction found. Not creating a child span for %s. Please start a Sentry transaction before calling this function.", + "test_decorator_py3.my_async_example_function", + ) + assert result2 == "return_of_async_function" diff --git a/tox.ini b/tox.ini index 45facf42c0..a305758d70 100644 --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,8 @@ [tox] envlist = - # === Core === - {py2.7,py3.4,py3.5,py3.6,py3.7,py3.8,py3.9,py3.10,py3.11} + # === Common === + {py2.7,py3.5,py3.6,py3.7,py3.8,py3.9,py3.10,py3.11}-common # === Integrations === # General format is {pythonversion}-{integrationname}-v{frameworkversion} @@ -159,22 +159,14 @@ deps = # with the -r flag -r test-requirements.txt - py3.4: colorama==0.4.1 - py3.4: watchdog==0.10.7 - - py3.8: hypothesis + py3.4-common: colorama==0.4.1 + py3.4-common: watchdog==0.10.7 + py3.8-common: hypothesis linters: -r linter-requirements.txt - # Gevent - # See http://www.gevent.org/install.html#older-versions-of-python - # for justification of the versions pinned below - py3.4-gevent: gevent==1.4.0 - py3.5-gevent: gevent==20.9.0 - # See https://stackoverflow.com/questions/51496550/runtime-warning-greenlet-greenlet-size-changed - # for justification why greenlet is pinned here - py3.5-gevent: greenlet==0.4.17 - {py2.7,py3.6,py3.7,py3.8,py3.9,py3.10,py3.11}-gevent: gevent>=22.10.0, <22.11.0 + # Common + {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11}-common: pytest-asyncio # AIOHTTP aiohttp-v3.4: aiohttp>=3.4.0,<3.5.0 @@ -289,6 +281,16 @@ deps = flask-v1.1: Flask>=1.1,<1.2 flask-v2.0: Flask>=2.0,<2.1 + # Gevent + # See http://www.gevent.org/install.html#older-versions-of-python + # for justification of the versions pinned below + py3.4-gevent: gevent==1.4.0 + py3.5-gevent: gevent==20.9.0 + # See https://stackoverflow.com/questions/51496550/runtime-warning-greenlet-greenlet-size-changed + # for justification why greenlet is pinned here + py3.5-gevent: greenlet==0.4.17 + {py2.7,py3.6,py3.7,py3.8,py3.9,py3.10,py3.11}-gevent: gevent>=22.10.0, <22.11.0 + # HTTPX httpx: pytest-httpx httpx-v0.16: httpx>=0.16,<0.17 @@ -409,7 +411,7 @@ deps = setenv = PYTHONDONTWRITEBYTECODE=1 - TESTPATH=tests + common: TESTPATH=tests aiohttp: TESTPATH=tests/integrations/aiohttp arq: TESTPATH=tests/integrations/arq asgi: TESTPATH=tests/integrations/asgi @@ -494,7 +496,8 @@ commands = ; Running `py.test` as an executable suffers from an import error ; when loading tests in scenarios. In particular, django fails to ; load the settings from the test module. - python -m pytest --durations=5 -vvv {env:TESTPATH} {posargs} + {py2.7}: python -m pytest --ignore-glob='*py3.py' --durations=5 -vvv {env:TESTPATH} {posargs} + {py3.5,py3.6,py3.7,py3.8,py3.9,py3.10,py3.11}: python -m pytest --durations=5 -vvv {env:TESTPATH} {posargs} [testenv:linters] commands =