From 3d0edfd6387c9e35bddac572d3613c741cc3c3d0 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 9 Sep 2024 11:22:25 +0000 Subject: [PATCH 01/16] release: 2.14.0 --- CHANGELOG.md | 23 +++++++++++++++++++++++ docs/conf.py | 2 +- sentry_sdk/consts.py | 2 +- setup.py | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54fa4a2133..85e3920251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 2.14.0 + +### Various fixes & improvements + +- tests: Remove broken bottle tests (#3505) by @sentrivana +- Fixed config for old coverage versions (#3504) by @antonpirker +- Fix AWS Lambda tests (#3495) by @antonpirker +- Better test coverage reports (#3498) by @antonpirker +- feat(celery): Add wrapper for `Celery().send_task` to support behavior as `Task.apply_async` (#2377) by @divaltor +- fix(django): SentryWrappingMiddleware.__init__ fails if super() is object (#2466) by @cameron-simpson +- feat(strawberry): Support Strawberry 0.239.2 (#3491) by @szokeasaurusrex +- ref: Remove obsolete object as superclass (#3480) by @sentrivana +- Fix data_category for sessions envelope items (#3473) by @sl0thentr0py +- fix: Fix non-UTC timestamps (#3461) by @szokeasaurusrex +- Add separate pii_denylist to EventScrubber and run it always (#3463) by @sl0thentr0py +- Revert "Pin httpx till upstream gets resolved (#3465)" (#3466) by @sl0thentr0py +- feat(integrations): New `SysExitIntegration` (#3401) by @szokeasaurusrex +- ref(types): Replace custom TYPE_CHECKING with stdlib typing.TYPE_CHECKING (#3447) by @dev-satoshi +- Pin httpx till upstream gets resolved (#3465) by @sl0thentr0py +- chore(tracing): Refactor `tracing_utils.py` (#3452) by @rominf +- feat: Add SENTRY_SPOTLIGHT env variable support (#3443) by @BYK +- style: explicitly export symbols instead of ignoring (#3400) by @hartungstenio + ## 2.13.0 ### Various fixes & improvements diff --git a/docs/conf.py b/docs/conf.py index c30f18c8a8..875dfcb575 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -28,7 +28,7 @@ copyright = "2019-{}, Sentry Team and Contributors".format(datetime.now().year) author = "Sentry Team and Contributors" -release = "2.13.0" +release = "2.14.0" version = ".".join(release.split(".")[:2]) # The short X.Y version. diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 5581f191b7..5f79031787 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -567,4 +567,4 @@ def _get_default_options(): del _get_default_options -VERSION = "2.13.0" +VERSION = "2.14.0" diff --git a/setup.py b/setup.py index ee1d52b2e8..c11b6b771e 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def get_file_text(file_name): setup( name="sentry-sdk", - version="2.13.0", + version="2.14.0", author="Sentry Team and Contributors", author_email="hello@sentry.io", url="https://github.com/getsentry/sentry-python", From 1e73ce9fa12ea04250a708c14531d94827501a1d Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Mon, 9 Sep 2024 13:33:13 +0200 Subject: [PATCH 02/16] Updated changelog --- CHANGELOG.md | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85e3920251..0fa0621afb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,24 +4,25 @@ ### Various fixes & improvements -- tests: Remove broken bottle tests (#3505) by @sentrivana -- Fixed config for old coverage versions (#3504) by @antonpirker -- Fix AWS Lambda tests (#3495) by @antonpirker -- Better test coverage reports (#3498) by @antonpirker -- feat(celery): Add wrapper for `Celery().send_task` to support behavior as `Task.apply_async` (#2377) by @divaltor -- fix(django): SentryWrappingMiddleware.__init__ fails if super() is object (#2466) by @cameron-simpson -- feat(strawberry): Support Strawberry 0.239.2 (#3491) by @szokeasaurusrex -- ref: Remove obsolete object as superclass (#3480) by @sentrivana +- New `SysExitIntegration` (#3401) by @szokeasaurusrex + + For more information, see the documentation for the [SysExitIntegration](https://docs.sentry.io/platforms/python/integrations/sys_exit). + +- Add `SENTRY_SPOTLIGHT` env variable support (#3443) by @BYK +- Support Strawberry `0.239.2` (#3491) by @szokeasaurusrex +- Add separate `pii_denylist` to `EventScrubber` and run it always (#3463) by @sl0thentr0py +- Celery: Add wrapper for `Celery().send_task` to support behavior as `Task.apply_async` (#2377) by @divaltor +- Django: SentryWrappingMiddleware.__init__ fails if super() is object (#2466) by @cameron-simpson - Fix data_category for sessions envelope items (#3473) by @sl0thentr0py -- fix: Fix non-UTC timestamps (#3461) by @szokeasaurusrex -- Add separate pii_denylist to EventScrubber and run it always (#3463) by @sl0thentr0py -- Revert "Pin httpx till upstream gets resolved (#3465)" (#3466) by @sl0thentr0py -- feat(integrations): New `SysExitIntegration` (#3401) by @szokeasaurusrex -- ref(types): Replace custom TYPE_CHECKING with stdlib typing.TYPE_CHECKING (#3447) by @dev-satoshi -- Pin httpx till upstream gets resolved (#3465) by @sl0thentr0py -- chore(tracing): Refactor `tracing_utils.py` (#3452) by @rominf -- feat: Add SENTRY_SPOTLIGHT env variable support (#3443) by @BYK -- style: explicitly export symbols instead of ignoring (#3400) by @hartungstenio +- Fix non-UTC timestamps (#3461) by @szokeasaurusrex +- Remove obsolete object as superclass (#3480) by @sentrivana +- Replace custom `TYPE_CHECKING` with stdlib `typing.TYPE_CHECKING` (#3447) by @dev-satoshi +- Refactor `tracing_utils.py` (#3452) by @rominf +- Explicitly export symbol in subpackages instead of ignoring (#3400) by @hartungstenio +- Better test coverage reports (#3498) by @antonpirker +- Fixed config for old coverage versions (#3504) by @antonpirker +- Fix AWS Lambda tests (#3495) by @antonpirker +- Remove broken Bottle tests (#3505) by @sentrivana ## 2.13.0 From 22f62b0d3236e888b1bf40a4532a11b289703172 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Tue, 10 Sep 2024 14:59:03 +0200 Subject: [PATCH 03/16] fix(breadcrumbs): Fix sorting (#3511) - best-effort coerce string timestamps into datetimes before sorting - ignore errors while breadcrumb sorting (better to have unsorted crumbs than breaking anything) --- sentry_sdk/scope.py | 12 +++++++++++- sentry_sdk/utils.py | 9 +++++++++ tests/test_basics.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 6e0d0925c8..b6a23253e8 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -30,6 +30,7 @@ capture_internal_exception, capture_internal_exceptions, ContextVar, + datetime_from_isoformat, disable_capture_event, event_from_exception, exc_info_from_error, @@ -1307,7 +1308,16 @@ def _apply_breadcrumbs_to_event(self, event, hint, options): event.setdefault("breadcrumbs", {}).setdefault("values", []).extend( self._breadcrumbs ) - event["breadcrumbs"]["values"].sort(key=lambda crumb: crumb["timestamp"]) + + # Attempt to sort timestamps + try: + for crumb in event["breadcrumbs"]["values"]: + if isinstance(crumb["timestamp"], str): + crumb["timestamp"] = datetime_from_isoformat(crumb["timestamp"]) + + event["breadcrumbs"]["values"].sort(key=lambda crumb: crumb["timestamp"]) + except Exception: + pass def _apply_user_to_event(self, event, hint, options): # type: (Event, Hint, Optional[Dict[str, Any]]) -> None diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 9f49b9470f..38ab7e3618 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -239,6 +239,15 @@ def format_timestamp(value): return utctime.strftime("%Y-%m-%dT%H:%M:%S.%fZ") +def datetime_from_isoformat(value): + # type: (str) -> datetime + try: + return datetime.fromisoformat(value) + except AttributeError: + # py 3.6 + return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") + + def event_hint_with_exc_info(exc_info=None): # type: (Optional[ExcInfo]) -> Dict[str, Optional[ExcInfo]] """Creates a hint with the exc info filled in.""" diff --git a/tests/test_basics.py b/tests/test_basics.py index c9d80118c2..6f77353c8a 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -425,6 +425,37 @@ def test_breadcrumb_ordering(sentry_init, capture_events): assert timestamps_from_event == sorted(timestamps) +def test_breadcrumb_ordering_different_types(sentry_init, capture_events): + sentry_init() + events = capture_events() + + timestamps = [ + datetime.datetime.now() - datetime.timedelta(days=10), + datetime.datetime.now() - datetime.timedelta(days=8), + datetime.datetime.now() - datetime.timedelta(days=12), + ] + + for i, timestamp in enumerate(timestamps): + add_breadcrumb( + message="Authenticated at %s" % timestamp, + category="auth", + level="info", + timestamp=timestamp if i % 2 == 0 else timestamp.isoformat(), + ) + + capture_exception(ValueError()) + (event,) = events + + assert len(event["breadcrumbs"]["values"]) == len(timestamps) + timestamps_from_event = [ + datetime.datetime.strptime( + x["timestamp"].replace("Z", ""), "%Y-%m-%dT%H:%M:%S.%f" + ) + for x in event["breadcrumbs"]["values"] + ] + assert timestamps_from_event == sorted(timestamps) + + def test_attachments(sentry_init, capture_envelopes): sentry_init() envelopes = capture_envelopes() From c635e3e1181304e70ec86ccfc486edae58286c26 Mon Sep 17 00:00:00 2001 From: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:17:13 +0200 Subject: [PATCH 04/16] ref(metrics): Deprecate `sentry_sdk.metrics` (#3512) Raise a `DeprecationWarning` on import of the `sentry_sdk.metrics` module. Closes #3502 --- sentry_sdk/metrics.py | 9 +++++++++ sentry_sdk/tracing.py | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/metrics.py b/sentry_sdk/metrics.py index 05dc13042c..da6d77c69a 100644 --- a/sentry_sdk/metrics.py +++ b/sentry_sdk/metrics.py @@ -5,6 +5,7 @@ import sys import threading import time +import warnings import zlib from abc import ABC, abstractmethod from contextlib import contextmanager @@ -54,6 +55,14 @@ from sentry_sdk._types import MetricValue +warnings.warn( + "The sentry_sdk.metrics module is deprecated and will be removed in the next major release. " + "Sentry will reject all metrics sent after October 7, 2024. " + "Learn more: https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Upcoming-API-Changes-to-Metrics", + DeprecationWarning, + stacklevel=2, +) + _in_metrics = ContextVar("in_metrics", default=False) _set = set # set is shadowed below diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 3ca9744b54..41525b4676 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1298,4 +1298,8 @@ async def my_async_function(): has_tracing_enabled, maybe_create_breadcrumbs_from_span, ) -from sentry_sdk.metrics import LocalAggregator + +with warnings.catch_warnings(): + # The code in this file which uses `LocalAggregator` is only called from the deprecated `metrics` module. + warnings.simplefilter("ignore", DeprecationWarning) + from sentry_sdk.metrics import LocalAggregator From 53897ff5d42bad05622e5ae53d026758fd28201c Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 11 Sep 2024 11:04:16 +0200 Subject: [PATCH 05/16] Update Codecov config (#3507) The Codecov plugins somehow changing our coverage reports, which lead to incorrect coverage measurements. This change will disable all Codecov plugins so our uploaded coverage reports will not be altered. According to Codecov engineers, this has no downsides. --- .github/workflows/test-integrations-ai.yml | 8 ++++++++ .github/workflows/test-integrations-aws-lambda.yml | 4 ++++ .github/workflows/test-integrations-cloud-computing.yml | 8 ++++++++ .github/workflows/test-integrations-common.yml | 4 ++++ .github/workflows/test-integrations-data-processing.yml | 8 ++++++++ .github/workflows/test-integrations-databases.yml | 8 ++++++++ .github/workflows/test-integrations-graphql.yml | 8 ++++++++ .github/workflows/test-integrations-miscellaneous.yml | 8 ++++++++ .github/workflows/test-integrations-networking.yml | 8 ++++++++ .github/workflows/test-integrations-web-frameworks-1.yml | 8 ++++++++ .github/workflows/test-integrations-web-frameworks-2.yml | 8 ++++++++ scripts/split-tox-gh-actions/templates/test_group.jinja | 6 +++++- 12 files changed, 85 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-integrations-ai.yml b/.github/workflows/test-integrations-ai.yml index c3c8f7a689..18b6e8e641 100644 --- a/.github/workflows/test-integrations-ai.yml +++ b/.github/workflows/test-integrations-ai.yml @@ -82,12 +82,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true test-ai-pinned: name: AI (pinned) timeout-minutes: 30 @@ -150,12 +154,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All AI tests passed needs: test-ai-pinned diff --git a/.github/workflows/test-integrations-aws-lambda.yml b/.github/workflows/test-integrations-aws-lambda.yml index 10e319f8a2..72ffee0492 100644 --- a/.github/workflows/test-integrations-aws-lambda.yml +++ b/.github/workflows/test-integrations-aws-lambda.yml @@ -101,12 +101,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All AWS Lambda tests passed needs: test-aws_lambda-pinned diff --git a/.github/workflows/test-integrations-cloud-computing.yml b/.github/workflows/test-integrations-cloud-computing.yml index 94dd3473cd..3fdc46f88b 100644 --- a/.github/workflows/test-integrations-cloud-computing.yml +++ b/.github/workflows/test-integrations-cloud-computing.yml @@ -78,12 +78,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true test-cloud_computing-pinned: name: Cloud Computing (pinned) timeout-minutes: 30 @@ -142,12 +146,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All Cloud Computing tests passed needs: test-cloud_computing-pinned diff --git a/.github/workflows/test-integrations-common.yml b/.github/workflows/test-integrations-common.yml index dbb3cb5d53..a64912b14d 100644 --- a/.github/workflows/test-integrations-common.yml +++ b/.github/workflows/test-integrations-common.yml @@ -66,12 +66,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All Common tests passed needs: test-common-pinned diff --git a/.github/workflows/test-integrations-data-processing.yml b/.github/workflows/test-integrations-data-processing.yml index 6eb3a9f71f..b38c9179e1 100644 --- a/.github/workflows/test-integrations-data-processing.yml +++ b/.github/workflows/test-integrations-data-processing.yml @@ -96,12 +96,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true test-data_processing-pinned: name: Data Processing (pinned) timeout-minutes: 30 @@ -178,12 +182,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All Data Processing tests passed needs: test-data_processing-pinned diff --git a/.github/workflows/test-integrations-databases.yml b/.github/workflows/test-integrations-databases.yml index eca776d1c4..cc93461b6a 100644 --- a/.github/workflows/test-integrations-databases.yml +++ b/.github/workflows/test-integrations-databases.yml @@ -105,12 +105,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true test-databases-pinned: name: Databases (pinned) timeout-minutes: 30 @@ -196,12 +200,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All Databases tests passed needs: test-databases-pinned diff --git a/.github/workflows/test-integrations-graphql.yml b/.github/workflows/test-integrations-graphql.yml index c89423327a..39b4aa5449 100644 --- a/.github/workflows/test-integrations-graphql.yml +++ b/.github/workflows/test-integrations-graphql.yml @@ -78,12 +78,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true test-graphql-pinned: name: GraphQL (pinned) timeout-minutes: 30 @@ -142,12 +146,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All GraphQL tests passed needs: test-graphql-pinned diff --git a/.github/workflows/test-integrations-miscellaneous.yml b/.github/workflows/test-integrations-miscellaneous.yml index 492338c40e..369e6afd87 100644 --- a/.github/workflows/test-integrations-miscellaneous.yml +++ b/.github/workflows/test-integrations-miscellaneous.yml @@ -82,12 +82,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true test-miscellaneous-pinned: name: Miscellaneous (pinned) timeout-minutes: 30 @@ -150,12 +154,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All Miscellaneous tests passed needs: test-miscellaneous-pinned diff --git a/.github/workflows/test-integrations-networking.yml b/.github/workflows/test-integrations-networking.yml index fb55e708ae..cb032f0ef4 100644 --- a/.github/workflows/test-integrations-networking.yml +++ b/.github/workflows/test-integrations-networking.yml @@ -78,12 +78,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true test-networking-pinned: name: Networking (pinned) timeout-minutes: 30 @@ -142,12 +146,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All Networking tests passed needs: test-networking-pinned diff --git a/.github/workflows/test-integrations-web-frameworks-1.yml b/.github/workflows/test-integrations-web-frameworks-1.yml index 01b391992d..f6a94e6d08 100644 --- a/.github/workflows/test-integrations-web-frameworks-1.yml +++ b/.github/workflows/test-integrations-web-frameworks-1.yml @@ -96,12 +96,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true test-web_frameworks_1-pinned: name: Web Frameworks 1 (pinned) timeout-minutes: 30 @@ -178,12 +182,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All Web Frameworks 1 tests passed needs: test-web_frameworks_1-pinned diff --git a/.github/workflows/test-integrations-web-frameworks-2.yml b/.github/workflows/test-integrations-web-frameworks-2.yml index 310921a250..0a66e98d3d 100644 --- a/.github/workflows/test-integrations-web-frameworks-2.yml +++ b/.github/workflows/test-integrations-web-frameworks-2.yml @@ -102,12 +102,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true test-web_frameworks_2-pinned: name: Web Frameworks 2 (pinned) timeout-minutes: 30 @@ -190,12 +194,16 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: ${{ !cancelled() }} uses: codecov/test-results-action@v1 with: token: ${{ secrets.CODECOV_TOKEN }} files: .junitxml + verbose: true check_required_tests: name: All Web Frameworks 2 tests passed needs: test-web_frameworks_2-pinned diff --git a/scripts/split-tox-gh-actions/templates/test_group.jinja b/scripts/split-tox-gh-actions/templates/test_group.jinja index e63d6e0235..66834f9ef2 100644 --- a/scripts/split-tox-gh-actions/templates/test_group.jinja +++ b/scripts/split-tox-gh-actions/templates/test_group.jinja @@ -96,10 +96,14 @@ with: token: {% raw %}${{ secrets.CODECOV_TOKEN }}{% endraw %} files: coverage.xml + # make sure no plugins alter our coverage reports + plugin: noop + verbose: true - name: Upload test results to Codecov if: {% raw %}${{ !cancelled() }}{% endraw %} uses: codecov/test-results-action@v1 with: token: {% raw %}${{ secrets.CODECOV_TOKEN }}{% endraw %} - files: .junitxml \ No newline at end of file + files: .junitxml + verbose: true \ No newline at end of file From a58154259468b0d2f944a4a01eb2bf96a543696c Mon Sep 17 00:00:00 2001 From: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:19:20 +0200 Subject: [PATCH 06/16] fix(django): Add `sync_capable` to `SentryWrappingMiddleware` (#3510) * fix(django): Add `sync_capable` to `SentryWrappingMiddleware` Fixes #3506 * test(django): Test that `sync_capable` set on wrapped middleware --- sentry_sdk/integrations/django/middleware.py | 1 + tests/integrations/django/test_middleware.py | 34 ++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/integrations/django/test_middleware.py diff --git a/sentry_sdk/integrations/django/middleware.py b/sentry_sdk/integrations/django/middleware.py index 981d192864..2cde251fd3 100644 --- a/sentry_sdk/integrations/django/middleware.py +++ b/sentry_sdk/integrations/django/middleware.py @@ -125,6 +125,7 @@ def sentry_wrapped_method(*args, **kwargs): class SentryWrappingMiddleware( _asgi_middleware_mixin_factory(_check_middleware_span) # type: ignore ): + sync_capable = getattr(middleware, "sync_capable", True) async_capable = DJANGO_SUPPORTS_ASYNC_MIDDLEWARE and getattr( middleware, "async_capable", False ) diff --git a/tests/integrations/django/test_middleware.py b/tests/integrations/django/test_middleware.py new file mode 100644 index 0000000000..2a8d94f623 --- /dev/null +++ b/tests/integrations/django/test_middleware.py @@ -0,0 +1,34 @@ +from typing import Optional + +import pytest + +from sentry_sdk.integrations.django.middleware import _wrap_middleware + + +def _sync_capable_middleware_factory(sync_capable): + # type: (Optional[bool]) -> type + """Create a middleware class with a sync_capable attribute set to the value passed to the factory. + If the factory is called with None, the middleware class will not have a sync_capable attribute. + """ + sc = sync_capable # rename so we can set sync_capable in the class + + class TestMiddleware: + nonlocal sc + if sc is not None: + sync_capable = sc + + return TestMiddleware + + +@pytest.mark.parametrize( + ("middleware", "sync_capable"), + ( + (_sync_capable_middleware_factory(True), True), + (_sync_capable_middleware_factory(False), False), + (_sync_capable_middleware_factory(None), True), + ), +) +def test_wrap_middleware_sync_capable_attribute(middleware, sync_capable): + wrapped_middleware = _wrap_middleware(middleware, "test_middleware") + + assert wrapped_middleware.sync_capable is sync_capable From b1b16b029ba98129dae181c083e5db89de16516a Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 12 Sep 2024 11:02:39 +0200 Subject: [PATCH 07/16] Added `name` parameter to `start_span()` and deprecated `description` parameter. (#3524) To align our API with OpenTelementry. In OTel a span has no description but a name. This only changes to user facing API, under the hood there is still everything using the description. (This will then be changed with OTel) --- sentry_sdk/scope.py | 8 +++++ sentry_sdk/tracing.py | 23 ++++++++++--- tests/tracing/test_misc.py | 5 --- tests/tracing/test_span_name.py | 59 +++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 tests/tracing/test_span_name.py diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index b6a23253e8..adae8dc888 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -1,5 +1,6 @@ import os import sys +import warnings from copy import copy from collections import deque from contextlib import contextmanager @@ -1067,6 +1068,13 @@ def start_span(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs): be removed in the next major version. Going forward, it should only be used by the SDK itself. """ + if kwargs.get("description") is not None: + warnings.warn( + "The `description` parameter is deprecated. Please use `name` instead.", + DeprecationWarning, + stacklevel=2, + ) + with new_scope(): kwargs.setdefault("scope", self) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 41525b4676..036e6619f6 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -70,7 +70,7 @@ class SpanKwargs(TypedDict, total=False): """ description: str - """A description of what operation is being performed within the span.""" + """A description of what operation is being performed within the span. This argument is DEPRECATED. Please use the `name` parameter, instead.""" hub: Optional["sentry_sdk.Hub"] """The hub to use for this span. This argument is DEPRECATED. Please use the `scope` parameter, instead.""" @@ -97,10 +97,10 @@ class SpanKwargs(TypedDict, total=False): Default "manual". """ - class TransactionKwargs(SpanKwargs, total=False): name: str - """Identifier of the transaction. Will show up in the Sentry UI.""" + """A string describing what operation is being performed within the span/transaction.""" + class TransactionKwargs(SpanKwargs, total=False): source: str """ A string describing the source of the transaction name. This will be used to determine the transaction's type. @@ -227,6 +227,10 @@ class Span: :param op: The span's operation. A list of recommended values is available here: https://develop.sentry.dev/sdk/performance/span-operations/ :param description: A description of what operation is being performed within the span. + + .. deprecated:: 2.X.X + Please use the `name` parameter, instead. + :param name: A string describing what operation is being performed within the span. :param hub: The hub to use for this span. .. deprecated:: 2.0.0 @@ -261,6 +265,7 @@ class Span: "_local_aggregator", "scope", "origin", + "name", ) def __init__( @@ -278,6 +283,7 @@ def __init__( start_timestamp=None, # type: Optional[Union[datetime, float]] scope=None, # type: Optional[sentry_sdk.Scope] origin="manual", # type: str + name=None, # type: Optional[str] ): # type: (...) -> None self.trace_id = trace_id or uuid.uuid4().hex @@ -286,7 +292,7 @@ def __init__( self.same_process_as_parent = same_process_as_parent self.sampled = sampled self.op = op - self.description = description + self.description = name or description self.status = status self.hub = hub # backwards compatibility self.scope = scope @@ -400,6 +406,13 @@ def start_child(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs): be removed in the next major version. Going forward, it should only be used by the SDK itself. """ + if kwargs.get("description") is not None: + warnings.warn( + "The `description` parameter is deprecated. Please use `name` instead.", + DeprecationWarning, + stacklevel=2, + ) + configuration_instrumenter = sentry_sdk.get_client().options["instrumenter"] if instrumenter != configuration_instrumenter: @@ -750,7 +763,7 @@ class Transaction(Span): "_baggage", ) - def __init__( + def __init__( # type: ignore[misc] self, name="", # type: str parent_sampled=None, # type: Optional[bool] diff --git a/tests/tracing/test_misc.py b/tests/tracing/test_misc.py index 02966642fd..de2f782538 100644 --- a/tests/tracing/test_misc.py +++ b/tests/tracing/test_misc.py @@ -36,11 +36,6 @@ def test_transaction_naming(sentry_init, capture_events): sentry_init(traces_sample_rate=1.0) events = capture_events() - # only transactions have names - spans don't - with pytest.raises(TypeError): - start_span(name="foo") - assert len(events) == 0 - # default name in event if no name is passed with start_transaction() as transaction: pass diff --git a/tests/tracing/test_span_name.py b/tests/tracing/test_span_name.py new file mode 100644 index 0000000000..9c1768990a --- /dev/null +++ b/tests/tracing/test_span_name.py @@ -0,0 +1,59 @@ +import pytest + +import sentry_sdk + + +def test_start_span_description(sentry_init, capture_events): + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + with sentry_sdk.start_transaction(name="hi"): + with pytest.deprecated_call(): + with sentry_sdk.start_span(op="foo", description="span-desc"): + ... + + (event,) = events + + assert event["spans"][0]["description"] == "span-desc" + + +def test_start_span_name(sentry_init, capture_events): + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + with sentry_sdk.start_transaction(name="hi"): + with sentry_sdk.start_span(op="foo", name="span-name"): + ... + + (event,) = events + + assert event["spans"][0]["description"] == "span-name" + + +def test_start_child_description(sentry_init, capture_events): + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + with sentry_sdk.start_transaction(name="hi"): + with pytest.deprecated_call(): + with sentry_sdk.start_span(op="foo", description="span-desc") as span: + with span.start_child(op="bar", description="child-desc"): + ... + + (event,) = events + + assert event["spans"][-1]["description"] == "child-desc" + + +def test_start_child_name(sentry_init, capture_events): + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + with sentry_sdk.start_transaction(name="hi"): + with sentry_sdk.start_span(op="foo", name="span-name") as span: + with span.start_child(op="bar", name="child-name"): + ... + + (event,) = events + + assert event["spans"][-1]["description"] == "child-name" From e6ca5a28dd139097ad7c8cb468e0b9232185b728 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Thu, 12 Sep 2024 11:11:56 +0200 Subject: [PATCH 08/16] Remove usages of deprecated `description` and replace by `name` in `start_span()` calls. (#3525) Replace the deprecated `description` parameter in all calls to `start_span()` and `start_child` and replace it with the new `name` parameter. --- sentry_sdk/ai/monitoring.py | 4 ++-- sentry_sdk/integrations/aiohttp.py | 2 +- sentry_sdk/integrations/anthropic.py | 2 +- sentry_sdk/integrations/arq.py | 2 +- sentry_sdk/integrations/asyncio.py | 2 +- sentry_sdk/integrations/asyncpg.py | 2 +- sentry_sdk/integrations/boto3.py | 4 ++-- sentry_sdk/integrations/celery/__init__.py | 6 +++--- sentry_sdk/integrations/clickhouse_driver.py | 2 +- sentry_sdk/integrations/cohere.py | 4 ++-- sentry_sdk/integrations/django/__init__.py | 2 +- sentry_sdk/integrations/django/asgi.py | 2 +- sentry_sdk/integrations/django/caching.py | 2 +- sentry_sdk/integrations/django/middleware.py | 2 +- .../integrations/django/signals_handlers.py | 2 +- sentry_sdk/integrations/django/templates.py | 4 ++-- sentry_sdk/integrations/django/views.py | 4 ++-- sentry_sdk/integrations/graphene.py | 4 ++-- sentry_sdk/integrations/grpc/aio/client.py | 4 ++-- sentry_sdk/integrations/grpc/client.py | 4 ++-- sentry_sdk/integrations/httpx.py | 4 ++-- sentry_sdk/integrations/huey.py | 2 +- sentry_sdk/integrations/huggingface_hub.py | 2 +- sentry_sdk/integrations/langchain.py | 16 +++++++--------- sentry_sdk/integrations/litestar.py | 6 +++--- sentry_sdk/integrations/openai.py | 4 ++-- .../integrations/opentelemetry/span_processor.py | 2 +- sentry_sdk/integrations/pymongo.py | 2 +- sentry_sdk/integrations/ray.py | 2 +- sentry_sdk/integrations/redis/_async_common.py | 6 +++--- sentry_sdk/integrations/redis/_sync_common.py | 6 +++--- sentry_sdk/integrations/socket.py | 4 ++-- sentry_sdk/integrations/starlette.py | 6 +++--- sentry_sdk/integrations/starlite.py | 6 +++--- sentry_sdk/integrations/stdlib.py | 4 ++-- sentry_sdk/integrations/strawberry.py | 12 ++++++------ sentry_sdk/metrics.py | 2 +- sentry_sdk/tracing_utils.py | 6 +++--- tests/integrations/asyncio/test_asyncio.py | 6 +++--- tests/integrations/grpc/test_grpc.py | 2 +- tests/integrations/grpc/test_grpc_aio.py | 2 +- .../opentelemetry/test_span_processor.py | 2 +- tests/integrations/ray/test_ray.py | 4 ++-- tests/integrations/threading/test_threading.py | 2 +- tests/test_scrubber.py | 2 +- tests/tracing/test_decorator.py | 4 ++-- tests/tracing/test_integration_tests.py | 14 +++++++------- tests/tracing/test_noop_span.py | 2 +- tests/tracing/test_span_origin.py | 6 +++--- 49 files changed, 98 insertions(+), 100 deletions(-) diff --git a/sentry_sdk/ai/monitoring.py b/sentry_sdk/ai/monitoring.py index e1679b0bc6..860833b8f5 100644 --- a/sentry_sdk/ai/monitoring.py +++ b/sentry_sdk/ai/monitoring.py @@ -33,7 +33,7 @@ def sync_wrapped(*args, **kwargs): curr_pipeline = _ai_pipeline_name.get() op = span_kwargs.get("op", "ai.run" if curr_pipeline else "ai.pipeline") - with start_span(description=description, op=op, **span_kwargs) as span: + with start_span(name=description, op=op, **span_kwargs) as span: for k, v in kwargs.pop("sentry_tags", {}).items(): span.set_tag(k, v) for k, v in kwargs.pop("sentry_data", {}).items(): @@ -62,7 +62,7 @@ async def async_wrapped(*args, **kwargs): curr_pipeline = _ai_pipeline_name.get() op = span_kwargs.get("op", "ai.run" if curr_pipeline else "ai.pipeline") - with start_span(description=description, op=op, **span_kwargs) as span: + with start_span(name=description, op=op, **span_kwargs) as span: for k, v in kwargs.pop("sentry_tags", {}).items(): span.set_tag(k, v) for k, v in kwargs.pop("sentry_data", {}).items(): diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index 33f2fc095c..a447b67f38 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -205,7 +205,7 @@ async def on_request_start(session, trace_config_ctx, params): span = sentry_sdk.start_span( op=OP.HTTP_CLIENT, - description="%s %s" + name="%s %s" % (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE), origin=AioHttpIntegration.origin, ) diff --git a/sentry_sdk/integrations/anthropic.py b/sentry_sdk/integrations/anthropic.py index 41d8e9d7d5..f54708eba5 100644 --- a/sentry_sdk/integrations/anthropic.py +++ b/sentry_sdk/integrations/anthropic.py @@ -94,7 +94,7 @@ def _sentry_patched_create(*args, **kwargs): span = sentry_sdk.start_span( op=OP.ANTHROPIC_MESSAGES_CREATE, - description="Anthropic messages create", + name="Anthropic messages create", origin=AnthropicIntegration.origin, ) span.__enter__() diff --git a/sentry_sdk/integrations/arq.py b/sentry_sdk/integrations/arq.py index 7a9f7a747d..4640204725 100644 --- a/sentry_sdk/integrations/arq.py +++ b/sentry_sdk/integrations/arq.py @@ -79,7 +79,7 @@ async def _sentry_enqueue_job(self, function, *args, **kwargs): return await old_enqueue_job(self, function, *args, **kwargs) with sentry_sdk.start_span( - op=OP.QUEUE_SUBMIT_ARQ, description=function, origin=ArqIntegration.origin + op=OP.QUEUE_SUBMIT_ARQ, name=function, origin=ArqIntegration.origin ): return await old_enqueue_job(self, function, *args, **kwargs) diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py index 313a306164..7021d7fceb 100644 --- a/sentry_sdk/integrations/asyncio.py +++ b/sentry_sdk/integrations/asyncio.py @@ -46,7 +46,7 @@ async def _coro_creating_hub_and_span(): with sentry_sdk.isolation_scope(): with sentry_sdk.start_span( op=OP.FUNCTION, - description=get_name(coro), + name=get_name(coro), origin=AsyncioIntegration.origin, ): try: diff --git a/sentry_sdk/integrations/asyncpg.py b/sentry_sdk/integrations/asyncpg.py index 4c1611613b..b05d5615ba 100644 --- a/sentry_sdk/integrations/asyncpg.py +++ b/sentry_sdk/integrations/asyncpg.py @@ -165,7 +165,7 @@ async def _inner(*args: Any, **kwargs: Any) -> T: with sentry_sdk.start_span( op=OP.DB, - description="connect", + name="connect", origin=AsyncPGIntegration.origin, ) as span: span.set_data(SPANDATA.DB_SYSTEM, "postgresql") diff --git a/sentry_sdk/integrations/boto3.py b/sentry_sdk/integrations/boto3.py index 8a59b9b797..c8da56fb14 100644 --- a/sentry_sdk/integrations/boto3.py +++ b/sentry_sdk/integrations/boto3.py @@ -69,7 +69,7 @@ def _sentry_request_created(service_id, request, operation_name, **kwargs): description = "aws.%s.%s" % (service_id, operation_name) span = sentry_sdk.start_span( op=OP.HTTP_CLIENT, - description=description, + name=description, origin=Boto3Integration.origin, ) @@ -107,7 +107,7 @@ def _sentry_after_call(context, parsed, **kwargs): streaming_span = span.start_child( op=OP.HTTP_CLIENT_STREAM, - description=span.description, + name=span.description, origin=Boto3Integration.origin, ) diff --git a/sentry_sdk/integrations/celery/__init__.py b/sentry_sdk/integrations/celery/__init__.py index 88a2119c09..28a44015aa 100644 --- a/sentry_sdk/integrations/celery/__init__.py +++ b/sentry_sdk/integrations/celery/__init__.py @@ -274,7 +274,7 @@ def apply_async(*args, **kwargs): span_mgr = ( sentry_sdk.start_span( op=OP.QUEUE_SUBMIT_CELERY, - description=task_name, + name=task_name, origin=CeleryIntegration.origin, ) if not task_started_from_beat @@ -374,7 +374,7 @@ def _inner(*args, **kwargs): try: with sentry_sdk.start_span( op=OP.QUEUE_PROCESS, - description=task.name, + name=task.name, origin=CeleryIntegration.origin, ) as span: _set_messaging_destination_name(task, span) @@ -503,7 +503,7 @@ def sentry_publish(self, *args, **kwargs): with sentry_sdk.start_span( op=OP.QUEUE_PUBLISH, - description=task_name, + name=task_name, origin=CeleryIntegration.origin, ) as span: if task_id is not None: diff --git a/sentry_sdk/integrations/clickhouse_driver.py b/sentry_sdk/integrations/clickhouse_driver.py index 02707fb7c5..daf4c2257c 100644 --- a/sentry_sdk/integrations/clickhouse_driver.py +++ b/sentry_sdk/integrations/clickhouse_driver.py @@ -83,7 +83,7 @@ def _inner(*args: P.args, **kwargs: P.kwargs) -> T: span = sentry_sdk.start_span( op=OP.DB, - description=query, + name=query, origin=ClickhouseDriverIntegration.origin, ) diff --git a/sentry_sdk/integrations/cohere.py b/sentry_sdk/integrations/cohere.py index 1d4e86a71b..388b86f1e0 100644 --- a/sentry_sdk/integrations/cohere.py +++ b/sentry_sdk/integrations/cohere.py @@ -142,7 +142,7 @@ def new_chat(*args, **kwargs): span = sentry_sdk.start_span( op=consts.OP.COHERE_CHAT_COMPLETIONS_CREATE, - description="cohere.client.Chat", + name="cohere.client.Chat", origin=CohereIntegration.origin, ) span.__enter__() @@ -227,7 +227,7 @@ def new_embed(*args, **kwargs): # type: (*Any, **Any) -> Any with sentry_sdk.start_span( op=consts.OP.COHERE_EMBEDDINGS_CREATE, - description="Cohere Embedding Creation", + name="Cohere Embedding Creation", origin=CohereIntegration.origin, ) as span: integration = sentry_sdk.get_client().get_integration(CohereIntegration) diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index 8fce1d138e..f6821dfa18 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -683,7 +683,7 @@ def connect(self): with sentry_sdk.start_span( op=OP.DB, - description="connect", + name="connect", origin=DjangoIntegration.origin_db, ) as span: _set_db_data(span, self) diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py index aa2f3e8c6d..bcc83b8e59 100644 --- a/sentry_sdk/integrations/django/asgi.py +++ b/sentry_sdk/integrations/django/asgi.py @@ -174,7 +174,7 @@ async def sentry_wrapped_callback(request, *args, **kwargs): with sentry_sdk.start_span( op=OP.VIEW_RENDER, - description=request.resolver_match.view_name, + name=request.resolver_match.view_name, origin=DjangoIntegration.origin, ): return await callback(request, *args, **kwargs) diff --git a/sentry_sdk/integrations/django/caching.py b/sentry_sdk/integrations/django/caching.py index 25b04f4820..4bd7cb7236 100644 --- a/sentry_sdk/integrations/django/caching.py +++ b/sentry_sdk/integrations/django/caching.py @@ -52,7 +52,7 @@ def _instrument_call( with sentry_sdk.start_span( op=op, - description=description, + name=description, origin=DjangoIntegration.origin, ) as span: value = original_method(*args, **kwargs) diff --git a/sentry_sdk/integrations/django/middleware.py b/sentry_sdk/integrations/django/middleware.py index 2cde251fd3..245276566e 100644 --- a/sentry_sdk/integrations/django/middleware.py +++ b/sentry_sdk/integrations/django/middleware.py @@ -87,7 +87,7 @@ def _check_middleware_span(old_method): middleware_span = sentry_sdk.start_span( op=OP.MIDDLEWARE_DJANGO, - description=description, + name=description, origin=DjangoIntegration.origin, ) middleware_span.set_tag("django.function_name", function_name) diff --git a/sentry_sdk/integrations/django/signals_handlers.py b/sentry_sdk/integrations/django/signals_handlers.py index dd0eabe4a7..cb0f8b9d2e 100644 --- a/sentry_sdk/integrations/django/signals_handlers.py +++ b/sentry_sdk/integrations/django/signals_handlers.py @@ -66,7 +66,7 @@ def wrapper(*args, **kwargs): signal_name = _get_receiver_name(receiver) with sentry_sdk.start_span( op=OP.EVENT_DJANGO, - description=signal_name, + name=signal_name, origin=DjangoIntegration.origin, ) as span: span.set_data("signal", signal_name) diff --git a/sentry_sdk/integrations/django/templates.py b/sentry_sdk/integrations/django/templates.py index 6edcdebf73..10e8a924b7 100644 --- a/sentry_sdk/integrations/django/templates.py +++ b/sentry_sdk/integrations/django/templates.py @@ -70,7 +70,7 @@ def rendered_content(self): # type: (SimpleTemplateResponse) -> str with sentry_sdk.start_span( op=OP.TEMPLATE_RENDER, - description=_get_template_name_description(self.template_name), + name=_get_template_name_description(self.template_name), origin=DjangoIntegration.origin, ) as span: span.set_data("context", self.context_data) @@ -98,7 +98,7 @@ def render(request, template_name, context=None, *args, **kwargs): with sentry_sdk.start_span( op=OP.TEMPLATE_RENDER, - description=_get_template_name_description(template_name), + name=_get_template_name_description(template_name), origin=DjangoIntegration.origin, ) as span: span.set_data("context", context) diff --git a/sentry_sdk/integrations/django/views.py b/sentry_sdk/integrations/django/views.py index a81ddd601f..cb81d3555c 100644 --- a/sentry_sdk/integrations/django/views.py +++ b/sentry_sdk/integrations/django/views.py @@ -35,7 +35,7 @@ def sentry_patched_render(self): # type: (SimpleTemplateResponse) -> Any with sentry_sdk.start_span( op=OP.VIEW_RESPONSE_RENDER, - description="serialize response", + name="serialize response", origin=DjangoIntegration.origin, ): return old_render(self) @@ -84,7 +84,7 @@ def sentry_wrapped_callback(request, *args, **kwargs): with sentry_sdk.start_span( op=OP.VIEW_RENDER, - description=request.resolver_match.view_name, + name=request.resolver_match.view_name, origin=DjangoIntegration.origin, ): return callback(request, *args, **kwargs) diff --git a/sentry_sdk/integrations/graphene.py b/sentry_sdk/integrations/graphene.py index 1b33bf76bf..03731dcaaa 100644 --- a/sentry_sdk/integrations/graphene.py +++ b/sentry_sdk/integrations/graphene.py @@ -142,9 +142,9 @@ def graphql_span(schema, source, kwargs): scope = sentry_sdk.get_current_scope() if scope.span: - _graphql_span = scope.span.start_child(op=op, description=operation_name) + _graphql_span = scope.span.start_child(op=op, name=operation_name) else: - _graphql_span = sentry_sdk.start_span(op=op, description=operation_name) + _graphql_span = sentry_sdk.start_span(op=op, name=operation_name) _graphql_span.set_data("graphql.document", source) _graphql_span.set_data("graphql.operation.name", operation_name) diff --git a/sentry_sdk/integrations/grpc/aio/client.py b/sentry_sdk/integrations/grpc/aio/client.py index 143f0e43a9..e8adeba05e 100644 --- a/sentry_sdk/integrations/grpc/aio/client.py +++ b/sentry_sdk/integrations/grpc/aio/client.py @@ -50,7 +50,7 @@ async def intercept_unary_unary( with sentry_sdk.start_span( op=OP.GRPC_CLIENT, - description="unary unary call to %s" % method.decode(), + name="unary unary call to %s" % method.decode(), origin=SPAN_ORIGIN, ) as span: span.set_data("type", "unary unary") @@ -80,7 +80,7 @@ async def intercept_unary_stream( with sentry_sdk.start_span( op=OP.GRPC_CLIENT, - description="unary stream call to %s" % method.decode(), + name="unary stream call to %s" % method.decode(), origin=SPAN_ORIGIN, ) as span: span.set_data("type", "unary stream") diff --git a/sentry_sdk/integrations/grpc/client.py b/sentry_sdk/integrations/grpc/client.py index 2155824eaf..a5b4f9f52e 100644 --- a/sentry_sdk/integrations/grpc/client.py +++ b/sentry_sdk/integrations/grpc/client.py @@ -29,7 +29,7 @@ def intercept_unary_unary(self, continuation, client_call_details, request): with sentry_sdk.start_span( op=OP.GRPC_CLIENT, - description="unary unary call to %s" % method, + name="unary unary call to %s" % method, origin=SPAN_ORIGIN, ) as span: span.set_data("type", "unary unary") @@ -50,7 +50,7 @@ def intercept_unary_stream(self, continuation, client_call_details, request): with sentry_sdk.start_span( op=OP.GRPC_CLIENT, - description="unary stream call to %s" % method, + name="unary stream call to %s" % method, origin=SPAN_ORIGIN, ) as span: span.set_data("type", "unary stream") diff --git a/sentry_sdk/integrations/httpx.py b/sentry_sdk/integrations/httpx.py index 3ab47bce70..6f80b93f4d 100644 --- a/sentry_sdk/integrations/httpx.py +++ b/sentry_sdk/integrations/httpx.py @@ -53,7 +53,7 @@ def send(self, request, **kwargs): with sentry_sdk.start_span( op=OP.HTTP_CLIENT, - description="%s %s" + name="%s %s" % ( request.method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE, @@ -109,7 +109,7 @@ async def send(self, request, **kwargs): with sentry_sdk.start_span( op=OP.HTTP_CLIENT, - description="%s %s" + name="%s %s" % ( request.method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE, diff --git a/sentry_sdk/integrations/huey.py b/sentry_sdk/integrations/huey.py index 98fab46711..7db57680f6 100644 --- a/sentry_sdk/integrations/huey.py +++ b/sentry_sdk/integrations/huey.py @@ -59,7 +59,7 @@ def _sentry_enqueue(self, task): # type: (Huey, Task) -> Optional[Union[Result, ResultGroup]] with sentry_sdk.start_span( op=OP.QUEUE_SUBMIT_HUEY, - description=task.name, + name=task.name, origin=HueyIntegration.origin, ): if not isinstance(task, PeriodicTask): diff --git a/sentry_sdk/integrations/huggingface_hub.py b/sentry_sdk/integrations/huggingface_hub.py index c7ed6907dd..857138ca1d 100644 --- a/sentry_sdk/integrations/huggingface_hub.py +++ b/sentry_sdk/integrations/huggingface_hub.py @@ -73,7 +73,7 @@ def new_text_generation(*args, **kwargs): span = sentry_sdk.start_span( op=consts.OP.HUGGINGFACE_HUB_CHAT_COMPLETIONS_CREATE, - description="Text Generation", + name="Text Generation", origin=HuggingfaceHubIntegration.origin, ) span.__enter__() diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index a77dec430d..fefc4619db 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -146,8 +146,8 @@ def _create_span(self, run_id, parent_id, **kwargs): watched_span = WatchedSpan(sentry_sdk.start_span(**kwargs)) if kwargs.get("op", "").startswith("ai.pipeline."): - if kwargs.get("description"): - set_ai_pipeline_name(kwargs.get("description")) + if kwargs.get("name"): + set_ai_pipeline_name(kwargs.get("name")) watched_span.is_pipeline = True watched_span.span.__enter__() @@ -186,7 +186,7 @@ def on_llm_start( run_id, kwargs.get("parent_run_id"), op=OP.LANGCHAIN_RUN, - description=kwargs.get("name") or "Langchain LLM call", + name=kwargs.get("name") or "Langchain LLM call", origin=LangchainIntegration.origin, ) span = watched_span.span @@ -208,7 +208,7 @@ def on_chat_model_start(self, serialized, messages, *, run_id, **kwargs): run_id, kwargs.get("parent_run_id"), op=OP.LANGCHAIN_CHAT_COMPLETIONS_CREATE, - description=kwargs.get("name") or "Langchain Chat Model", + name=kwargs.get("name") or "Langchain Chat Model", origin=LangchainIntegration.origin, ) span = watched_span.span @@ -312,7 +312,7 @@ def on_chain_start(self, serialized, inputs, *, run_id, **kwargs): if kwargs.get("parent_run_id") is not None else OP.LANGCHAIN_PIPELINE ), - description=kwargs.get("name") or "Chain execution", + name=kwargs.get("name") or "Chain execution", origin=LangchainIntegration.origin, ) metadata = kwargs.get("metadata") @@ -345,7 +345,7 @@ def on_agent_action(self, action, *, run_id, **kwargs): run_id, kwargs.get("parent_run_id"), op=OP.LANGCHAIN_AGENT, - description=action.tool or "AI tool usage", + name=action.tool or "AI tool usage", origin=LangchainIntegration.origin, ) if action.tool_input and should_send_default_pii() and self.include_prompts: @@ -378,9 +378,7 @@ def on_tool_start(self, serialized, input_str, *, run_id, **kwargs): run_id, kwargs.get("parent_run_id"), op=OP.LANGCHAIN_TOOL, - description=serialized.get("name") - or kwargs.get("name") - or "AI tool usage", + name=serialized.get("name") or kwargs.get("name") or "AI tool usage", origin=LangchainIntegration.origin, ) if should_send_default_pii() and self.include_prompts: diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index bf4fdf49bf..4b04dada8a 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -139,7 +139,7 @@ async def _create_span_call(self, scope, receive, send): middleware_name = self.__class__.__name__ with sentry_sdk.start_span( op=OP.MIDDLEWARE_LITESTAR, - description=middleware_name, + name=middleware_name, origin=LitestarIntegration.origin, ) as middleware_span: middleware_span.set_tag("litestar.middleware_name", middleware_name) @@ -151,7 +151,7 @@ async def _sentry_receive(*args, **kwargs): return await receive(*args, **kwargs) with sentry_sdk.start_span( op=OP.MIDDLEWARE_LITESTAR_RECEIVE, - description=getattr(receive, "__qualname__", str(receive)), + name=getattr(receive, "__qualname__", str(receive)), origin=LitestarIntegration.origin, ) as span: span.set_tag("litestar.middleware_name", middleware_name) @@ -168,7 +168,7 @@ async def _sentry_send(message): return await send(message) with sentry_sdk.start_span( op=OP.MIDDLEWARE_LITESTAR_SEND, - description=getattr(send, "__qualname__", str(send)), + name=getattr(send, "__qualname__", str(send)), origin=LitestarIntegration.origin, ) as span: span.set_tag("litestar.middleware_name", middleware_name) diff --git a/sentry_sdk/integrations/openai.py b/sentry_sdk/integrations/openai.py index 5cf0817c87..b8c758f75f 100644 --- a/sentry_sdk/integrations/openai.py +++ b/sentry_sdk/integrations/openai.py @@ -133,7 +133,7 @@ def new_chat_completion(*args, **kwargs): span = sentry_sdk.start_span( op=consts.OP.OPENAI_CHAT_COMPLETIONS_CREATE, - description="Chat Completion", + name="Chat Completion", origin=OpenAIIntegration.origin, ) span.__enter__() @@ -223,7 +223,7 @@ def new_embeddings_create(*args, **kwargs): # type: (*Any, **Any) -> Any with sentry_sdk.start_span( op=consts.OP.OPENAI_EMBEDDINGS_CREATE, - description="OpenAI Embedding Creation", + name="OpenAI Embedding Creation", origin=OpenAIIntegration.origin, ) as span: integration = sentry_sdk.get_client().get_integration(OpenAIIntegration) diff --git a/sentry_sdk/integrations/opentelemetry/span_processor.py b/sentry_sdk/integrations/opentelemetry/span_processor.py index 1a2951983e..e00562a509 100644 --- a/sentry_sdk/integrations/opentelemetry/span_processor.py +++ b/sentry_sdk/integrations/opentelemetry/span_processor.py @@ -147,7 +147,7 @@ def on_start(self, otel_span, parent_context=None): if sentry_parent_span: sentry_span = sentry_parent_span.start_child( span_id=trace_data["span_id"], - description=otel_span.name, + name=otel_span.name, start_timestamp=start_timestamp, instrumenter=INSTRUMENTER.OTEL, origin=SPAN_ORIGIN, diff --git a/sentry_sdk/integrations/pymongo.py b/sentry_sdk/integrations/pymongo.py index ebfaa19766..f65ad73687 100644 --- a/sentry_sdk/integrations/pymongo.py +++ b/sentry_sdk/integrations/pymongo.py @@ -158,7 +158,7 @@ def started(self, event): query = json.dumps(command, default=str) span = sentry_sdk.start_span( op=OP.DB, - description=query, + name=query, origin=PyMongoIntegration.origin, ) diff --git a/sentry_sdk/integrations/ray.py b/sentry_sdk/integrations/ray.py index bafd42c8d6..2f5086ed92 100644 --- a/sentry_sdk/integrations/ray.py +++ b/sentry_sdk/integrations/ray.py @@ -88,7 +88,7 @@ def _remote_method_with_header_propagation(*args, **kwargs): """ with sentry_sdk.start_span( op=OP.QUEUE_SUBMIT_RAY, - description=qualname_from_function(f), + name=qualname_from_function(f), origin=RayIntegration.origin, ) as span: tracing = { diff --git a/sentry_sdk/integrations/redis/_async_common.py b/sentry_sdk/integrations/redis/_async_common.py index d311b3fa0f..196e85e74b 100644 --- a/sentry_sdk/integrations/redis/_async_common.py +++ b/sentry_sdk/integrations/redis/_async_common.py @@ -37,7 +37,7 @@ async def _sentry_execute(self, *args, **kwargs): with sentry_sdk.start_span( op=OP.DB_REDIS, - description="redis.pipeline.execute", + name="redis.pipeline.execute", origin=SPAN_ORIGIN, ) as span: with capture_internal_exceptions(): @@ -78,7 +78,7 @@ async def _sentry_execute_command(self, name, *args, **kwargs): if cache_properties["is_cache_key"] and cache_properties["op"] is not None: cache_span = sentry_sdk.start_span( op=cache_properties["op"], - description=cache_properties["description"], + name=cache_properties["description"], origin=SPAN_ORIGIN, ) cache_span.__enter__() @@ -87,7 +87,7 @@ async def _sentry_execute_command(self, name, *args, **kwargs): db_span = sentry_sdk.start_span( op=db_properties["op"], - description=db_properties["description"], + name=db_properties["description"], origin=SPAN_ORIGIN, ) db_span.__enter__() diff --git a/sentry_sdk/integrations/redis/_sync_common.py b/sentry_sdk/integrations/redis/_sync_common.py index 177e89143d..ef10e9e4f0 100644 --- a/sentry_sdk/integrations/redis/_sync_common.py +++ b/sentry_sdk/integrations/redis/_sync_common.py @@ -38,7 +38,7 @@ def sentry_patched_execute(self, *args, **kwargs): with sentry_sdk.start_span( op=OP.DB_REDIS, - description="redis.pipeline.execute", + name="redis.pipeline.execute", origin=SPAN_ORIGIN, ) as span: with capture_internal_exceptions(): @@ -83,7 +83,7 @@ def sentry_patched_execute_command(self, name, *args, **kwargs): if cache_properties["is_cache_key"] and cache_properties["op"] is not None: cache_span = sentry_sdk.start_span( op=cache_properties["op"], - description=cache_properties["description"], + name=cache_properties["description"], origin=SPAN_ORIGIN, ) cache_span.__enter__() @@ -92,7 +92,7 @@ def sentry_patched_execute_command(self, name, *args, **kwargs): db_span = sentry_sdk.start_span( op=db_properties["op"], - description=db_properties["description"], + name=db_properties["description"], origin=SPAN_ORIGIN, ) db_span.__enter__() diff --git a/sentry_sdk/integrations/socket.py b/sentry_sdk/integrations/socket.py index beec7dbf3e..0866ceb608 100644 --- a/sentry_sdk/integrations/socket.py +++ b/sentry_sdk/integrations/socket.py @@ -55,7 +55,7 @@ def create_connection( with sentry_sdk.start_span( op=OP.SOCKET_CONNECTION, - description=_get_span_description(address[0], address[1]), + name=_get_span_description(address[0], address[1]), origin=SocketIntegration.origin, ) as span: span.set_data("address", address) @@ -81,7 +81,7 @@ def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0): with sentry_sdk.start_span( op=OP.SOCKET_DNS, - description=_get_span_description(host, port), + name=_get_span_description(host, port), origin=SocketIntegration.origin, ) as span: span.set_data("host", host) diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index 9df30fba72..1179003561 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -132,7 +132,7 @@ async def _create_span_call(app, scope, receive, send, **kwargs): with sentry_sdk.start_span( op=OP.MIDDLEWARE_STARLETTE, - description=middleware_name, + name=middleware_name, origin=StarletteIntegration.origin, ) as middleware_span: middleware_span.set_tag("starlette.middleware_name", middleware_name) @@ -142,7 +142,7 @@ async def _sentry_receive(*args, **kwargs): # type: (*Any, **Any) -> Any with sentry_sdk.start_span( op=OP.MIDDLEWARE_STARLETTE_RECEIVE, - description=getattr(receive, "__qualname__", str(receive)), + name=getattr(receive, "__qualname__", str(receive)), origin=StarletteIntegration.origin, ) as span: span.set_tag("starlette.middleware_name", middleware_name) @@ -157,7 +157,7 @@ async def _sentry_send(*args, **kwargs): # type: (*Any, **Any) -> Any with sentry_sdk.start_span( op=OP.MIDDLEWARE_STARLETTE_SEND, - description=getattr(send, "__qualname__", str(send)), + name=getattr(send, "__qualname__", str(send)), origin=StarletteIntegration.origin, ) as span: span.set_tag("starlette.middleware_name", middleware_name) diff --git a/sentry_sdk/integrations/starlite.py b/sentry_sdk/integrations/starlite.py index 72bea97854..8714ee2f08 100644 --- a/sentry_sdk/integrations/starlite.py +++ b/sentry_sdk/integrations/starlite.py @@ -138,7 +138,7 @@ async def _create_span_call(self, scope, receive, send): middleware_name = self.__class__.__name__ with sentry_sdk.start_span( op=OP.MIDDLEWARE_STARLITE, - description=middleware_name, + name=middleware_name, origin=StarliteIntegration.origin, ) as middleware_span: middleware_span.set_tag("starlite.middleware_name", middleware_name) @@ -150,7 +150,7 @@ async def _sentry_receive(*args, **kwargs): return await receive(*args, **kwargs) with sentry_sdk.start_span( op=OP.MIDDLEWARE_STARLITE_RECEIVE, - description=getattr(receive, "__qualname__", str(receive)), + name=getattr(receive, "__qualname__", str(receive)), origin=StarliteIntegration.origin, ) as span: span.set_tag("starlite.middleware_name", middleware_name) @@ -167,7 +167,7 @@ async def _sentry_send(message): return await send(message) with sentry_sdk.start_span( op=OP.MIDDLEWARE_STARLITE_SEND, - description=getattr(send, "__qualname__", str(send)), + name=getattr(send, "__qualname__", str(send)), origin=StarliteIntegration.origin, ) as span: span.set_tag("starlite.middleware_name", middleware_name) diff --git a/sentry_sdk/integrations/stdlib.py b/sentry_sdk/integrations/stdlib.py index bef29ebec7..287c8cb272 100644 --- a/sentry_sdk/integrations/stdlib.py +++ b/sentry_sdk/integrations/stdlib.py @@ -90,7 +90,7 @@ def putrequest(self, method, url, *args, **kwargs): span = sentry_sdk.start_span( op=OP.HTTP_CLIENT, - description="%s %s" + name="%s %s" % (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE), origin="auto.http.stdlib.httplib", ) @@ -203,7 +203,7 @@ def sentry_patched_popen_init(self, *a, **kw): with sentry_sdk.start_span( op=OP.SUBPROCESS, - description=description, + name=description, origin="auto.subprocess.stdlib.subprocess", ) as span: for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers( diff --git a/sentry_sdk/integrations/strawberry.py b/sentry_sdk/integrations/strawberry.py index ac792c8612..521609d379 100644 --- a/sentry_sdk/integrations/strawberry.py +++ b/sentry_sdk/integrations/strawberry.py @@ -182,13 +182,13 @@ def on_operation(self): if span: self.graphql_span = span.start_child( op=op, - description=description, + name=description, origin=StrawberryIntegration.origin, ) else: self.graphql_span = sentry_sdk.start_span( op=op, - description=description, + name=description, origin=StrawberryIntegration.origin, ) @@ -211,7 +211,7 @@ def on_validate(self): # type: () -> Generator[None, None, None] self.validation_span = self.graphql_span.start_child( op=OP.GRAPHQL_VALIDATE, - description="validation", + name="validation", origin=StrawberryIntegration.origin, ) @@ -223,7 +223,7 @@ def on_parse(self): # type: () -> Generator[None, None, None] self.parsing_span = self.graphql_span.start_child( op=OP.GRAPHQL_PARSE, - description="parsing", + name="parsing", origin=StrawberryIntegration.origin, ) @@ -253,7 +253,7 @@ async def resolve(self, _next, root, info, *args, **kwargs): with self.graphql_span.start_child( op=OP.GRAPHQL_RESOLVE, - description="resolving {}".format(field_path), + name="resolving {}".format(field_path), origin=StrawberryIntegration.origin, ) as span: span.set_data("graphql.field_name", info.field_name) @@ -274,7 +274,7 @@ def resolve(self, _next, root, info, *args, **kwargs): with self.graphql_span.start_child( op=OP.GRAPHQL_RESOLVE, - description="resolving {}".format(field_path), + name="resolving {}".format(field_path), origin=StrawberryIntegration.origin, ) as span: span.set_data("graphql.field_name", info.field_name) diff --git a/sentry_sdk/metrics.py b/sentry_sdk/metrics.py index da6d77c69a..f6e9fd6bde 100644 --- a/sentry_sdk/metrics.py +++ b/sentry_sdk/metrics.py @@ -826,7 +826,7 @@ def __enter__(self): # type: (...) -> _Timing self.entered = TIMING_FUNCTIONS[self.unit]() self._validate_invocation("context-manager") - self._span = sentry_sdk.start_span(op="metric.timing", description=self.key) + self._span = sentry_sdk.start_span(op="metric.timing", name=self.key) if self.tags: for key, value in self.tags.items(): if isinstance(value, (tuple, list)): diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 0df1ae5bd4..7c07f31e9f 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -146,7 +146,7 @@ def record_sql_queries( with sentry_sdk.start_span( op=OP.DB, - description=query, + name=query, origin=span_origin, ) as span: for k, v in data.items(): @@ -649,7 +649,7 @@ async def func_with_tracing(*args, **kwargs): with span.start_child( op=OP.FUNCTION, - description=qualname_from_function(func), + name=qualname_from_function(func), ): return await func(*args, **kwargs) @@ -677,7 +677,7 @@ def func_with_tracing(*args, **kwargs): with span.start_child( op=OP.FUNCTION, - description=qualname_from_function(func), + name=qualname_from_function(func), ): return func(*args, **kwargs) diff --git a/tests/integrations/asyncio/test_asyncio.py b/tests/integrations/asyncio/test_asyncio.py index a7ecd8034a..c9e572ca73 100644 --- a/tests/integrations/asyncio/test_asyncio.py +++ b/tests/integrations/asyncio/test_asyncio.py @@ -75,7 +75,7 @@ async def test_create_task( events = capture_events() with sentry_sdk.start_transaction(name="test_transaction_for_create_task"): - with sentry_sdk.start_span(op="root", description="not so important"): + with sentry_sdk.start_span(op="root", name="not so important"): tasks = [event_loop.create_task(foo()), event_loop.create_task(bar())] await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION) @@ -118,7 +118,7 @@ async def test_gather( events = capture_events() with sentry_sdk.start_transaction(name="test_transaction_for_gather"): - with sentry_sdk.start_span(op="root", description="not so important"): + with sentry_sdk.start_span(op="root", name="not so important"): await asyncio.gather(foo(), bar(), return_exceptions=True) sentry_sdk.flush() @@ -161,7 +161,7 @@ async def test_exception( events = capture_events() with sentry_sdk.start_transaction(name="test_exception"): - with sentry_sdk.start_span(op="root", description="not so important"): + with sentry_sdk.start_span(op="root", name="not so important"): tasks = [event_loop.create_task(boom()), event_loop.create_task(bar())] await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION) diff --git a/tests/integrations/grpc/test_grpc.py b/tests/integrations/grpc/test_grpc.py index 66b65bbbf7..a8872ef0b5 100644 --- a/tests/integrations/grpc/test_grpc.py +++ b/tests/integrations/grpc/test_grpc.py @@ -357,7 +357,7 @@ class TestService(gRPCTestServiceServicer): def TestServe(request, context): # noqa: N802 with start_span( op="test", - description="test", + name="test", origin="auto.grpc.grpc.TestService", ): pass diff --git a/tests/integrations/grpc/test_grpc_aio.py b/tests/integrations/grpc/test_grpc_aio.py index 2ff91dcf16..fff22626d9 100644 --- a/tests/integrations/grpc/test_grpc_aio.py +++ b/tests/integrations/grpc/test_grpc_aio.py @@ -282,7 +282,7 @@ def __init__(self): async def TestServe(cls, request, context): # noqa: N802 with start_span( op="test", - description="test", + name="test", origin="auto.grpc.grpc.TestService.aio", ): pass diff --git a/tests/integrations/opentelemetry/test_span_processor.py b/tests/integrations/opentelemetry/test_span_processor.py index 7045b52f17..ec5cf6af23 100644 --- a/tests/integrations/opentelemetry/test_span_processor.py +++ b/tests/integrations/opentelemetry/test_span_processor.py @@ -361,7 +361,7 @@ def test_on_start_child(): fake_span.start_child.assert_called_once_with( span_id="1234567890abcdef", - description="Sample OTel Span", + name="Sample OTel Span", start_timestamp=datetime.fromtimestamp( otel_span.start_time / 1e9, timezone.utc ), diff --git a/tests/integrations/ray/test_ray.py b/tests/integrations/ray/test_ray.py index f1c109533b..02c08c2a9e 100644 --- a/tests/integrations/ray/test_ray.py +++ b/tests/integrations/ray/test_ray.py @@ -52,7 +52,7 @@ def test_ray_tracing(): @ray.remote def example_task(): - with sentry_sdk.start_span(op="task", description="example task step"): + with sentry_sdk.start_span(op="task", name="example task step"): ... return sentry_sdk.get_client().transport.envelopes @@ -177,7 +177,7 @@ def __init__(self): self.n = 0 def increment(self): - with sentry_sdk.start_span(op="task", description="example task step"): + with sentry_sdk.start_span(op="task", name="example task step"): self.n += 1 return sentry_sdk.get_client().transport.envelopes diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 2b6b280c1e..0d14fae352 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -80,7 +80,7 @@ def test_propagates_threadpool_hub(sentry_init, capture_events, propagate_hub): events = capture_events() def double(number): - with sentry_sdk.start_span(op="task", description=str(number)): + with sentry_sdk.start_span(op="task", name=str(number)): return number * 2 with sentry_sdk.start_transaction(name="test_handles_threadpool"): diff --git a/tests/test_scrubber.py b/tests/test_scrubber.py index a544c31cc0..2c462153dd 100644 --- a/tests/test_scrubber.py +++ b/tests/test_scrubber.py @@ -146,7 +146,7 @@ def test_span_data_scrubbing(sentry_init, capture_events): events = capture_events() with start_transaction(name="hi"): - with start_span(op="foo", description="bar") as span: + with start_span(op="foo", name="bar") as span: span.set_data("password", "secret") span.set_data("datafoo", "databar") diff --git a/tests/tracing/test_decorator.py b/tests/tracing/test_decorator.py index 584268fbdd..18a66bd43e 100644 --- a/tests/tracing/test_decorator.py +++ b/tests/tracing/test_decorator.py @@ -26,7 +26,7 @@ def test_trace_decorator(): result2 = start_child_span_decorator(my_example_function)() fake_start_child.assert_called_once_with( - op="function", description="test_decorator.my_example_function" + op="function", name="test_decorator.my_example_function" ) assert result2 == "return_of_sync_function" @@ -58,7 +58,7 @@ async def test_trace_decorator_async(): result2 = await start_child_span_decorator(my_async_example_function)() fake_start_child.assert_called_once_with( op="function", - description="test_decorator.my_async_example_function", + name="test_decorator.my_async_example_function", ) assert result2 == "return_of_async_function" diff --git a/tests/tracing/test_integration_tests.py b/tests/tracing/test_integration_tests.py index 47170af97b..e27dbea901 100644 --- a/tests/tracing/test_integration_tests.py +++ b/tests/tracing/test_integration_tests.py @@ -23,10 +23,10 @@ def test_basic(sentry_init, capture_events, sample_rate): with start_transaction(name="hi") as transaction: transaction.set_status(SPANSTATUS.OK) with pytest.raises(ZeroDivisionError): - with start_span(op="foo", description="foodesc"): + with start_span(op="foo", name="foodesc"): 1 / 0 - with start_span(op="bar", description="bardesc"): + with start_span(op="bar", name="bardesc"): pass if sample_rate: @@ -158,7 +158,7 @@ def test_dynamic_sampling_head_sdk_creates_dsc( assert baggage.third_party_items == "" with start_transaction(transaction): - with start_span(op="foo", description="foodesc"): + with start_span(op="foo", name="foodesc"): pass # finish will create a new baggage entry @@ -211,7 +211,7 @@ def test_memory_usage(sentry_init, capture_events, args, expected_refcount): with start_transaction(name="hi"): for i in range(100): - with start_span(op="helloworld", description="hi {}".format(i)) as span: + with start_span(op="helloworld", name="hi {}".format(i)) as span: def foo(): pass @@ -248,14 +248,14 @@ def capture_envelope(self, envelope): pass def capture_event(self, event): - start_span(op="toolate", description="justdont") + start_span(op="toolate", name="justdont") pass sentry_init(traces_sample_rate=1, transport=CustomTransport()) events = capture_events() with start_transaction(name="hi"): - with start_span(op="bar", description="bardesc"): + with start_span(op="bar", name="bardesc"): pass assert len(events) == 1 @@ -269,7 +269,7 @@ def test_trace_propagation_meta_head_sdk(sentry_init): span = None with start_transaction(transaction): - with start_span(op="foo", description="foodesc") as current_span: + with start_span(op="foo", name="foodesc") as current_span: span = current_span meta = sentry_sdk.get_current_scope().trace_propagation_meta() diff --git a/tests/tracing/test_noop_span.py b/tests/tracing/test_noop_span.py index ec2c7782f3..36778cd485 100644 --- a/tests/tracing/test_noop_span.py +++ b/tests/tracing/test_noop_span.py @@ -23,7 +23,7 @@ def test_noop_start_transaction(sentry_init): def test_noop_start_span(sentry_init): sentry_init(instrumenter="otel") - with sentry_sdk.start_span(op="http", description="GET /") as span: + with sentry_sdk.start_span(op="http", name="GET /") as span: assert isinstance(span, NoOpSpan) assert sentry_sdk.get_current_scope().span is span diff --git a/tests/tracing/test_span_origin.py b/tests/tracing/test_span_origin.py index f880279f08..16635871b3 100644 --- a/tests/tracing/test_span_origin.py +++ b/tests/tracing/test_span_origin.py @@ -6,7 +6,7 @@ def test_span_origin_manual(sentry_init, capture_events): events = capture_events() with start_transaction(name="hi"): - with start_span(op="foo", description="bar"): + with start_span(op="foo", name="bar"): pass (event,) = events @@ -21,11 +21,11 @@ def test_span_origin_custom(sentry_init, capture_events): events = capture_events() with start_transaction(name="hi"): - with start_span(op="foo", description="bar", origin="foo.foo2.foo3"): + with start_span(op="foo", name="bar", origin="foo.foo2.foo3"): pass with start_transaction(name="ho", origin="ho.ho2.ho3"): - with start_span(op="baz", description="qux", origin="baz.baz2.baz3"): + with start_span(op="baz", name="qux", origin="baz.baz2.baz3"): pass (first_transaction, second_transaction) = events From 23ef8cadc796e936744140c6179d674a89542a28 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 13 Sep 2024 12:23:46 +0200 Subject: [PATCH 09/16] Removed experimental explain_plan feature. (#3534) Attaching the database explain plan to a db span was an experimental feature done in an Sentry Hackweek. As we are moving into an Otel world, we remove this experiment from our Repository. There is still a branch experiment/explain_plans on Github to keep the code for future reference: https://github.com/getsentry/sentry-python/tree/experiment/explain_plans (maybe we can copy the code into the Opentelemetry instrumentation if we want to see this feature in the future) --- sentry_sdk/consts.py | 1 - sentry_sdk/db/__init__.py | 0 sentry_sdk/db/explain_plan/__init__.py | 59 ---------------------- sentry_sdk/db/explain_plan/django.py | 48 ------------------ sentry_sdk/db/explain_plan/sqlalchemy.py | 48 ------------------ sentry_sdk/integrations/django/__init__.py | 15 ------ sentry_sdk/integrations/sqlalchemy.py | 13 ----- 7 files changed, 184 deletions(-) delete mode 100644 sentry_sdk/db/__init__.py delete mode 100644 sentry_sdk/db/explain_plan/__init__.py delete mode 100644 sentry_sdk/db/explain_plan/django.py delete mode 100644 sentry_sdk/db/explain_plan/sqlalchemy.py diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 5f79031787..803b159299 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -53,7 +53,6 @@ class EndpointType(Enum): Experiments = TypedDict( "Experiments", { - "attach_explain_plans": dict[str, Any], "max_spans": Optional[int], "record_sql_params": Optional[bool], "continuous_profiling_auto_start": Optional[bool], diff --git a/sentry_sdk/db/__init__.py b/sentry_sdk/db/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/sentry_sdk/db/explain_plan/__init__.py b/sentry_sdk/db/explain_plan/__init__.py deleted file mode 100644 index 1cc475f0f4..0000000000 --- a/sentry_sdk/db/explain_plan/__init__.py +++ /dev/null @@ -1,59 +0,0 @@ -from datetime import datetime, timedelta, timezone -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from typing import Any - - -EXPLAIN_CACHE = {} -EXPLAIN_CACHE_SIZE = 50 -EXPLAIN_CACHE_TIMEOUT_SECONDS = 60 * 60 * 24 - - -def cache_statement(statement, options): - # type: (str, dict[str, Any]) -> None - global EXPLAIN_CACHE - - now = datetime.now(timezone.utc) - explain_cache_timeout_seconds = options.get( - "explain_cache_timeout_seconds", EXPLAIN_CACHE_TIMEOUT_SECONDS - ) - expiration_time = now + timedelta(seconds=explain_cache_timeout_seconds) - - EXPLAIN_CACHE[hash(statement)] = expiration_time - - -def remove_expired_cache_items(): - # type: () -> None - """ - Remove expired cache items from the cache. - """ - global EXPLAIN_CACHE - - now = datetime.now(timezone.utc) - - for key, expiration_time in EXPLAIN_CACHE.items(): - expiration_in_the_past = expiration_time < now - if expiration_in_the_past: - del EXPLAIN_CACHE[key] - - -def should_run_explain_plan(statement, options): - # type: (str, dict[str, Any]) -> bool - """ - Check cache if the explain plan for the given statement should be run. - """ - global EXPLAIN_CACHE - - remove_expired_cache_items() - - key = hash(statement) - if key in EXPLAIN_CACHE: - return False - - explain_cache_size = options.get("explain_cache_size", EXPLAIN_CACHE_SIZE) - cache_is_full = len(EXPLAIN_CACHE.keys()) >= explain_cache_size - if cache_is_full: - return False - - return True diff --git a/sentry_sdk/db/explain_plan/django.py b/sentry_sdk/db/explain_plan/django.py deleted file mode 100644 index 21ebc9c81a..0000000000 --- a/sentry_sdk/db/explain_plan/django.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import TYPE_CHECKING - -from sentry_sdk.db.explain_plan import cache_statement, should_run_explain_plan - -if TYPE_CHECKING: - from typing import Any - from typing import Callable - - from sentry_sdk.tracing import Span - - -def attach_explain_plan_to_span( - span, connection, statement, parameters, mogrify, options -): - # type: (Span, Any, str, Any, Callable[[str, Any], bytes], dict[str, Any]) -> None - """ - Run EXPLAIN or EXPLAIN ANALYZE on the given statement and attach the explain plan to the span data. - - Usage: - ``` - sentry_sdk.init( - dsn="...", - _experiments={ - "attach_explain_plans": { - "explain_cache_size": 1000, # Run explain plan for the 1000 most run queries - "explain_cache_timeout_seconds": 60 * 60 * 24, # Run the explain plan for each statement only every 24 hours - "use_explain_analyze": True, # Run "explain analyze" instead of only "explain" - } - } - ``` - """ - if not statement.strip().upper().startswith("SELECT"): - return - - if not should_run_explain_plan(statement, options): - return - - analyze = "ANALYZE" if options.get("use_explain_analyze", False) else "" - explain_statement = ("EXPLAIN %s " % analyze) + mogrify( - statement, parameters - ).decode("utf-8") - - with connection.cursor() as cursor: - cursor.execute(explain_statement) - explain_plan = [row for row in cursor.fetchall()] - - span.set_data("db.explain_plan", explain_plan) - cache_statement(statement, options) diff --git a/sentry_sdk/db/explain_plan/sqlalchemy.py b/sentry_sdk/db/explain_plan/sqlalchemy.py deleted file mode 100644 index 9320ff8fb3..0000000000 --- a/sentry_sdk/db/explain_plan/sqlalchemy.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import TYPE_CHECKING - -from sentry_sdk.db.explain_plan import cache_statement, should_run_explain_plan -from sentry_sdk.integrations import DidNotEnable - -try: - from sqlalchemy.sql import text # type: ignore -except ImportError: - raise DidNotEnable("SQLAlchemy not installed.") - -if TYPE_CHECKING: - from typing import Any - - from sentry_sdk.tracing import Span - - -def attach_explain_plan_to_span(span, connection, statement, parameters, options): - # type: (Span, Any, str, Any, dict[str, Any]) -> None - """ - Run EXPLAIN or EXPLAIN ANALYZE on the given statement and attach the explain plan to the span data. - - Usage: - ``` - sentry_sdk.init( - dsn="...", - _experiments={ - "attach_explain_plans": { - "explain_cache_size": 1000, # Run explain plan for the 1000 most run queries - "explain_cache_timeout_seconds": 60 * 60 * 24, # Run the explain plan for each statement only every 24 hours - "use_explain_analyze": True, # Run "explain analyze" instead of only "explain" - } - } - ``` - """ - if not statement.strip().upper().startswith("SELECT"): - return - - if not should_run_explain_plan(statement, options): - return - - analyze = "ANALYZE" if options.get("use_explain_analyze", False) else "" - explain_statement = (("EXPLAIN %s " % analyze) + statement) % parameters - - result = connection.execute(text(explain_statement)) - explain_plan = [row for row in result] - - span.set_data("db.explain_plan", explain_plan) - cache_statement(statement, options) diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index f6821dfa18..fce93503e9 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -6,7 +6,6 @@ import sentry_sdk from sentry_sdk.consts import OP, SPANDATA -from sentry_sdk.db.explain_plan.django import attach_explain_plan_to_span from sentry_sdk.scope import add_global_event_processor, should_send_default_pii from sentry_sdk.serializer import add_global_repr_processor from sentry_sdk.tracing import SOURCE_FOR_STYLE, TRANSACTION_SOURCE_URL @@ -634,20 +633,6 @@ def execute(self, sql, params=None): span_origin=DjangoIntegration.origin_db, ) as span: _set_db_data(span, self) - options = ( - sentry_sdk.get_client() - .options["_experiments"] - .get("attach_explain_plans") - ) - if options is not None: - attach_explain_plan_to_span( - span, - self.cursor.connection, - sql, - params, - self.mogrify, - options, - ) result = real_execute(self, sql, params) with capture_internal_exceptions(): diff --git a/sentry_sdk/integrations/sqlalchemy.py b/sentry_sdk/integrations/sqlalchemy.py index a968b7db9e..0a54108e75 100644 --- a/sentry_sdk/integrations/sqlalchemy.py +++ b/sentry_sdk/integrations/sqlalchemy.py @@ -1,6 +1,4 @@ -import sentry_sdk from sentry_sdk.consts import SPANSTATUS, SPANDATA -from sentry_sdk.db.explain_plan.sqlalchemy import attach_explain_plan_to_span from sentry_sdk.integrations import Integration, DidNotEnable from sentry_sdk.tracing_utils import add_query_source, record_sql_queries from sentry_sdk.utils import ( @@ -68,17 +66,6 @@ def _before_cursor_execute( if span is not None: _set_db_data(span, conn) - options = ( - sentry_sdk.get_client().options["_experiments"].get("attach_explain_plans") - ) - if options is not None: - attach_explain_plan_to_span( - span, - conn, - statement, - parameters, - options, - ) context._sentry_sql_span = span From 4f6ccc45af0e0cc0a09f3b38c76f02b49e469feb Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 13 Sep 2024 14:15:21 +0200 Subject: [PATCH 10/16] fixed message (#3536) --- sentry_sdk/tracing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 036e6619f6..7ce577b1d0 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -228,7 +228,7 @@ class Span: https://develop.sentry.dev/sdk/performance/span-operations/ :param description: A description of what operation is being performed within the span. - .. deprecated:: 2.X.X + .. deprecated:: 2.15.0 Please use the `name` parameter, instead. :param name: A string describing what operation is being performed within the span. :param hub: The hub to use for this span. From 49dd64d7db499da45746f7c947181f7bcc19d4a3 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Fri, 20 Sep 2024 08:13:30 +0100 Subject: [PATCH 11/16] tests: Fix cohere API change (#3549) --- sentry_sdk/integrations/cohere.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/cohere.py b/sentry_sdk/integrations/cohere.py index 388b86f1e0..4d6a4a244c 100644 --- a/sentry_sdk/integrations/cohere.py +++ b/sentry_sdk/integrations/cohere.py @@ -26,7 +26,6 @@ from cohere import ( ChatStreamEndEvent, NonStreamedChatResponse, - StreamedChatResponse_StreamEnd, ) if TYPE_CHECKING: @@ -34,6 +33,12 @@ except ImportError: raise DidNotEnable("Cohere not installed") +try: + # cohere 5.9.3+ + from cohere import StreamEndStreamedChatResponse +except ImportError: + from cohere import StreamedChatResponse_StreamEnd as StreamEndStreamedChatResponse + COLLECTED_CHAT_PARAMS = { "model": SPANDATA.AI_MODEL_ID, @@ -189,7 +194,7 @@ def new_iterator(): with capture_internal_exceptions(): for x in old_iterator: if isinstance(x, ChatStreamEndEvent) or isinstance( - x, StreamedChatResponse_StreamEnd + x, StreamEndStreamedChatResponse ): collect_chat_response_fields( span, From 64e2977b39c7e1b3b6fbad6e003f7800139e2913 Mon Sep 17 00:00:00 2001 From: joshuarli Date: Fri, 20 Sep 2024 00:22:40 -0700 Subject: [PATCH 12/16] ci: update actions/upload-artifact to v4 with merge (#3545) --- .github/workflows/ci.yml | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6e6415b65..7cd7847e42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,11 +70,14 @@ jobs: # This will also trigger "make dist" that creates the Python packages make aws-lambda-layer - name: Upload Python Packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: ${{ github.sha }} + name: artifact-build_lambda_layer path: | dist/* + if-no-files-found: 'error' + # since this artifact will be merged, compression is not necessary + compression-level: '0' docs: name: Build SDK API Doc @@ -91,7 +94,23 @@ jobs: make apidocs cd docs/_build && zip -r gh-pages ./ - - uses: actions/upload-artifact@v3.1.1 + - uses: actions/upload-artifact@v4 + with: + name: artifact-docs + path: | + docs/_build/gh-pages.zip + if-no-files-found: 'error' + # since this artifact will be merged, compression is not necessary + compression-level: '0' + + merge: + name: Create Release Artifact + runs-on: ubuntu-latest + needs: [build_lambda_layer, docs] + steps: + - uses: actions/upload-artifact/merge@v4 with: + # Craft expects release assets from github to be a single artifact named after the sha. name: ${{ github.sha }} - path: docs/_build/gh-pages.zip + pattern: artifact-* + delete-merged: true From ed614c0fa52ad457977e648f73bf8a2729c179ff Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 20 Sep 2024 14:14:30 +0200 Subject: [PATCH 13/16] fix: Don't use deprecated logger.warn (#3552) --- sentry_sdk/integrations/langchain.py | 2 +- tests/integrations/django/myapp/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index fefc4619db..9a784ddf19 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -443,7 +443,7 @@ def new_configure(*args, **kwargs): elif isinstance(existing_callbacks, BaseCallbackHandler): new_callbacks.append(existing_callbacks) else: - logger.warn("Unknown callback type: %s", existing_callbacks) + logger.debug("Unknown callback type: %s", existing_callbacks) already_added = False for callback in new_callbacks: diff --git a/tests/integrations/django/myapp/settings.py b/tests/integrations/django/myapp/settings.py index 0678762b6b..d70adf63ec 100644 --- a/tests/integrations/django/myapp/settings.py +++ b/tests/integrations/django/myapp/settings.py @@ -132,7 +132,7 @@ def middleware(request): except (ImportError, KeyError): from sentry_sdk.utils import logger - logger.warn("No psycopg2 found, testing with SQLite.") + logger.warning("No psycopg2 found, testing with SQLite.") # Password validation From 0ee7c5076828ec6e0ed484ccfcc4d0d28e81c5ad Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 23 Sep 2024 09:52:05 +0200 Subject: [PATCH 14/16] fix(django): Don't let RawPostDataException bubble up (#3553) --- sentry_sdk/integrations/_wsgi_common.py | 8 ++++++- tests/integrations/django/test_basic.py | 28 ++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/_wsgi_common.py b/sentry_sdk/integrations/_wsgi_common.py index 14a4c4aea4..c4f3f1c77e 100644 --- a/sentry_sdk/integrations/_wsgi_common.py +++ b/sentry_sdk/integrations/_wsgi_common.py @@ -152,7 +152,13 @@ def json(self): if not self.is_json(): return None - raw_data = self.raw_data() + try: + raw_data = self.raw_data() + except (RawPostDataException, ValueError): + # The body might have already been read, in which case this will + # fail + raw_data = None + if raw_data is None: return None diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py index 45c25595f3..f02f8ee217 100644 --- a/tests/integrations/django/test_basic.py +++ b/tests/integrations/django/test_basic.py @@ -3,6 +3,7 @@ import re import pytest from functools import partial +from unittest.mock import patch from werkzeug.test import Client @@ -10,6 +11,7 @@ from django.contrib.auth.models import User from django.core.management import execute_from_command_line from django.db.utils import OperationalError, ProgrammingError, DataError +from django.http.request import RawPostDataException try: from django.urls import reverse @@ -20,7 +22,11 @@ from sentry_sdk._compat import PY310 from sentry_sdk import capture_message, capture_exception from sentry_sdk.consts import SPANDATA -from sentry_sdk.integrations.django import DjangoIntegration, _set_db_data +from sentry_sdk.integrations.django import ( + DjangoIntegration, + DjangoRequestExtractor, + _set_db_data, +) from sentry_sdk.integrations.django.signals_handlers import _get_receiver_name from sentry_sdk.integrations.executing import ExecutingIntegration from sentry_sdk.tracing import Span @@ -740,6 +746,26 @@ def test_read_request(sentry_init, client, capture_events): assert "data" not in event["request"] +def test_request_body_already_read(sentry_init, client, capture_events): + sentry_init(integrations=[DjangoIntegration()]) + + events = capture_events() + + class MockExtractor(DjangoRequestExtractor): + def raw_data(self): + raise RawPostDataException + + with patch("sentry_sdk.integrations.django.DjangoRequestExtractor", MockExtractor): + client.post( + reverse("post_echo"), data=b'{"hey": 42}', content_type="application/json" + ) + + (event,) = events + + assert event["message"] == "hi" + assert "data" not in event["request"] + + def test_template_tracing_meta(sentry_init, client, capture_events): sentry_init(integrations=[DjangoIntegration()]) events = capture_events() From 25ab10cdbc556e949b37daf95c77711604bfbdf4 Mon Sep 17 00:00:00 2001 From: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:11:19 +0200 Subject: [PATCH 15/16] fix(aiohttp): Handle invalid responses (#3554) If the request handler returns an invalid response (e.g. `None`), our SDK triggers an error because we try to access the invalid response's `status` attribute. Wrap this with a `try` block to handle the `AttributeError` and ensure the SDK does not break the app. --- sentry_sdk/integrations/aiohttp.py | 12 +++++++++++- tests/integrations/aiohttp/test_aiohttp.py | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/aiohttp.py b/sentry_sdk/integrations/aiohttp.py index a447b67f38..6a738f3af0 100644 --- a/sentry_sdk/integrations/aiohttp.py +++ b/sentry_sdk/integrations/aiohttp.py @@ -139,7 +139,17 @@ async def sentry_app_handle(self, request, *args, **kwargs): # have no way to tell. Do not set span status. reraise(*_capture_exception()) - transaction.set_http_status(response.status) + try: + # A valid response handler will return a valid response with a status. But, if the handler + # returns an invalid response (e.g. None), the line below will raise an AttributeError. + # Even though this is likely invalid, we need to handle this case to ensure we don't break + # the application. + response_status = response.status + except AttributeError: + pass + else: + transaction.set_http_status(response_status) + return response Application._handle = sentry_app_handle diff --git a/tests/integrations/aiohttp/test_aiohttp.py b/tests/integrations/aiohttp/test_aiohttp.py index 43e3bec546..be372b6643 100644 --- a/tests/integrations/aiohttp/test_aiohttp.py +++ b/tests/integrations/aiohttp/test_aiohttp.py @@ -596,3 +596,24 @@ async def hello(request): (event,) = events assert event["contexts"]["trace"]["origin"] == "auto.http.aiohttp" assert event["spans"][0]["origin"] == "auto.http.aiohttp" + + +@pytest.mark.asyncio +@pytest.mark.parametrize("invalid_response", (None, "invalid")) +async def test_invalid_response( + sentry_init, aiohttp_client, capture_events, invalid_response +): + sentry_init(integrations=[AioHttpIntegration()]) + + async def handler(_): + return invalid_response + + app = web.Application() + app.router.add_get("/", handler) + + client = await aiohttp_client(app) + + # Invalid response should result on a ServerDisconnectedError in the client side, not an internal server error. + # Important to note that the ServerDisconnectedError indicates we have no error server-side. + with pytest.raises(ServerDisconnectedError): + await client.get("/") From 26b86a5e256a54ed83060863a350f46c8522645e Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Mon, 23 Sep 2024 09:21:34 +0100 Subject: [PATCH 16/16] fix: Fix breadcrumb timestamp casting and its tests (#3546) These tests were failing for me locally as the timestamps were without tzinfo and all were assumed UTC whereas my local timezone is BST at the moment. This patch fixes the tests along with faulty/incomplete breadcrumb timestamp parsing logic on py3.7 and py3.8. --------- Co-authored-by: Anton Pirker Co-authored-by: Ivana Kellyer --- sentry_sdk/scope.py | 5 +++-- sentry_sdk/utils.py | 22 ++++++++++++++++--- tests/test_basics.py | 39 +++++++++++++++++++++------------- tests/test_utils.py | 50 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 20 deletions(-) diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index adae8dc888..0c0482904e 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -968,7 +968,7 @@ def start_transaction( transaction=None, instrumenter=INSTRUMENTER.SENTRY, custom_sampling_context=None, - **kwargs + **kwargs, ): # type: (Optional[Transaction], str, Optional[SamplingContext], Unpack[TransactionKwargs]) -> Union[Transaction, NoOpSpan] """ @@ -1324,7 +1324,8 @@ def _apply_breadcrumbs_to_event(self, event, hint, options): crumb["timestamp"] = datetime_from_isoformat(crumb["timestamp"]) event["breadcrumbs"]["values"].sort(key=lambda crumb: crumb["timestamp"]) - except Exception: + except Exception as err: + logger.debug("Error when sorting breadcrumbs", exc_info=err) pass def _apply_user_to_event(self, event, hint, options): diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 38ab7e3618..44cb98bfed 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -239,13 +239,29 @@ def format_timestamp(value): return utctime.strftime("%Y-%m-%dT%H:%M:%S.%fZ") +ISO_TZ_SEPARATORS = frozenset(("+", "-")) + + def datetime_from_isoformat(value): # type: (str) -> datetime try: - return datetime.fromisoformat(value) - except AttributeError: + result = datetime.fromisoformat(value) + except (AttributeError, ValueError): # py 3.6 - return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") + timestamp_format = ( + "%Y-%m-%dT%H:%M:%S.%f" if "." in value else "%Y-%m-%dT%H:%M:%S" + ) + if value.endswith("Z"): + value = value[:-1] + "+0000" + + if value[-6] in ISO_TZ_SEPARATORS: + timestamp_format += "%z" + value = value[:-3] + value[-2:] + elif value[-5] in ISO_TZ_SEPARATORS: + timestamp_format += "%z" + + result = datetime.strptime(value, timestamp_format) + return result.astimezone(timezone.utc) def event_hint_with_exc_info(exc_info=None): diff --git a/tests/test_basics.py b/tests/test_basics.py index 6f77353c8a..74dfe1955a 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -8,6 +8,7 @@ import pytest from sentry_sdk.client import Client +from sentry_sdk.utils import datetime_from_isoformat from tests.conftest import patch_start_tracing_child import sentry_sdk @@ -397,11 +398,12 @@ def test_breadcrumbs(sentry_init, capture_events): def test_breadcrumb_ordering(sentry_init, capture_events): sentry_init() events = capture_events() + now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0) timestamps = [ - datetime.datetime.now() - datetime.timedelta(days=10), - datetime.datetime.now() - datetime.timedelta(days=8), - datetime.datetime.now() - datetime.timedelta(days=12), + now - datetime.timedelta(days=10), + now - datetime.timedelta(days=8), + now - datetime.timedelta(days=12), ] for timestamp in timestamps: @@ -417,10 +419,7 @@ def test_breadcrumb_ordering(sentry_init, capture_events): assert len(event["breadcrumbs"]["values"]) == len(timestamps) timestamps_from_event = [ - datetime.datetime.strptime( - x["timestamp"].replace("Z", ""), "%Y-%m-%dT%H:%M:%S.%f" - ) - for x in event["breadcrumbs"]["values"] + datetime_from_isoformat(x["timestamp"]) for x in event["breadcrumbs"]["values"] ] assert timestamps_from_event == sorted(timestamps) @@ -428,11 +427,24 @@ def test_breadcrumb_ordering(sentry_init, capture_events): def test_breadcrumb_ordering_different_types(sentry_init, capture_events): sentry_init() events = capture_events() + now = datetime.datetime.now(datetime.timezone.utc) timestamps = [ - datetime.datetime.now() - datetime.timedelta(days=10), - datetime.datetime.now() - datetime.timedelta(days=8), - datetime.datetime.now() - datetime.timedelta(days=12), + now - datetime.timedelta(days=10), + now - datetime.timedelta(days=8), + now.replace(microsecond=0) - datetime.timedelta(days=12), + now - datetime.timedelta(days=9), + now - datetime.timedelta(days=13), + now.replace(microsecond=0) - datetime.timedelta(days=11), + ] + + breadcrumb_timestamps = [ + timestamps[0], + timestamps[1].isoformat(), + datetime.datetime.strftime(timestamps[2], "%Y-%m-%dT%H:%M:%S") + "Z", + datetime.datetime.strftime(timestamps[3], "%Y-%m-%dT%H:%M:%S.%f") + "+00:00", + datetime.datetime.strftime(timestamps[4], "%Y-%m-%dT%H:%M:%S.%f") + "+0000", + datetime.datetime.strftime(timestamps[5], "%Y-%m-%dT%H:%M:%S.%f") + "-0000", ] for i, timestamp in enumerate(timestamps): @@ -440,7 +452,7 @@ def test_breadcrumb_ordering_different_types(sentry_init, capture_events): message="Authenticated at %s" % timestamp, category="auth", level="info", - timestamp=timestamp if i % 2 == 0 else timestamp.isoformat(), + timestamp=breadcrumb_timestamps[i], ) capture_exception(ValueError()) @@ -448,10 +460,7 @@ def test_breadcrumb_ordering_different_types(sentry_init, capture_events): assert len(event["breadcrumbs"]["values"]) == len(timestamps) timestamps_from_event = [ - datetime.datetime.strptime( - x["timestamp"].replace("Z", ""), "%Y-%m-%dT%H:%M:%S.%f" - ) - for x in event["breadcrumbs"]["values"] + datetime_from_isoformat(x["timestamp"]) for x in event["breadcrumbs"]["values"] ] assert timestamps_from_event == sorted(timestamps) diff --git a/tests/test_utils.py b/tests/test_utils.py index 4df343a357..c46cac7f9f 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -12,6 +12,7 @@ from sentry_sdk.utils import ( Components, Dsn, + datetime_from_isoformat, env_to_bool, format_timestamp, get_current_thread_meta, @@ -61,6 +62,55 @@ def _normalize_distribution_name(name): return re.sub(r"[-_.]+", "-", name).lower() +@pytest.mark.parametrize( + ("input_str", "expected_output"), + ( + ( + "2021-01-01T00:00:00.000000Z", + datetime(2021, 1, 1, tzinfo=timezone.utc), + ), # UTC time + ( + "2021-01-01T00:00:00.000000", + datetime(2021, 1, 1, tzinfo=datetime.now().astimezone().tzinfo), + ), # No TZ -- assume UTC + ( + "2021-01-01T00:00:00Z", + datetime(2021, 1, 1, tzinfo=timezone.utc), + ), # UTC - No milliseconds + ( + "2021-01-01T00:00:00.000000+00:00", + datetime(2021, 1, 1, tzinfo=timezone.utc), + ), + ( + "2021-01-01T00:00:00.000000-00:00", + datetime(2021, 1, 1, tzinfo=timezone.utc), + ), + ( + "2021-01-01T00:00:00.000000+0000", + datetime(2021, 1, 1, tzinfo=timezone.utc), + ), + ( + "2021-01-01T00:00:00.000000-0000", + datetime(2021, 1, 1, tzinfo=timezone.utc), + ), + ( + "2020-12-31T00:00:00.000000+02:00", + datetime(2020, 12, 31, tzinfo=timezone(timedelta(hours=2))), + ), # UTC+2 time + ( + "2020-12-31T00:00:00.000000-0200", + datetime(2020, 12, 31, tzinfo=timezone(timedelta(hours=-2))), + ), # UTC-2 time + ( + "2020-12-31T00:00:00-0200", + datetime(2020, 12, 31, tzinfo=timezone(timedelta(hours=-2))), + ), # UTC-2 time - no milliseconds + ), +) +def test_datetime_from_isoformat(input_str, expected_output): + assert datetime_from_isoformat(input_str) == expected_output, input_str + + @pytest.mark.parametrize( "env_var_value,strict,expected", [