Skip to content

Commit 88f390c

Browse files
authored
Merge branch 'master' into logger_ourlogs
2 parents 42c3835 + 12b3ca3 commit 88f390c

File tree

13 files changed

+172
-56
lines changed

13 files changed

+172
-56
lines changed

CONTRIBUTING.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,14 @@ You need to have an AWS account and AWS CLI installed and setup.
182182

183183
We put together two helper functions that can help you with development:
184184

185-
- `./scripts/aws-deploy-local-layer.sh`
185+
- `./scripts/aws/aws-deploy-local-layer.sh`
186186

187-
This script [scripts/aws-deploy-local-layer.sh](scripts/aws-deploy-local-layer.sh) will take the code you have checked out locally, create a Lambda layer out of it and deploy it to the `eu-central-1` region of your configured AWS account using `aws` CLI.
187+
This script [scripts/aws/aws-deploy-local-layer.sh](scripts/aws/aws-deploy-local-layer.sh) will take the code you have checked out locally, create a Lambda layer out of it and deploy it to the `eu-central-1` region of your configured AWS account using `aws` CLI.
188188

189189
The Lambda layer will have the name `SentryPythonServerlessSDK-local-dev`
190190

191-
- `./scripts/aws-attach-layer-to-lambda-function.sh`
191+
- `./scripts/aws/aws-attach-layer-to-lambda-function.sh`
192192

193-
You can use this script [scripts/aws-attach-layer-to-lambda-function.sh](scripts/aws-attach-layer-to-lambda-function.sh) to attach the Lambda layer you just deployed (using the first script) onto one of your existing Lambda functions. You will have to give the name of the Lambda function to attach onto as an argument. (See the script for details.)
193+
You can use this script [scripts/aws/aws-attach-layer-to-lambda-function.sh](scripts/aws/aws-attach-layer-to-lambda-function.sh) to attach the Lambda layer you just deployed (using the first script) onto one of your existing Lambda functions. You will have to give the name of the Lambda function to attach onto as an argument. (See the script for details.)
194194

195195
With these two helper scripts it should be easy to rapidly iterate your development on the Lambda layer.

sentry_sdk/client.py

+9
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
ClientConstructor,
3737
)
3838
from sentry_sdk.integrations import _DEFAULT_INTEGRATIONS, setup_integrations
39+
from sentry_sdk.integrations.dedupe import DedupeIntegration
3940
from sentry_sdk.sessions import SessionFlusher
4041
from sentry_sdk.envelope import Envelope
4142
from sentry_sdk.profiler.continuous_profiler import setup_continuous_profiler
@@ -605,6 +606,14 @@ def _prepare_event(
605606
self.transport.record_lost_event(
606607
"before_send", data_category="error"
607608
)
609+
610+
# If this is an exception, reset the DedupeIntegration. It still
611+
# remembers the dropped exception as the last exception, meaning
612+
# that if the same exception happens again and is not dropped
613+
# in before_send, it'd get dropped by DedupeIntegration.
614+
if event.get("exception"):
615+
DedupeIntegration.reset_last_seen()
616+
608617
event = new_event
609618

610619
before_send_transaction = self.options["before_send_transaction"]

sentry_sdk/integrations/dedupe.py

+9
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,12 @@ def processor(event, hint):
4040
return None
4141
integration._last_seen.set(exc)
4242
return event
43+
44+
@staticmethod
45+
def reset_last_seen():
46+
# type: () -> None
47+
integration = sentry_sdk.get_client().get_integration(DedupeIntegration)
48+
if integration is None:
49+
return
50+
51+
integration._last_seen.set(None)

sentry_sdk/integrations/logging.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,10 @@ def _emit(self, record):
242242
event["logger"] = record.name
243243

244244
# Log records from `warnings` module as separate issues
245-
record_caputured_from_warnings_module = (
245+
record_captured_from_warnings_module = (
246246
record.name == "py.warnings" and record.msg == "%s"
247247
)
248-
if record_caputured_from_warnings_module:
248+
if record_captured_from_warnings_module:
249249
# use the actual message and not "%s" as the message
250250
# this prevents grouping all warnings under one "%s" issue
251251
msg = record.args[0] # type: ignore

sentry_sdk/integrations/loguru.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
if TYPE_CHECKING:
1313
from logging import LogRecord
14-
from typing import Optional, Tuple
14+
from typing import Optional, Tuple, Any
1515

1616
try:
1717
import loguru
@@ -31,6 +31,16 @@ class LoggingLevels(enum.IntEnum):
3131
CRITICAL = 50
3232

3333

34+
SENTRY_LEVEL_FROM_LOGURU_LEVEL = {
35+
"TRACE": "DEBUG",
36+
"DEBUG": "DEBUG",
37+
"INFO": "INFO",
38+
"SUCCESS": "INFO",
39+
"WARNING": "WARNING",
40+
"ERROR": "ERROR",
41+
"CRITICAL": "CRITICAL",
42+
}
43+
3444
DEFAULT_LEVEL = LoggingLevels.INFO.value
3545
DEFAULT_EVENT_LEVEL = LoggingLevels.ERROR.value
3646
# We need to save the handlers to be able to remove them later
@@ -87,14 +97,34 @@ class _LoguruBaseHandler(_BaseHandler):
8797
def _logging_to_event_level(self, record):
8898
# type: (LogRecord) -> str
8999
try:
90-
return LoggingLevels(record.levelno).name.lower()
91-
except ValueError:
100+
return SENTRY_LEVEL_FROM_LOGURU_LEVEL[
101+
LoggingLevels(record.levelno).name
102+
].lower()
103+
except (ValueError, KeyError):
92104
return record.levelname.lower() if record.levelname else ""
93105

94106

95107
class LoguruEventHandler(_LoguruBaseHandler, EventHandler):
96108
"""Modified version of :class:`sentry_sdk.integrations.logging.EventHandler` to use loguru's level names."""
97109

110+
def __init__(self, *args, **kwargs):
111+
# type: (*Any, **Any) -> None
112+
if kwargs.get("level"):
113+
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
114+
kwargs.get("level", ""), DEFAULT_LEVEL
115+
)
116+
117+
super().__init__(*args, **kwargs)
118+
98119

99120
class LoguruBreadcrumbHandler(_LoguruBaseHandler, BreadcrumbHandler):
100121
"""Modified version of :class:`sentry_sdk.integrations.logging.BreadcrumbHandler` to use loguru's level names."""
122+
123+
def __init__(self, *args, **kwargs):
124+
# type: (*Any, **Any) -> None
125+
if kwargs.get("level"):
126+
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
127+
kwargs.get("level", ""), DEFAULT_LEVEL
128+
)
129+
130+
super().__init__(*args, **kwargs)

sentry_sdk/integrations/spark/spark_driver.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ def _set_app_properties():
3131

3232
spark_context = SparkContext._active_spark_context
3333
if spark_context:
34-
spark_context.setLocalProperty("sentry_app_name", spark_context.appName)
3534
spark_context.setLocalProperty(
36-
"sentry_application_id", spark_context.applicationId
35+
"sentry_app_name",
36+
spark_context.appName,
37+
)
38+
spark_context.setLocalProperty(
39+
"sentry_application_id",
40+
spark_context.applicationId,
3741
)
3842

3943

@@ -231,12 +235,14 @@ def _add_breadcrumb(
231235
data=None, # type: Optional[dict[str, Any]]
232236
):
233237
# type: (...) -> None
234-
sentry_sdk.get_global_scope().add_breadcrumb(
238+
sentry_sdk.get_isolation_scope().add_breadcrumb(
235239
level=level, message=message, data=data
236240
)
237241

238242
def onJobStart(self, jobStart): # noqa: N802,N803
239243
# type: (Any) -> None
244+
sentry_sdk.get_isolation_scope().clear_breadcrumbs()
245+
240246
message = "Job {} Started".format(jobStart.jobId())
241247
self._add_breadcrumb(level="info", message=message)
242248
_set_app_properties()

sentry_sdk/profiler/__init__.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
)
2626

2727
__all__ = [
28-
"start_profile_session",
29-
"start_profiler", # TODO: Deprecate this in favor of `start_profile_session`
30-
"stop_profile_session",
31-
"stop_profiler", # TODO: Deprecate this in favor of `stop_profile_session`
28+
"start_profile_session", # TODO: Deprecate this in favor of `start_profiler`
29+
"start_profiler",
30+
"stop_profile_session", # TODO: Deprecate this in favor of `stop_profiler`
31+
"stop_profiler",
3232
# DEPRECATED: The following was re-exported for backwards compatibility. It
3333
# will be removed from sentry_sdk.profiler in a future release.
3434
"MAX_PROFILE_DURATION_NS",

sentry_sdk/profiler/continuous_profiler.py

+19-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import threading
66
import time
77
import uuid
8+
import warnings
89
from collections import deque
910
from datetime import datetime, timezone
1011

@@ -145,32 +146,40 @@ def try_profile_lifecycle_trace_start():
145146

146147
def start_profiler():
147148
# type: () -> None
149+
if _scheduler is None:
150+
return
148151

149-
# TODO: deprecate this as it'll be replaced by `start_profile_session`
150-
start_profile_session()
152+
_scheduler.manual_start()
151153

152154

153155
def start_profile_session():
154156
# type: () -> None
155-
if _scheduler is None:
156-
return
157157

158-
_scheduler.manual_start()
158+
warnings.warn(
159+
"The `start_profile_session` function is deprecated. Please use `start_profile` instead.",
160+
DeprecationWarning,
161+
stacklevel=2,
162+
)
163+
start_profiler()
159164

160165

161166
def stop_profiler():
162167
# type: () -> None
168+
if _scheduler is None:
169+
return
163170

164-
# TODO: deprecate this as it'll be replaced by `stop_profile_session`
165-
stop_profile_session()
171+
_scheduler.manual_stop()
166172

167173

168174
def stop_profile_session():
169175
# type: () -> None
170-
if _scheduler is None:
171-
return
172176

173-
_scheduler.manual_stop()
177+
warnings.warn(
178+
"The `stop_profile_session` function is deprecated. Please use `stop_profile` instead.",
179+
DeprecationWarning,
180+
stacklevel=2,
181+
)
182+
stop_profiler()
174183

175184

176185
def teardown_continuous_profiler():

sentry_sdk/tracing_utils.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66
from collections.abc import Mapping
77
from datetime import timedelta
8-
from decimal import ROUND_DOWN, Decimal
8+
from decimal import ROUND_DOWN, Context, Decimal
99
from functools import wraps
1010
from random import Random
1111
from urllib.parse import quote, unquote
@@ -871,7 +871,11 @@ def _generate_sample_rand(
871871
sample_rand = rng.uniform(lower, upper)
872872

873873
# Round down to exactly six decimal-digit precision.
874-
return Decimal(sample_rand).quantize(Decimal("0.000001"), rounding=ROUND_DOWN)
874+
# Setting the context is needed to avoid an InvalidOperation exception
875+
# in case the user has changed the default precision.
876+
return Decimal(sample_rand).quantize(
877+
Decimal("0.000001"), rounding=ROUND_DOWN, context=Context(prec=6)
878+
)
875879

876880

877881
def _sample_rand_range(parent_sampled, sample_rate):

tests/integrations/loguru/test_loguru.py

+12-11
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88

99

1010
@pytest.mark.parametrize(
11-
"level,created_event",
11+
"level,created_event,expected_sentry_level",
1212
[
1313
# None - no breadcrumb
1414
# False - no event
1515
# True - event created
16-
(LoggingLevels.TRACE, None),
17-
(LoggingLevels.DEBUG, None),
18-
(LoggingLevels.INFO, False),
19-
(LoggingLevels.SUCCESS, False),
20-
(LoggingLevels.WARNING, False),
21-
(LoggingLevels.ERROR, True),
22-
(LoggingLevels.CRITICAL, True),
16+
(LoggingLevels.TRACE, None, "debug"),
17+
(LoggingLevels.DEBUG, None, "debug"),
18+
(LoggingLevels.INFO, False, "info"),
19+
(LoggingLevels.SUCCESS, False, "info"),
20+
(LoggingLevels.WARNING, False, "warning"),
21+
(LoggingLevels.ERROR, True, "error"),
22+
(LoggingLevels.CRITICAL, True, "critical"),
2323
],
2424
)
2525
@pytest.mark.parametrize("disable_breadcrumbs", [True, False])
@@ -29,6 +29,7 @@ def test_just_log(
2929
capture_events,
3030
level,
3131
created_event,
32+
expected_sentry_level,
3233
disable_breadcrumbs,
3334
disable_events,
3435
):
@@ -48,7 +49,7 @@ def test_just_log(
4849
formatted_message = (
4950
" | "
5051
+ "{:9}".format(level.name.upper())
51-
+ "| tests.integrations.loguru.test_loguru:test_just_log:46 - test"
52+
+ "| tests.integrations.loguru.test_loguru:test_just_log:47 - test"
5253
)
5354

5455
if not created_event:
@@ -59,7 +60,7 @@ def test_just_log(
5960
not disable_breadcrumbs and created_event is not None
6061
): # not None == not TRACE or DEBUG level
6162
(breadcrumb,) = breadcrumbs
62-
assert breadcrumb["level"] == level.name.lower()
63+
assert breadcrumb["level"] == expected_sentry_level
6364
assert breadcrumb["category"] == "tests.integrations.loguru.test_loguru"
6465
assert breadcrumb["message"][23:] == formatted_message
6566
else:
@@ -72,7 +73,7 @@ def test_just_log(
7273
return
7374

7475
(event,) = events
75-
assert event["level"] == (level.name.lower())
76+
assert event["level"] == expected_sentry_level
7677
assert event["logger"] == "tests.integrations.loguru.test_loguru"
7778
assert event["logentry"]["message"][23:] == formatted_message
7879

tests/integrations/stdlib/test_httplib.py

+8-17
Original file line numberDiff line numberDiff line change
@@ -398,25 +398,16 @@ def test_http_timeout(monkeypatch, sentry_init, capture_envelopes):
398398

399399
envelopes = capture_envelopes()
400400

401-
with start_transaction(op="op", name="name"):
402-
try:
403-
conn = HTTPSConnection("www.squirrelchasers.com")
404-
conn.request("GET", "/top-chasers")
401+
with pytest.raises(TimeoutError):
402+
with start_transaction(op="op", name="name"):
403+
conn = HTTPSConnection("www.example.com")
404+
conn.request("GET", "/bla")
405405
conn.getresponse()
406-
except Exception:
407-
pass
408-
409-
items = [
410-
item
411-
for envelope in envelopes
412-
for item in envelope.items
413-
if item.type == "transaction"
414-
]
415-
assert len(items) == 1
416-
417-
transaction = items[0].payload.json
406+
407+
(transaction_envelope,) = envelopes
408+
transaction = transaction_envelope.get_transaction_event()
418409
assert len(transaction["spans"]) == 1
419410

420411
span = transaction["spans"][0]
421412
assert span["op"] == "http.client"
422-
assert span["description"] == "GET https://www.squirrelchasers.com/top-chasers"
413+
assert span["description"] == "GET https://www.example.com/bla"

tests/test_basics.py

+31
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,37 @@ def test_dedupe_event_processor_drop_records_client_report(
710710
assert lost_event_call == ("event_processor", "error", None, 1)
711711

712712

713+
def test_dedupe_doesnt_take_into_account_dropped_exception(sentry_init, capture_events):
714+
# Two exceptions happen one after another. The first one is dropped in the
715+
# user's before_send. The second one isn't.
716+
# Originally, DedupeIntegration would drop the second exception. This test
717+
# is making sure that that is no longer the case -- i.e., DedupeIntegration
718+
# doesn't consider exceptions dropped in before_send.
719+
count = 0
720+
721+
def before_send(event, hint):
722+
nonlocal count
723+
count += 1
724+
if count == 1:
725+
return None
726+
return event
727+
728+
sentry_init(before_send=before_send)
729+
events = capture_events()
730+
731+
exc = ValueError("aha!")
732+
for _ in range(2):
733+
# The first ValueError will be dropped by before_send. The second
734+
# ValueError will be accepted by before_send, and should be sent to
735+
# Sentry.
736+
try:
737+
raise exc
738+
except Exception:
739+
capture_exception()
740+
741+
assert len(events) == 1
742+
743+
713744
def test_event_processor_drop_records_client_report(
714745
sentry_init, capture_events, capture_record_lost_event_calls
715746
):

0 commit comments

Comments
 (0)