Skip to content

Commit 088431e

Browse files
HazATsl0thentr0pyantonpirker
authored
feat: Send to Spotlight sidecar (#2524)
Add Spotlight option to SDK. This allows sending envelopes to the Spotlight sidecar. --------- Co-authored-by: Neel Shah <neelshah.sa@gmail.com> Co-authored-by: Anton Pirker <anton.pirker@sentry.io>
1 parent ea55387 commit 088431e

File tree

4 files changed

+127
-3
lines changed

4 files changed

+127
-3
lines changed

sentry_sdk/client.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from sentry_sdk.profiler import has_profiling_enabled, setup_profiler
3434
from sentry_sdk.scrubber import EventScrubber
3535
from sentry_sdk.monitor import Monitor
36+
from sentry_sdk.spotlight import setup_spotlight
3637

3738
from sentry_sdk._types import TYPE_CHECKING
3839

@@ -268,6 +269,10 @@ def _capture_envelope(envelope):
268269
],
269270
)
270271

272+
self.spotlight = None
273+
if self.options.get("spotlight"):
274+
self.spotlight = setup_spotlight(self.options)
275+
271276
sdk_name = get_sdk_name(list(self.integrations.keys()))
272277
SDK_INFO["name"] = sdk_name
273278
logger.debug("Setting SDK name to '%s'", sdk_name)
@@ -548,8 +553,6 @@ def capture_event(
548553
if disable_capture_event.get(False):
549554
return None
550555

551-
if self.transport is None:
552-
return None
553556
if hint is None:
554557
hint = {}
555558
event_id = event.get("event_id")
@@ -591,7 +594,11 @@ def capture_event(
591594
# If tracing is enabled all events should go to /envelope endpoint.
592595
# If no tracing is enabled only transactions, events with attachments, and checkins should go to the /envelope endpoint.
593596
should_use_envelope_endpoint = (
594-
tracing_enabled or is_transaction or is_checkin or bool(attachments)
597+
tracing_enabled
598+
or is_transaction
599+
or is_checkin
600+
or bool(attachments)
601+
or bool(self.spotlight)
595602
)
596603
if should_use_envelope_endpoint:
597604
headers = {
@@ -616,9 +623,18 @@ def capture_event(
616623
for attachment in attachments or ():
617624
envelope.add_item(attachment.to_envelope_item())
618625

626+
if self.spotlight:
627+
self.spotlight.capture_envelope(envelope)
628+
629+
if self.transport is None:
630+
return None
631+
619632
self.transport.capture_envelope(envelope)
620633

621634
else:
635+
if self.transport is None:
636+
return None
637+
622638
# All other events go to the legacy /store/ endpoint (will be removed in the future).
623639
self.transport.capture_event(event_opt)
624640

sentry_sdk/consts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ def __init__(
263263
max_value_length=DEFAULT_MAX_VALUE_LENGTH, # type: int
264264
enable_backpressure_handling=True, # type: bool
265265
error_sampler=None, # type: Optional[Callable[[Event, Hint], Union[float, bool]]]
266+
spotlight=None, # type: Optional[Union[bool, str]]
266267
):
267268
# type: (...) -> None
268269
pass

sentry_sdk/spotlight.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import io
2+
import urllib3
3+
4+
from sentry_sdk._types import TYPE_CHECKING
5+
6+
if TYPE_CHECKING:
7+
from typing import Any
8+
from typing import Dict
9+
from typing import Optional
10+
11+
from sentry_sdk.utils import logger
12+
from sentry_sdk.envelope import Envelope
13+
14+
15+
class SpotlightClient(object):
16+
def __init__(self, url):
17+
# type: (str) -> None
18+
self.url = url
19+
self.http = urllib3.PoolManager()
20+
21+
def capture_envelope(self, envelope):
22+
# type: (Envelope) -> None
23+
body = io.BytesIO()
24+
envelope.serialize_into(body)
25+
try:
26+
req = self.http.request(
27+
url=self.url,
28+
body=body.getvalue(),
29+
method="POST",
30+
headers={
31+
"Content-Type": "application/x-sentry-envelope",
32+
},
33+
)
34+
req.close()
35+
except Exception as e:
36+
logger.exception(str(e))
37+
38+
39+
def setup_spotlight(options):
40+
# type: (Dict[str, Any]) -> Optional[SpotlightClient]
41+
42+
url = options.get("spotlight")
43+
44+
if isinstance(url, str):
45+
pass
46+
elif url is True:
47+
url = "http://localhost:8969/stream"
48+
else:
49+
return None
50+
51+
return SpotlightClient(url)

tests/test_spotlight.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import pytest
2+
3+
from sentry_sdk import Hub, capture_exception
4+
5+
6+
@pytest.fixture
7+
def capture_spotlight_envelopes(monkeypatch):
8+
def inner():
9+
envelopes = []
10+
test_spotlight = Hub.current.client.spotlight
11+
old_capture_envelope = test_spotlight.capture_envelope
12+
13+
def append_envelope(envelope):
14+
envelopes.append(envelope)
15+
return old_capture_envelope(envelope)
16+
17+
monkeypatch.setattr(test_spotlight, "capture_envelope", append_envelope)
18+
return envelopes
19+
20+
return inner
21+
22+
23+
def test_spotlight_off_by_default(sentry_init):
24+
sentry_init()
25+
assert Hub.current.client.spotlight is None
26+
27+
28+
def test_spotlight_default_url(sentry_init):
29+
sentry_init(spotlight=True)
30+
31+
spotlight = Hub.current.client.spotlight
32+
assert spotlight is not None
33+
assert spotlight.url == "http://localhost:8969/stream"
34+
35+
36+
def test_spotlight_custom_url(sentry_init):
37+
sentry_init(spotlight="http://foobar@test.com/132")
38+
39+
spotlight = Hub.current.client.spotlight
40+
assert spotlight is not None
41+
assert spotlight.url == "http://foobar@test.com/132"
42+
43+
44+
def test_spotlight_envelope(sentry_init, capture_spotlight_envelopes):
45+
sentry_init(spotlight=True)
46+
envelopes = capture_spotlight_envelopes()
47+
48+
try:
49+
raise ValueError("aha!")
50+
except Exception:
51+
capture_exception()
52+
53+
(envelope,) = envelopes
54+
payload = envelope.items[0].payload.json
55+
56+
assert payload["exception"]["values"][0]["value"] == "aha!"

0 commit comments

Comments
 (0)