Skip to content

Commit 3d06bca

Browse files
szokeasaurusrexcnschnantonpirkercolin-sentry
authored
Merge master into 2.0 branch (#2805)
* ref: Improve scrub_dict typing (#2768) This change improves the typing of the scrub_dict method. Previously, the scrub_dict method's type hints indicated that only dict[str, Any] was accepted as the parameter. However, the method is actually implemented to accept any object, since it checks the types of the parameters at runtime. Therefore, object is a more appropriate type hint for the parameter. #2753 depends on this change for mypy to pass * Propagate sentry-trace and baggage to huey tasks (#2792) This PR enables passing `sentry-trace` and `baggage` headers to background tasks using the Huey task queue. This allows easily correlating what happens inside a background task with whatever transaction (e.g. a user request in a Django application) queued the task in the first place. Periodic tasks do not get these headers, because otherwise each execution of the periodic task would be tied to the same parent trace (the long-running worker process). --- Co-authored-by: Anton Pirker <[email protected]> * OpenAI integration (#2791) * OpenAI integration * Fix linting errors * Fix CI * Fix lint * Fix more CI issues * Run tests on version pinned OpenAI too * Fix pydantic issue in test * Import type in TYPE_CHECKING gate * PR feedback fixes * Fix tiktoken test variant * PII gate the request and response * Rename set_data tags * Move doc location * Add "exclude prompts" flag as optional * Change prompts to be excluded by default * Set flag in tests * Fix tiktoken tox.ini extra dash * Change strip PII semantics * More test coverage for PII * notiktoken --------- Co-authored-by: Anton Pirker <[email protected]> * Add a method for normalizing data passed to set_data (#2800) * Discard open spans after 10 minutes (#2801) OTel spans that are handled in the Sentry span processor can never be finished/closed. This leads to a memory leak. This change makes sure that open spans will be removed from memory after 10 minutes to prevent memory usage from growing constantly. Fixes #2722 --------- Co-authored-by: Daniel Szoke <[email protected]> * ref: Event Type (#2753) Implements type hinting for Event via a TypedDict. This commit mainly adjusts type hints; however, there are also some minor code changes to make the code type-safe following the new changes. Some items in the Event could have their types expanded by being defined as TypedDicts themselves. These items have been indicated with TODO comments. Fixes GH-2357 * Fix mypy in `client.py` * Fix functools import * Fix CI config problem ... by running `python scripts/split-tox-gh-actions/split-tox-gh-actions.py` --------- Co-authored-by: Christian Schneider <[email protected]> Co-authored-by: Anton Pirker <[email protected]> Co-authored-by: colin-sentry <[email protected]>
1 parent 412ca67 commit 3d06bca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+932
-116
lines changed

.github/workflows/test-integrations-data-processing.yml

+9-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
strategy:
2626
fail-fast: false
2727
matrix:
28-
python-version: ["3.6","3.7","3.8","3.11","3.12"]
28+
python-version: ["3.6","3.7","3.8","3.9","3.11","3.12"]
2929
# python3.6 reached EOL and is no longer being supported on
3030
# new versions of hosted runners on Github Actions
3131
# ubuntu-20.04 is the last version that supported python3.6
@@ -58,6 +58,10 @@ jobs:
5858
run: |
5959
set -x # print commands that are executed
6060
./scripts/runtox.sh "py${{ matrix.python-version }}-huey-latest" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch
61+
- name: Test openai latest
62+
run: |
63+
set -x # print commands that are executed
64+
./scripts/runtox.sh "py${{ matrix.python-version }}-openai-latest" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch
6165
- name: Test rq latest
6266
run: |
6367
set -x # print commands that are executed
@@ -110,6 +114,10 @@ jobs:
110114
run: |
111115
set -x # print commands that are executed
112116
./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-huey" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch
117+
- name: Test openai pinned
118+
run: |
119+
set -x # print commands that are executed
120+
./scripts/runtox.sh --exclude-latest "py${{ matrix.python-version }}-openai" --cov=tests --cov=sentry_sdk --cov-report= --cov-branch
113121
- name: Test rq pinned
114122
run: |
115123
set -x # print commands that are executed

mypy.ini

+2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ ignore_missing_imports = True
6767
ignore_missing_imports = True
6868
[mypy-huey.*]
6969
ignore_missing_imports = True
70+
[mypy-openai.*]
71+
ignore_missing_imports = True
7072
[mypy-arq.*]
7173
ignore_missing_imports = True
7274
[mypy-grpc.*]

scripts/split-tox-gh-actions/split-tox-gh-actions.py

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"beam",
7171
"celery",
7272
"huey",
73+
"openai",
7374
"rq",
7475
],
7576
"Databases": [

sentry_sdk/_types.py

+62-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010

1111
if TYPE_CHECKING:
12+
from collections.abc import MutableMapping
13+
14+
from datetime import datetime
15+
1216
from types import TracebackType
1317
from typing import Any
1418
from typing import Callable
@@ -19,13 +23,69 @@
1923
from typing import Tuple
2024
from typing import Type
2125
from typing import Union
22-
from typing_extensions import Literal
26+
from typing_extensions import Literal, TypedDict
27+
28+
# "critical" is an alias of "fatal" recognized by Relay
29+
LogLevelStr = Literal["fatal", "critical", "error", "warning", "info", "debug"]
30+
31+
Event = TypedDict(
32+
"Event",
33+
{
34+
"breadcrumbs": dict[
35+
Literal["values"], list[dict[str, Any]]
36+
], # TODO: We can expand on this type
37+
"check_in_id": str,
38+
"contexts": dict[str, dict[str, object]],
39+
"dist": str,
40+
"duration": Optional[float],
41+
"environment": str,
42+
"errors": list[dict[str, Any]], # TODO: We can expand on this type
43+
"event_id": str,
44+
"exception": dict[
45+
Literal["values"], list[dict[str, Any]]
46+
], # TODO: We can expand on this type
47+
"extra": MutableMapping[str, object],
48+
"fingerprint": list[str],
49+
"level": LogLevelStr,
50+
"logentry": Mapping[str, object],
51+
"logger": str,
52+
"measurements": dict[str, object],
53+
"message": str,
54+
"modules": dict[str, str],
55+
"monitor_config": Mapping[str, object],
56+
"monitor_slug": Optional[str],
57+
"platform": Literal["python"],
58+
"profile": object, # Should be sentry_sdk.profiler.Profile, but we can't import that here due to circular imports
59+
"release": str,
60+
"request": dict[str, object],
61+
"sdk": Mapping[str, object],
62+
"server_name": str,
63+
"spans": list[dict[str, object]],
64+
"stacktrace": dict[
65+
str, object
66+
], # We access this key in the code, but I am unsure whether we ever set it
67+
"start_timestamp": datetime,
68+
"status": Optional[str],
69+
"tags": MutableMapping[
70+
str, str
71+
], # Tags must be less than 200 characters each
72+
"threads": dict[
73+
Literal["values"], list[dict[str, Any]]
74+
], # TODO: We can expand on this type
75+
"timestamp": Optional[datetime], # Must be set before sending the event
76+
"transaction": str,
77+
"transaction_info": Mapping[str, Any], # TODO: We can expand on this type
78+
"type": Literal["check_in", "transaction"],
79+
"user": dict[str, object],
80+
"_metrics_summary": dict[str, object],
81+
},
82+
total=False,
83+
)
2384

2485
ExcInfo = Tuple[
2586
Optional[Type[BaseException]], Optional[BaseException], Optional[TracebackType]
2687
]
2788

28-
Event = Dict[str, Any]
2989
Hint = Dict[str, Any]
3090

3191
Breadcrumb = Dict[str, Any]

sentry_sdk/api.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
BreadcrumbHint,
2828
ExcInfo,
2929
MeasurementUnit,
30+
LogLevelStr,
3031
)
3132
from sentry_sdk.scope import StartTransactionKwargs
3233
from sentry_sdk.tracing import Span
@@ -122,7 +123,7 @@ def capture_event(
122123
@scopemethod
123124
def capture_message(
124125
message, # type: str
125-
level=None, # type: Optional[str]
126+
level=None, # type: Optional[LogLevelStr]
126127
scope=None, # type: Optional[Any]
127128
**scope_kwargs, # type: Any
128129
):
@@ -257,7 +258,7 @@ def set_user(value):
257258

258259
@scopemethod
259260
def set_level(value):
260-
# type: (str) -> None
261+
# type: (LogLevelStr) -> None
261262
return Scope.get_isolation_scope().set_level(value)
262263

263264

sentry_sdk/client.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
try:
2+
from collections.abc import Mapping
3+
except ImportError:
4+
from collections import Mapping # type: ignore[attr-defined]
5+
16
import os
27
import uuid
38
import random
@@ -32,7 +37,7 @@
3237
from sentry_sdk.utils import ContextVar
3338
from sentry_sdk.sessions import SessionFlusher
3439
from sentry_sdk.envelope import Envelope
35-
from sentry_sdk.profiler import has_profiling_enabled, setup_profiler
40+
from sentry_sdk.profiler import has_profiling_enabled, Profile, setup_profiler
3641
from sentry_sdk.scrubber import EventScrubber
3742
from sentry_sdk.monitor import Monitor
3843
from sentry_sdk.spotlight import setup_spotlight
@@ -460,7 +465,7 @@ def _prepare_event(
460465

461466
for key in "release", "environment", "server_name", "dist":
462467
if event.get(key) is None and self.options[key] is not None:
463-
event[key] = str(self.options[key]).strip()
468+
event[key] = str(self.options[key]).strip() # type: ignore[literal-required]
464469
if event.get("sdk") is None:
465470
sdk_info = dict(SDK_INFO)
466471
sdk_info["integrations"] = sorted(self.integrations.keys())
@@ -634,15 +639,16 @@ def _update_session_from_event(
634639
errored = True
635640
for error in exceptions:
636641
mechanism = error.get("mechanism")
637-
if mechanism and mechanism.get("handled") is False:
642+
if isinstance(mechanism, Mapping) and mechanism.get("handled") is False:
638643
crashed = True
639644
break
640645

641646
user = event.get("user")
642647

643648
if session.user_agent is None:
644649
headers = (event.get("request") or {}).get("headers")
645-
for k, v in (headers or {}).items():
650+
headers_dict = headers if isinstance(headers, dict) else {}
651+
for k, v in headers_dict.items():
646652
if k.lower() == "user-agent":
647653
user_agent = v
648654
break
@@ -714,15 +720,15 @@ def capture_event(
714720
headers = {
715721
"event_id": event_opt["event_id"],
716722
"sent_at": format_timestamp(datetime.now(timezone.utc)),
717-
}
723+
} # type: dict[str, object]
718724

719725
if dynamic_sampling_context:
720726
headers["trace"] = dynamic_sampling_context
721727

722728
envelope = Envelope(headers=headers)
723729

724730
if is_transaction:
725-
if profile is not None:
731+
if isinstance(profile, Profile):
726732
envelope.add_profile(profile.to_json(event_opt, self.options))
727733
envelope.add_transaction(event_opt)
728734
elif is_checkin:

sentry_sdk/consts.py

+2
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ class OP:
229229
MIDDLEWARE_STARLITE = "middleware.starlite"
230230
MIDDLEWARE_STARLITE_RECEIVE = "middleware.starlite.receive"
231231
MIDDLEWARE_STARLITE_SEND = "middleware.starlite.send"
232+
OPENAI_CHAT_COMPLETIONS_CREATE = "ai.chat_completions.create.openai"
233+
OPENAI_EMBEDDINGS_CREATE = "ai.embeddings.create.openai"
232234
QUEUE_SUBMIT_ARQ = "queue.submit.arq"
233235
QUEUE_TASK_ARQ = "queue.task.arq"
234236
QUEUE_SUBMIT_CELERY = "queue.submit.celery"

sentry_sdk/crons/api.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
if TYPE_CHECKING:
88
from typing import Any, Dict, Optional
9+
from sentry_sdk._types import Event
910

1011

1112
def _create_check_in_event(
@@ -15,7 +16,7 @@ def _create_check_in_event(
1516
duration_s=None,
1617
monitor_config=None,
1718
):
18-
# type: (Optional[str], Optional[str], Optional[str], Optional[float], Optional[Dict[str, Any]]) -> Dict[str, Any]
19+
# type: (Optional[str], Optional[str], Optional[str], Optional[float], Optional[Dict[str, Any]]) -> Event
1920
options = Hub.current.client.options if Hub.current.client else {}
2021
check_in_id = check_in_id or uuid.uuid4().hex # type: str
2122

@@ -27,7 +28,7 @@ def _create_check_in_event(
2728
"duration": duration_s,
2829
"environment": options.get("environment", None),
2930
"release": options.get("release", None),
30-
}
31+
} # type: Event
3132

3233
if monitor_config:
3334
check_in["monitor_config"] = monitor_config

sentry_sdk/hub.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
Breadcrumb,
4242
BreadcrumbHint,
4343
ExcInfo,
44+
LogLevelStr,
4445
)
4546
from sentry_sdk.consts import ClientConstructor
4647
from sentry_sdk.scope import StartTransactionKwargs
@@ -347,7 +348,7 @@ def capture_event(self, event, hint=None, scope=None, **scope_kwargs):
347348
return last_event_id
348349

349350
def capture_message(self, message, level=None, scope=None, **scope_kwargs):
350-
# type: (str, Optional[str], Optional[Scope], Any) -> Optional[str]
351+
# type: (str, Optional[LogLevelStr], Optional[Scope], Any) -> Optional[str]
351352
"""
352353
.. deprecated:: 2.0.0
353354
This function is deprecated and will be removed in a future release.

sentry_sdk/integrations/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def iter_default_integrations(with_auto_enabling_integrations):
8686
"sentry_sdk.integrations.httpx.HttpxIntegration",
8787
"sentry_sdk.integrations.huey.HueyIntegration",
8888
"sentry_sdk.integrations.loguru.LoguruIntegration",
89+
"sentry_sdk.integrations.openai.OpenAIIntegration",
8990
"sentry_sdk.integrations.pymongo.PyMongoIntegration",
9091
"sentry_sdk.integrations.pyramid.PyramidIntegration",
9192
"sentry_sdk.integrations.quart.QuartIntegration",

sentry_sdk/integrations/_wsgi_common.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from typing import Mapping
2020
from typing import Optional
2121
from typing import Union
22+
from sentry_sdk._types import Event
2223

2324

2425
SENSITIVE_ENV_KEYS = (
@@ -65,7 +66,7 @@ def __init__(self, request):
6566
self.request = request
6667

6768
def extract_into_event(self, event):
68-
# type: (Dict[str, Any]) -> None
69+
# type: (Event) -> None
6970
client = Hub.current.client
7071
if client is None:
7172
return

sentry_sdk/integrations/aiohttp.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,12 @@
4949
from aiohttp import TraceRequestStartParams, TraceRequestEndParams
5050
from types import SimpleNamespace
5151
from typing import Any
52-
from typing import Dict
5352
from typing import Optional
5453
from typing import Tuple
5554
from typing import Union
5655

5756
from sentry_sdk.utils import ExcInfo
58-
from sentry_sdk._types import EventProcessor
57+
from sentry_sdk._types import Event, EventProcessor
5958

6059

6160
TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern")
@@ -256,10 +255,10 @@ async def on_request_end(session, trace_config_ctx, params):
256255
def _make_request_processor(weak_request):
257256
# type: (weakref.ReferenceType[Request]) -> EventProcessor
258257
def aiohttp_processor(
259-
event, # type: Dict[str, Any]
260-
hint, # type: Dict[str, Tuple[type, BaseException, Any]]
258+
event, # type: Event
259+
hint, # type: dict[str, Tuple[type, BaseException, Any]]
261260
):
262-
# type: (...) -> Dict[str, Any]
261+
# type: (...) -> Event
263262
request = weak_request()
264263
if request is None:
265264
return event

sentry_sdk/integrations/ariadne.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from typing import Any, Dict, List, Optional
2424
from ariadne.types import GraphQLError, GraphQLResult, GraphQLSchema, QueryParser # type: ignore
2525
from graphql.language.ast import DocumentNode # type: ignore
26-
from sentry_sdk._types import EventProcessor
26+
from sentry_sdk._types import Event, EventProcessor
2727

2828

2929
class AriadneIntegration(Integration):
@@ -131,7 +131,7 @@ def _make_request_event_processor(data):
131131
"""Add request data and api_target to events."""
132132

133133
def inner(event, hint):
134-
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
134+
# type: (Event, dict[str, Any]) -> Event
135135
if not isinstance(data, dict):
136136
return event
137137

@@ -163,7 +163,7 @@ def _make_response_event_processor(response):
163163
"""Add response data to the event's response context."""
164164

165165
def inner(event, hint):
166-
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
166+
# type: (Event, dict[str, Any]) -> Event
167167
with capture_internal_exceptions():
168168
if _should_send_default_pii() and response.get("errors"):
169169
contexts = event.setdefault("contexts", {})

sentry_sdk/integrations/bottle.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def _make_request_event_processor(app, request, integration):
198198
# type: (Bottle, LocalRequest, BottleIntegration) -> EventProcessor
199199

200200
def event_processor(event, hint):
201-
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
201+
# type: (Event, dict[str, Any]) -> Event
202202
_set_transaction_name_and_source(event, integration.transaction_style, request)
203203

204204
with capture_internal_exceptions():

sentry_sdk/integrations/django/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ def sentry_patched_get_response(self, request):
467467
def _make_wsgi_request_event_processor(weak_request, integration):
468468
# type: (Callable[[], WSGIRequest], DjangoIntegration) -> EventProcessor
469469
def wsgi_request_event_processor(event, hint):
470-
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
470+
# type: (Event, dict[str, Any]) -> Event
471471
# if the request is gone we are fine not logging the data from
472472
# it. This might happen if the processor is pushed away to
473473
# another thread.
@@ -565,7 +565,7 @@ def parsed_body(self):
565565

566566

567567
def _set_user_info(request, event):
568-
# type: (WSGIRequest, Dict[str, Any]) -> None
568+
# type: (WSGIRequest, Event) -> None
569569
user_info = event.setdefault("user", {})
570570

571571
user = getattr(request, "user", None)

sentry_sdk/integrations/django/asgi.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@
2727
from django.core.handlers.asgi import ASGIRequest
2828
from django.http.response import HttpResponse
2929

30-
from sentry_sdk._types import EventProcessor
30+
from sentry_sdk._types import Event, EventProcessor
3131

3232

3333
def _make_asgi_request_event_processor(request):
3434
# type: (ASGIRequest) -> EventProcessor
3535
def asgi_request_event_processor(event, hint):
36-
# type: (dict[str, Any], dict[str, Any]) -> dict[str, Any]
36+
# type: (Event, dict[str, Any]) -> Event
3737
# if the request is gone we are fine not logging the data from
3838
# it. This might happen if the processor is pushed away to
3939
# another thread.

0 commit comments

Comments
 (0)