Skip to content

Commit c1c318d

Browse files
authored
Merge branch 'master' into fix-litestar-middleware-failed-request-status-codes
2 parents 53baddd + adcfa0f commit c1c318d

File tree

26 files changed

+803
-481
lines changed

26 files changed

+803
-481
lines changed

.github/workflows/test-integrations-web-1.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
strategy:
3030
fail-fast: false
3131
matrix:
32-
python-version: ["3.8","3.10","3.12","3.13"]
32+
python-version: ["3.8","3.12","3.13"]
3333
# python3.6 reached EOL and is no longer being supported on
3434
# new versions of hosted runners on Github Actions
3535
# ubuntu-20.04 is the last version that supported python3.6

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## 2.25.1
4+
5+
### Various fixes & improvements
6+
7+
- fix(logs): Add a class which batches groups of logs together. (#4229) by @colin-sentry
8+
- fix(logs): Use repr instead of json for message and arguments (#4227) by @colin-sentry
9+
- fix(logs): Debug output from Sentry logs should always be `debug` level. (#4224) by @antonpirker
10+
- fix(ai): Do not consume anthropic streaming stop (#4232) by @colin-sentry
11+
- fix(spotlight): Do not spam sentry_sdk.warnings logger w/ Spotlight (#4219) by @BYK
12+
- fix(docs): fixed code snippet (#4218) by @antonpirker
13+
- build(deps): bump actions/create-github-app-token from 1.11.7 to 1.12.0 (#4214) by @dependabot
14+
315
## 2.25.0
416

517
### Various fixes & improvements

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
copyright = "2019-{}, Sentry Team and Contributors".format(datetime.now().year)
3232
author = "Sentry Team and Contributors"
3333

34-
release = "2.25.0"
34+
release = "2.25.1"
3535
version = ".".join(release.split(".")[:2]) # The short X.Y version.
3636

3737

scripts/populate_tox/config.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@
2929
"clickhouse_driver": {
3030
"package": "clickhouse-driver",
3131
},
32+
"django": {
33+
"package": "django",
34+
"deps": {
35+
"*": [
36+
"psycopg2-binary",
37+
"djangorestframework",
38+
"pytest-django",
39+
"Werkzeug",
40+
],
41+
">=3.0": ["pytest-asyncio"],
42+
">=2.2,<3.1": ["six"],
43+
"<3.3": [
44+
"djangorestframework>=3.0,<4.0",
45+
"Werkzeug<2.1.0",
46+
],
47+
"<3.1": ["pytest-django<4.0"],
48+
">=2.0": ["channels[daphne]"],
49+
},
50+
},
3251
"dramatiq": {
3352
"package": "dramatiq",
3453
},

scripts/populate_tox/populate_tox.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@
6969
"boto3",
7070
"chalice",
7171
"cohere",
72-
"django",
7372
"fastapi",
7473
"gcp",
7574
"httpx",

scripts/populate_tox/tox.jinja

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,6 @@ envlist =
8080
{py3.9,py3.11,py3.12}-cohere-v5
8181
{py3.9,py3.11,py3.12}-cohere-latest
8282

83-
# Django
84-
# - Django 1.x
85-
{py3.6,py3.7}-django-v{1.11}
86-
# - Django 2.x
87-
{py3.6,py3.7}-django-v{2.0}
88-
{py3.6,py3.9}-django-v{2.2}
89-
# - Django 3.x
90-
{py3.6,py3.9}-django-v{3.0}
91-
{py3.6,py3.9,py3.11}-django-v{3.2}
92-
# - Django 4.x
93-
{py3.8,py3.11,py3.12}-django-v{4.0,4.1,4.2}
94-
# - Django 5.x
95-
{py3.10,py3.11,py3.12}-django-v{5.0,5.1}
96-
{py3.10,py3.12,py3.13}-django-latest
97-
9883
# FastAPI
9984
{py3.7,py3.10}-fastapi-v{0.79}
10085
{py3.8,py3.12,py3.13}-fastapi-latest
@@ -267,35 +252,6 @@ deps =
267252
cohere-v5: cohere~=5.3.3
268253
cohere-latest: cohere
269254
270-
# Django
271-
django: psycopg2-binary
272-
django-v{1.11,2.0,2.1,2.2,3.0,3.1,3.2}: djangorestframework>=3.0.0,<4.0.0
273-
django-v{2.0,2.2,3.0,3.2,4.0,4.1,4.2,5.0,5.1}: channels[daphne]
274-
django-v{2.2,3.0}: six
275-
django-v{1.11,2.0,2.2,3.0,3.2}: Werkzeug<2.1.0
276-
django-v{1.11,2.0,2.2,3.0}: pytest-django<4.0
277-
django-v{3.2,4.0,4.1,4.2,5.0,5.1}: pytest-django
278-
django-v{4.0,4.1,4.2,5.0,5.1}: djangorestframework
279-
django-v{4.0,4.1,4.2,5.0,5.1}: pytest-asyncio
280-
django-v{4.0,4.1,4.2,5.0,5.1}: Werkzeug
281-
django-latest: djangorestframework
282-
django-latest: pytest-asyncio
283-
django-latest: pytest-django
284-
django-latest: Werkzeug
285-
django-latest: channels[daphne]
286-
287-
django-v1.11: Django~=1.11.0
288-
django-v2.0: Django~=2.0.0
289-
django-v2.2: Django~=2.2.0
290-
django-v3.0: Django~=3.0.0
291-
django-v3.2: Django~=3.2.0
292-
django-v4.0: Django~=4.0.0
293-
django-v4.1: Django~=4.1.0
294-
django-v4.2: Django~=4.2.0
295-
django-v5.0: Django~=5.0.0
296-
django-v5.1: Django==5.1rc1
297-
django-latest: Django
298-
299255
# FastAPI
300256
fastapi: httpx
301257
# (this is a dependency of httpx)

sentry_sdk/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"start_transaction",
4646
"trace",
4747
"monitor",
48-
"_experimental_logger",
48+
"logger",
4949
]
5050

5151
# Initialize the debug support after everything is loaded

sentry_sdk/_log_batcher.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import os
2+
import random
3+
import threading
4+
from datetime import datetime, timezone
5+
from typing import Optional, List, Callable, TYPE_CHECKING, Any
6+
7+
from sentry_sdk.utils import format_timestamp, safe_repr
8+
from sentry_sdk.envelope import Envelope
9+
10+
if TYPE_CHECKING:
11+
from sentry_sdk._types import Log
12+
13+
14+
class LogBatcher:
15+
MAX_LOGS_BEFORE_FLUSH = 100
16+
FLUSH_WAIT_TIME = 5.0
17+
18+
def __init__(
19+
self,
20+
capture_func, # type: Callable[[Envelope], None]
21+
):
22+
# type: (...) -> None
23+
self._log_buffer = [] # type: List[Log]
24+
self._capture_func = capture_func
25+
self._running = True
26+
self._lock = threading.Lock()
27+
28+
self._flush_event = threading.Event() # type: threading.Event
29+
30+
self._flusher = None # type: Optional[threading.Thread]
31+
self._flusher_pid = None # type: Optional[int]
32+
33+
def _ensure_thread(self):
34+
# type: (...) -> bool
35+
"""For forking processes we might need to restart this thread.
36+
This ensures that our process actually has that thread running.
37+
"""
38+
if not self._running:
39+
return False
40+
41+
pid = os.getpid()
42+
if self._flusher_pid == pid:
43+
return True
44+
45+
with self._lock:
46+
# Recheck to make sure another thread didn't get here and start the
47+
# the flusher in the meantime
48+
if self._flusher_pid == pid:
49+
return True
50+
51+
self._flusher_pid = pid
52+
53+
self._flusher = threading.Thread(target=self._flush_loop)
54+
self._flusher.daemon = True
55+
56+
try:
57+
self._flusher.start()
58+
except RuntimeError:
59+
# Unfortunately at this point the interpreter is in a state that no
60+
# longer allows us to spawn a thread and we have to bail.
61+
self._running = False
62+
return False
63+
64+
return True
65+
66+
def _flush_loop(self):
67+
# type: (...) -> None
68+
while self._running:
69+
self._flush_event.wait(self.FLUSH_WAIT_TIME + random.random())
70+
self._flush_event.clear()
71+
self._flush()
72+
73+
def add(
74+
self,
75+
log, # type: Log
76+
):
77+
# type: (...) -> None
78+
if not self._ensure_thread() or self._flusher is None:
79+
return None
80+
81+
with self._lock:
82+
self._log_buffer.append(log)
83+
if len(self._log_buffer) >= self.MAX_LOGS_BEFORE_FLUSH:
84+
self._flush_event.set()
85+
86+
def kill(self):
87+
# type: (...) -> None
88+
if self._flusher is None:
89+
return
90+
91+
self._running = False
92+
self._flush_event.set()
93+
self._flusher = None
94+
95+
def flush(self):
96+
# type: (...) -> None
97+
self._flush()
98+
99+
@staticmethod
100+
def _log_to_otel(log):
101+
# type: (Log) -> Any
102+
def format_attribute(key, val):
103+
# type: (str, int | float | str | bool) -> Any
104+
if isinstance(val, bool):
105+
return {"key": key, "value": {"boolValue": val}}
106+
if isinstance(val, int):
107+
return {"key": key, "value": {"intValue": str(val)}}
108+
if isinstance(val, float):
109+
return {"key": key, "value": {"doubleValue": val}}
110+
if isinstance(val, str):
111+
return {"key": key, "value": {"stringValue": val}}
112+
return {"key": key, "value": {"stringValue": safe_repr(val)}}
113+
114+
otel_log = {
115+
"severityText": log["severity_text"],
116+
"severityNumber": log["severity_number"],
117+
"body": {"stringValue": log["body"]},
118+
"timeUnixNano": str(log["time_unix_nano"]),
119+
"attributes": [
120+
format_attribute(k, v) for (k, v) in log["attributes"].items()
121+
],
122+
}
123+
124+
if "trace_id" in log:
125+
otel_log["traceId"] = log["trace_id"]
126+
127+
return otel_log
128+
129+
def _flush(self):
130+
# type: (...) -> Optional[Envelope]
131+
132+
envelope = Envelope(
133+
headers={"sent_at": format_timestamp(datetime.now(timezone.utc))}
134+
)
135+
with self._lock:
136+
for log in self._log_buffer:
137+
envelope.add_log(self._log_to_otel(log))
138+
self._log_buffer.clear()
139+
if envelope.items:
140+
self._capture_func(envelope)
141+
return envelope
142+
return None

sentry_sdk/_types.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ def __eq__(self, other):
3030

3131
return self.value == other.value and self.metadata == other.metadata
3232

33+
def __str__(self):
34+
# type: (AnnotatedValue) -> str
35+
return str({"value": str(self.value), "metadata": str(self.metadata)})
36+
37+
def __len__(self):
38+
# type: (AnnotatedValue) -> int
39+
if self.value is not None:
40+
return len(self.value)
41+
else:
42+
return 0
43+
3344
@classmethod
3445
def removed_because_raw_data(cls):
3546
# type: () -> AnnotatedValue
@@ -152,8 +163,8 @@ class SDKInfo(TypedDict):
152163
Event = TypedDict(
153164
"Event",
154165
{
155-
"breadcrumbs": dict[
156-
Literal["values"], list[dict[str, Any]]
166+
"breadcrumbs": Annotated[
167+
dict[Literal["values"], list[dict[str, Any]]]
157168
], # TODO: We can expand on this type
158169
"check_in_id": str,
159170
"contexts": dict[str, dict[str, object]],

0 commit comments

Comments
 (0)