Skip to content

Commit 5b3376d

Browse files
committed
Merge branch 'ivana/potel/move-span-processor-out-of-integration' into ivana/potel/move-propagator
2 parents 38b051e + b10193d commit 5b3376d

File tree

11 files changed

+144
-53
lines changed

11 files changed

+144
-53
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
* @getsentry/team-web-sdk-backend
1+
* @getsentry/owners-python-sdk

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
steps:
2121
- name: Get auth token
2222
id: token
23-
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0
23+
uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2
2424
with:
2525
app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }}
2626
private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }}

scripts/populate_tox/populate_tox.py

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import time
1010
from bisect import bisect_left
1111
from collections import defaultdict
12-
from datetime import datetime, timezone
12+
from datetime import datetime, timedelta, timezone # noqa: F401
1313
from importlib.metadata import metadata
1414
from packaging.specifiers import SpecifierSet
1515
from packaging.version import Version
@@ -29,13 +29,19 @@
2929
from split_tox_gh_actions.split_tox_gh_actions import GROUPS
3030

3131

32+
# Set CUTOFF this to a datetime to ignore packages older than CUTOFF
33+
CUTOFF = None
34+
# CUTOFF = datetime.now(tz=timezone.utc) - timedelta(days=365 * 5)
35+
3236
TOX_FILE = Path(__file__).resolve().parent.parent.parent / "tox.ini"
3337
ENV = Environment(
3438
loader=FileSystemLoader(Path(__file__).resolve().parent),
3539
trim_blocks=True,
3640
lstrip_blocks=True,
3741
)
3842

43+
PYPI_COOLDOWN = 0.15 # seconds to wait between requests to PyPI
44+
3945
PYPI_PROJECT_URL = "https://pypi.python.org/pypi/{project}/json"
4046
PYPI_VERSION_URL = "https://pypi.python.org/pypi/{project}/{version}/json"
4147
CLASSIFIER_PREFIX = "Programming Language :: Python :: "
@@ -88,27 +94,34 @@
8894
}
8995

9096

91-
@functools.cache
92-
def fetch_package(package: str) -> dict:
93-
"""Fetch package metadata from PyPI."""
94-
url = PYPI_PROJECT_URL.format(project=package)
95-
pypi_data = requests.get(url)
97+
def fetch_url(url: str) -> Optional[dict]:
98+
for attempt in range(3):
99+
pypi_data = requests.get(url)
96100

97-
if pypi_data.status_code != 200:
98-
print(f"{package} not found")
101+
if pypi_data.status_code == 200:
102+
return pypi_data.json()
99103

100-
return pypi_data.json()
104+
backoff = PYPI_COOLDOWN * 2**attempt
105+
print(
106+
f"{url} returned an error: {pypi_data.status_code}. Attempt {attempt + 1}/3. Waiting {backoff}s"
107+
)
108+
time.sleep(backoff)
109+
110+
return None
101111

102112

103113
@functools.cache
104-
def fetch_release(package: str, version: Version) -> dict:
105-
url = PYPI_VERSION_URL.format(project=package, version=version)
106-
pypi_data = requests.get(url)
114+
def fetch_package(package: str) -> Optional[dict]:
115+
"""Fetch package metadata from PyPI."""
116+
url = PYPI_PROJECT_URL.format(project=package)
117+
return fetch_url(url)
107118

108-
if pypi_data.status_code != 200:
109-
print(f"{package} not found")
110119

111-
return pypi_data.json()
120+
@functools.cache
121+
def fetch_release(package: str, version: Version) -> Optional[dict]:
122+
"""Fetch release metadata from PyPI."""
123+
url = PYPI_VERSION_URL.format(project=package, version=version)
124+
return fetch_url(url)
112125

113126

114127
def _prefilter_releases(
@@ -153,9 +166,13 @@ def _prefilter_releases(
153166
if meta["yanked"]:
154167
continue
155168

156-
if older_than is not None:
157-
if datetime.fromisoformat(meta["upload_time_iso_8601"]) > older_than:
158-
continue
169+
uploaded = datetime.fromisoformat(meta["upload_time_iso_8601"])
170+
171+
if older_than is not None and uploaded > older_than:
172+
continue
173+
174+
if CUTOFF is not None and uploaded < CUTOFF:
175+
continue
159176

160177
version = Version(release)
161178

@@ -229,8 +246,14 @@ def get_supported_releases(
229246
expected_python_versions = SpecifierSet(f">={MIN_PYTHON_VERSION}")
230247

231248
def _supports_lowest(release: Version) -> bool:
232-
time.sleep(0.1) # don't DoS PYPI
233-
py_versions = determine_python_versions(fetch_release(package, release))
249+
time.sleep(PYPI_COOLDOWN) # don't DoS PYPI
250+
251+
pypi_data = fetch_release(package, release)
252+
if pypi_data is None:
253+
print("Failed to fetch necessary data from PyPI. Aborting.")
254+
sys.exit(1)
255+
256+
py_versions = determine_python_versions(pypi_data)
234257
target_python_versions = TEST_SUITE_CONFIG[integration].get("python")
235258
if target_python_versions:
236259
target_python_versions = SpecifierSet(target_python_versions)
@@ -499,7 +522,11 @@ def _add_python_versions_to_release(
499522
integration: str, package: str, release: Version
500523
) -> None:
501524
release_pypi_data = fetch_release(package, release)
502-
time.sleep(0.1) # give PYPI some breathing room
525+
if release_pypi_data is None:
526+
print("Failed to fetch necessary data from PyPI. Aborting.")
527+
sys.exit(1)
528+
529+
time.sleep(PYPI_COOLDOWN) # give PYPI some breathing room
503530

504531
target_python_versions = TEST_SUITE_CONFIG[integration].get("python")
505532
if target_python_versions:
@@ -592,6 +619,9 @@ def main(fail_on_changes: bool = False) -> None:
592619

593620
# Fetch data for the main package
594621
pypi_data = fetch_package(package)
622+
if pypi_data is None:
623+
print("Failed to fetch necessary data from PyPI. Aborting.")
624+
sys.exit(1)
595625

596626
# Get the list of all supported releases
597627

sentry_sdk/client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from sentry_sdk.tracing import trace
2626
from sentry_sdk.transport import BaseHttpTransport, make_transport
2727
from sentry_sdk.consts import (
28+
SPANDATA,
2829
DEFAULT_MAX_VALUE_LENGTH,
2930
DEFAULT_OPTIONS,
3031
VERSION,
@@ -837,6 +838,13 @@ def _capture_experimental_log(self, current_scope, log):
837838
return
838839
isolation_scope = current_scope.get_isolation_scope()
839840

841+
log["attributes"]["sentry.sdk.name"] = SDK_INFO["name"]
842+
log["attributes"]["sentry.sdk.version"] = SDK_INFO["version"]
843+
844+
server_name = self.options.get("server_name")
845+
if server_name is not None and SPANDATA.SERVER_ADDRESS not in log["attributes"]:
846+
log["attributes"][SPANDATA.SERVER_ADDRESS] = server_name
847+
840848
environment = self.options.get("environment")
841849
if environment is not None and "sentry.environment" not in log["attributes"]:
842850
log["attributes"]["sentry.environment"] = environment

sentry_sdk/integrations/django/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def __init__(
115115
transaction_style="url", # type: str
116116
middleware_spans=True, # type: bool
117117
signals_spans=True, # type: bool
118-
cache_spans=False, # type: bool
118+
cache_spans=True, # type: bool
119119
signals_denylist=None, # type: Optional[list[signals.Signal]]
120120
http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: tuple[str, ...]
121121
):

sentry_sdk/integrations/django/caching.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -134,22 +134,10 @@ def _get_address_port(settings):
134134
return address, int(port) if port is not None else None
135135

136136

137-
def should_enable_cache_spans():
138-
# type: () -> bool
139-
from sentry_sdk.integrations.django import DjangoIntegration
140-
141-
client = sentry_sdk.get_client()
142-
integration = client.get_integration(DjangoIntegration)
143-
from django.conf import settings
144-
145-
return integration is not None and (
146-
(client.spotlight is not None and settings.DEBUG is True)
147-
or integration.cache_spans is True
148-
)
149-
150-
151137
def patch_caching():
152138
# type: () -> None
139+
from sentry_sdk.integrations.django import DjangoIntegration
140+
153141
if not hasattr(CacheHandler, "_sentry_patched"):
154142
if DJANGO_VERSION < (3, 2):
155143
original_get_item = CacheHandler.__getitem__
@@ -159,7 +147,8 @@ def sentry_get_item(self, alias):
159147
# type: (CacheHandler, str) -> Any
160148
cache = original_get_item(self, alias)
161149

162-
if should_enable_cache_spans():
150+
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
151+
if integration is not None and integration.cache_spans:
163152
from django.conf import settings
164153

165154
address, port = _get_address_port(
@@ -181,7 +170,8 @@ def sentry_create_connection(self, alias):
181170
# type: (CacheHandler, str) -> Any
182171
cache = original_create_connection(self, alias)
183172

184-
if should_enable_cache_spans():
173+
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
174+
if integration is not None and integration.cache_spans:
185175
address, port = _get_address_port(self.settings[alias or "default"])
186176

187177
_patch_cache(cache, address, port)

sentry_sdk/integrations/logging.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,9 @@ def _capture_log_from_record(client, record):
363363
# type: (BaseClient, LogRecord) -> None
364364
scope = sentry_sdk.get_current_scope()
365365
otel_severity_number, otel_severity_text = _python_level_to_otel(record.levelno)
366-
attrs = {} # type: dict[str, str | bool | float | int]
366+
attrs = {
367+
"sentry.origin": "auto.logger.log",
368+
} # type: dict[str, str | bool | float | int]
367369
if isinstance(record.msg, str):
368370
attrs["sentry.message.template"] = record.msg
369371
if record.args is not None:

sentry_sdk/transport.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ def _parse_rate_limits(header, now=None):
166166
class BaseHttpTransport(Transport):
167167
"""The base HTTP transport."""
168168

169+
TIMEOUT = 30 # seconds
170+
169171
def __init__(self, options):
170172
# type: (Self, Dict[str, Any]) -> None
171173
from sentry_sdk.consts import VERSION
@@ -558,6 +560,7 @@ def _get_pool_options(self):
558560
options = {
559561
"num_pools": 2 if num_pools is None else int(num_pools),
560562
"cert_reqs": "CERT_REQUIRED",
563+
"timeout": urllib3.Timeout(total=self.TIMEOUT),
561564
}
562565

563566
socket_options = None # type: Optional[List[Tuple[int, int, int | bytes]]]
@@ -673,6 +676,8 @@ def __init__(self, options):
673676
class Http2Transport(BaseHttpTransport): # type: ignore
674677
"""The HTTP2 transport based on httpcore."""
675678

679+
TIMEOUT = 15
680+
676681
if TYPE_CHECKING:
677682
_pool: Union[
678683
httpcore.SOCKSProxy, httpcore.HTTPProxy, httpcore.ConnectionPool
@@ -702,6 +707,14 @@ def _request(
702707
self._auth.get_api_url(endpoint_type),
703708
content=body,
704709
headers=headers, # type: ignore
710+
extensions={
711+
"timeout": {
712+
"pool": self.TIMEOUT,
713+
"connect": self.TIMEOUT,
714+
"write": self.TIMEOUT,
715+
"read": self.TIMEOUT,
716+
}
717+
},
705718
)
706719
return response
707720

tests/test_logs.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from sentry_sdk.envelope import Envelope
1212
from sentry_sdk.integrations.logging import LoggingIntegration
1313
from sentry_sdk.types import Log
14+
from sentry_sdk.consts import SPANDATA, VERSION
1415

1516
minimum_python_37 = pytest.mark.skipif(
1617
sys.version_info < (3, 7), reason="Asyncio tests need Python >= 3.7"
@@ -161,7 +162,7 @@ def test_logs_attributes(sentry_init, capture_envelopes):
161162
"""
162163
Passing arbitrary attributes to log messages.
163164
"""
164-
sentry_init(_experiments={"enable_logs": True})
165+
sentry_init(_experiments={"enable_logs": True}, server_name="test-server")
165166
envelopes = capture_envelopes()
166167

167168
attrs = {
@@ -184,6 +185,9 @@ def test_logs_attributes(sentry_init, capture_envelopes):
184185
assert logs[0]["attributes"]["sentry.environment"] == "production"
185186
assert "sentry.release" in logs[0]["attributes"]
186187
assert logs[0]["attributes"]["sentry.message.parameters.my_var"] == "some value"
188+
assert logs[0]["attributes"][SPANDATA.SERVER_ADDRESS] == "test-server"
189+
assert logs[0]["attributes"]["sentry.sdk.name"] == "sentry.python"
190+
assert logs[0]["attributes"]["sentry.sdk.version"] == VERSION
187191

188192

189193
@minimum_python_37
@@ -283,6 +287,7 @@ def test_logger_integration_warning(sentry_init, capture_envelopes):
283287
assert attrs["sentry.environment"] == "production"
284288
assert attrs["sentry.message.parameters.0"] == "1"
285289
assert attrs["sentry.message.parameters.1"] == "2"
290+
assert attrs["sentry.origin"] == "auto.logger.log"
286291
assert logs[0]["severity_number"] == 13
287292
assert logs[0]["severity_text"] == "warn"
288293

tests/test_transport.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@
1414
from pytest_localserver.http import WSGIServer
1515
from werkzeug.wrappers import Request, Response
1616

17+
try:
18+
import httpcore
19+
except (ImportError, ModuleNotFoundError):
20+
httpcore = None
21+
22+
try:
23+
import gevent
24+
except ImportError:
25+
gevent = None
26+
1727
import sentry_sdk
1828
from sentry_sdk import (
1929
Client,
@@ -260,6 +270,37 @@ def test_keep_alive_on_by_default(make_client):
260270
assert "socket_options" not in options
261271

262272

273+
def test_default_timeout(make_client):
274+
client = make_client()
275+
276+
options = client.transport._get_pool_options()
277+
assert "timeout" in options
278+
assert options["timeout"].total == client.transport.TIMEOUT
279+
280+
281+
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
282+
def test_default_timeout_http2(make_client):
283+
client = make_client(_experiments={"transport_http2": True})
284+
285+
with mock.patch(
286+
"sentry_sdk.transport.httpcore.ConnectionPool.request",
287+
return_value=httpcore.Response(200),
288+
) as request_mock:
289+
sentry_sdk.get_global_scope().set_client(client)
290+
capture_message("hi")
291+
client.flush()
292+
293+
request_mock.assert_called_once()
294+
assert request_mock.call_args.kwargs["extensions"] == {
295+
"timeout": {
296+
"pool": client.transport.TIMEOUT,
297+
"connect": client.transport.TIMEOUT,
298+
"write": client.transport.TIMEOUT,
299+
"read": client.transport.TIMEOUT,
300+
}
301+
}
302+
303+
263304
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
264305
def test_http2_with_https_dsn(make_client):
265306
client = make_client(_experiments={"transport_http2": True})

0 commit comments

Comments
 (0)