Skip to content

Commit cead2b0

Browse files
committed
Merge branch 'master' into antonpirker/openai-agents-integration
2 parents e79d3c7 + 0e21fa2 commit cead2b0

File tree

19 files changed

+875
-300
lines changed

19 files changed

+875
-300
lines changed

.github/workflows/test-integrations-tasks.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ jobs:
4646
allow-prereleases: true
4747
- name: Start Redis
4848
uses: supercharge/[email protected]
49+
- name: Install Java
50+
uses: actions/setup-java@v4
51+
with:
52+
distribution: 'temurin'
53+
java-version: '21'
4954
- name: Setup Test Env
5055
run: |
5156
pip install "coverage[toml]" tox
@@ -135,6 +140,11 @@ jobs:
135140
allow-prereleases: true
136141
- name: Start Redis
137142
uses: supercharge/[email protected]
143+
- name: Install Java
144+
uses: actions/setup-java@v4
145+
with:
146+
distribution: 'temurin'
147+
java-version: '21'
138148
- name: Setup Test Env
139149
run: |
140150
pip install "coverage[toml]" tox

CHANGELOG.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,44 @@
11
# Changelog
22

3+
## 2.30.0
4+
5+
### Various fixes & improvements
6+
7+
- **New beta feature:** Sentry logs for Loguru (#4445) by @sentrivana
8+
9+
We can now capture Loguru logs and send them to Sentry.
10+
11+
```python
12+
import sentry_sdk
13+
from sentry_sdk.integrations.loguru import LoguruIntegration
14+
15+
# Setup Sentry SDK to send Loguru log messages with a level of "error" or higher to Sentry
16+
sentry_sdk.init(
17+
_experiments={
18+
"enable_logs": True,
19+
},
20+
integrations=[
21+
LoguruIntegration(sentry_logs_level=logging.ERROR),
22+
]
23+
)
24+
```
25+
26+
- fix(logs): Don't gate user behind `send_default_pii` (#4453) by @AbhiPrasad
27+
- fix(logging): Strip log `record.name` for more robust matching (#4411) by @romaingd-spi
28+
- Migrate to modern threading interface (#4452) by @emmanuel-ferdman
29+
- ref: Remove `_capture_experimental_log` `scope` parameter (#4424) by @szokeasaurusrex
30+
- feat(logs): Add user attributes to logs (#4423) by @szokeasaurusrex
31+
- fix: fix ARQ integration error (#4427) (#4428) by @ninoseki
32+
- fix(grpc): Fix AttributeError when instrumenting with OTel (#4405) by @sentrivana
33+
- fix(redis): Use `command_queue` instead of `command_stack` if available (#4404) by @sentrivana
34+
- fix: Handle invalid `SENTRY_DEBUG` values properly (#4400) by @szokeasaurusrex
35+
- Increase test coverage (#4393) by @mgaligniana
36+
- tests(logs): avoid failures when running with integrations enabled (#4388) by @rominf
37+
- Fix CI, adapt to new redis-py release (#4431) by @sentrivana
38+
- tests: Regenerate toxgen (#4403) by @sentrivana
39+
- tests: Regenerate tox.ini & fix CI (#4435) by @sentrivana
40+
- build(deps): bump codecov/codecov-action from 5.4.2 to 5.4.3 (#4397) by @dependabot
41+
342
## 2.29.1
443

544
### Various fixes & improvements
@@ -122,7 +161,7 @@
122161
sentry_sdk.init(
123162
dsn="...",
124163
_experiments={
125-
"enable_sentry_logs": True
164+
"enable_logs": True
126165
}
127166
integrations=[
128167
LoggingIntegration(sentry_logs_level=logging.ERROR),

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.29.1"
34+
release = "2.30.0"
3535
version = ".".join(release.split(".")[:2]) # The short X.Y version.
3636

3737

scripts/populate_tox/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@
5858
"pytest-django",
5959
"Werkzeug",
6060
],
61-
">=3.0": ["pytest-asyncio"],
61+
">=2.0": ["channels[daphne]"],
6262
">=2.2,<3.1": ["six"],
63+
">=3.0": ["pytest-asyncio"],
6364
"<3.3": [
6465
"djangorestframework>=3.0,<4.0",
6566
"Werkzeug<2.1.0",
6667
],
6768
"<3.1": ["pytest-django<4.0"],
68-
">=2.0": ["channels[daphne]"],
6969
},
7070
},
7171
"dramatiq": {

scripts/populate_tox/tox.jinja

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ deps =
172172
# for justification of the upper bound on pytest
173173
{py3.6,py3.7}-gevent: pytest<7.0.0
174174
{py3.8,py3.9,py3.10,py3.11,py3.12}-gevent: pytest
175+
gevent: pytest-asyncio
175176
176177
# === Integrations ===
177178
@@ -362,6 +363,7 @@ setenv =
362363
py3.6: COVERAGE_RCFILE=.coveragerc36
363364
364365
django: DJANGO_SETTINGS_MODULE=tests.integrations.django.myapp.settings
366+
spark-v{3.0.3,3.5.6}: JAVA_HOME=/usr/lib/jvm/temurin-11-jdk-amd64
365367
366368
common: TESTPATH=tests
367369
gevent: TESTPATH=tests

scripts/split_tox_gh_actions/split_tox_gh_actions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
"aws_lambda",
4848
}
4949

50+
FRAMEWORKS_NEEDING_JAVA = {
51+
"spark",
52+
}
53+
5054
# Frameworks grouped here will be tested together to not hog all GitHub runners.
5155
# If you add or remove a group, make sure to git rm the generated YAML file as
5256
# well.
@@ -289,6 +293,7 @@ def render_template(group, frameworks, py_versions_pinned, py_versions_latest):
289293
"needs_docker": bool(set(frameworks) & FRAMEWORKS_NEEDING_DOCKER),
290294
"needs_postgres": bool(set(frameworks) & FRAMEWORKS_NEEDING_POSTGRES),
291295
"needs_redis": bool(set(frameworks) & FRAMEWORKS_NEEDING_REDIS),
296+
"needs_java": bool(set(frameworks) & FRAMEWORKS_NEEDING_JAVA),
292297
"py_versions": {
293298
category: [f'"{version}"' for version in _normalize_py_versions(versions)]
294299
for category, versions in py_versions.items()

scripts/split_tox_gh_actions/templates/test_group.jinja

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
with:
4949
python-version: {% raw %}${{ matrix.python-version }}{% endraw %}
5050
allow-prereleases: true
51+
5152
{% if needs_clickhouse %}
5253
- name: "Setup ClickHouse Server"
5354
uses: getsentry/[email protected]
@@ -58,6 +59,14 @@
5859
uses: supercharge/[email protected]
5960
{% endif %}
6061

62+
{% if needs_java %}
63+
- name: Install Java
64+
uses: actions/setup-java@v4
65+
with:
66+
distribution: 'temurin'
67+
java-version: '21'
68+
{% endif %}
69+
6170
- name: Setup Test Env
6271
run: |
6372
pip install "coverage[toml]" tox

sentry_sdk/client.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import TYPE_CHECKING, List, Dict, cast, overload
99
import warnings
1010

11+
import sentry_sdk
1112
from sentry_sdk._compat import PY37, check_uwsgi_thread_support
1213
from sentry_sdk.utils import (
1314
AnnotatedValue,
@@ -215,8 +216,8 @@ def capture_event(self, *args, **kwargs):
215216
# type: (*Any, **Any) -> Optional[str]
216217
return None
217218

218-
def _capture_experimental_log(self, scope, log):
219-
# type: (Scope, Log) -> None
219+
def _capture_experimental_log(self, log):
220+
# type: (Log) -> None
220221
pass
221222

222223
def capture_session(self, *args, **kwargs):
@@ -893,12 +894,14 @@ def capture_event(
893894

894895
return return_value
895896

896-
def _capture_experimental_log(self, current_scope, log):
897-
# type: (Scope, Log) -> None
897+
def _capture_experimental_log(self, log):
898+
# type: (Log) -> None
898899
logs_enabled = self.options["_experiments"].get("enable_logs", False)
899900
if not logs_enabled:
900901
return
901-
isolation_scope = current_scope.get_isolation_scope()
902+
903+
current_scope = sentry_sdk.get_current_scope()
904+
isolation_scope = sentry_sdk.get_isolation_scope()
902905

903906
log["attributes"]["sentry.sdk.name"] = SDK_INFO["name"]
904907
log["attributes"]["sentry.sdk.version"] = SDK_INFO["version"]
@@ -927,6 +930,21 @@ def _capture_experimental_log(self, current_scope, log):
927930
elif propagation_context is not None:
928931
log["trace_id"] = propagation_context.trace_id
929932

933+
# The user, if present, is always set on the isolation scope.
934+
if isolation_scope._user is not None:
935+
for log_attribute, user_attribute in (
936+
("user.id", "id"),
937+
("user.name", "username"),
938+
("user.email", "email"),
939+
):
940+
if (
941+
user_attribute in isolation_scope._user
942+
and log_attribute not in log["attributes"]
943+
):
944+
log["attributes"][log_attribute] = isolation_scope._user[
945+
user_attribute
946+
]
947+
930948
# If debug is enabled, log the log to the console
931949
debug = self.options.get("debug", False)
932950
if debug:

sentry_sdk/consts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1175,4 +1175,4 @@ def _get_default_options():
11751175
del _get_default_options
11761176

11771177

1178-
VERSION = "2.29.1"
1178+
VERSION = "2.30.0"

sentry_sdk/integrations/logging.py

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import sentry_sdk
77
from sentry_sdk.client import BaseClient
8+
from sentry_sdk.logger import _log_level_to_otel
89
from sentry_sdk.utils import (
910
safe_repr,
1011
to_string,
@@ -14,7 +15,7 @@
1415
)
1516
from sentry_sdk.integrations import Integration
1617

17-
from typing import TYPE_CHECKING, Tuple
18+
from typing import TYPE_CHECKING
1819

1920
if TYPE_CHECKING:
2021
from collections.abc import MutableMapping
@@ -36,6 +37,16 @@
3637
logging.CRITICAL: "fatal", # CRITICAL is same as FATAL
3738
}
3839

40+
# Map logging level numbers to corresponding OTel level numbers
41+
SEVERITY_TO_OTEL_SEVERITY = {
42+
logging.CRITICAL: 21, # fatal
43+
logging.ERROR: 17, # error
44+
logging.WARNING: 13, # warn
45+
logging.INFO: 9, # info
46+
logging.DEBUG: 5, # debug
47+
}
48+
49+
3950
# Capturing events from those loggers causes recursion errors. We cannot allow
4051
# the user to unconditionally create events from those loggers under any
4152
# circumstances.
@@ -119,7 +130,10 @@ def sentry_patched_callhandlers(self, record):
119130
# the integration. Otherwise we have a high chance of getting
120131
# into a recursion error when the integration is resolved
121132
# (this also is slower).
122-
if ignored_loggers is not None and record.name not in ignored_loggers:
133+
if (
134+
ignored_loggers is not None
135+
and record.name.strip() not in ignored_loggers
136+
):
123137
integration = sentry_sdk.get_client().get_integration(
124138
LoggingIntegration
125139
)
@@ -164,7 +178,7 @@ def _can_record(self, record):
164178
# type: (LogRecord) -> bool
165179
"""Prevents ignored loggers from recording"""
166180
for logger in _IGNORED_LOGGERS:
167-
if fnmatch(record.name, logger):
181+
if fnmatch(record.name.strip(), logger):
168182
return False
169183
return True
170184

@@ -312,21 +326,6 @@ def _breadcrumb_from_record(self, record):
312326
}
313327

314328

315-
def _python_level_to_otel(record_level):
316-
# type: (int) -> Tuple[int, str]
317-
for py_level, otel_severity_number, otel_severity_text in [
318-
(50, 21, "fatal"),
319-
(40, 17, "error"),
320-
(30, 13, "warn"),
321-
(20, 9, "info"),
322-
(10, 5, "debug"),
323-
(5, 1, "trace"),
324-
]:
325-
if record_level >= py_level:
326-
return otel_severity_number, otel_severity_text
327-
return 0, "default"
328-
329-
330329
class SentryLogsHandler(_BaseHandler):
331330
"""
332331
A logging handler that records Sentry logs for each Python log record.
@@ -352,8 +351,9 @@ def emit(self, record):
352351

353352
def _capture_log_from_record(self, client, record):
354353
# type: (BaseClient, LogRecord) -> None
355-
scope = sentry_sdk.get_current_scope()
356-
otel_severity_number, otel_severity_text = _python_level_to_otel(record.levelno)
354+
otel_severity_number, otel_severity_text = _log_level_to_otel(
355+
record.levelno, SEVERITY_TO_OTEL_SEVERITY
356+
)
357357
project_root = client.options["project_root"]
358358
attrs = self._extra_from_record(record) # type: Any
359359
attrs["sentry.origin"] = "auto.logger.log"
@@ -364,10 +364,7 @@ def _capture_log_from_record(self, client, record):
364364
for i, arg in enumerate(record.args):
365365
attrs[f"sentry.message.parameter.{i}"] = (
366366
arg
367-
if isinstance(arg, str)
368-
or isinstance(arg, float)
369-
or isinstance(arg, int)
370-
or isinstance(arg, bool)
367+
if isinstance(arg, (str, float, int, bool))
371368
else safe_repr(arg)
372369
)
373370
if record.lineno:
@@ -394,7 +391,6 @@ def _capture_log_from_record(self, client, record):
394391

395392
# noinspection PyProtectedMember
396393
client._capture_experimental_log(
397-
scope,
398394
{
399395
"severity_text": otel_severity_text,
400396
"severity_number": otel_severity_number,

0 commit comments

Comments
 (0)