Skip to content

Commit ff2ff75

Browse files
authored
Merge branch 'master' into feat/grpc-aio-integration
2 parents 9a97c9a + fc638fd commit ff2ff75

File tree

5 files changed

+241
-90
lines changed

5 files changed

+241
-90
lines changed

sentry_sdk/integrations/redis/__init__.py

Lines changed: 90 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,31 @@
22

33
from sentry_sdk import Hub
44
from sentry_sdk.consts import OP, SPANDATA
5+
from sentry_sdk._compat import text_type
56
from sentry_sdk.hub import _should_send_default_pii
7+
from sentry_sdk.integrations import Integration, DidNotEnable
8+
from sentry_sdk._types import TYPE_CHECKING
69
from sentry_sdk.utils import (
710
SENSITIVE_DATA_SUBSTITUTE,
811
capture_internal_exceptions,
912
logger,
1013
)
11-
from sentry_sdk.integrations import Integration, DidNotEnable
12-
13-
from sentry_sdk._types import TYPE_CHECKING
1414

1515
if TYPE_CHECKING:
16-
from typing import Any, Sequence
16+
from typing import Any, Dict, Sequence
1717
from sentry_sdk.tracing import Span
1818

1919
_SINGLE_KEY_COMMANDS = frozenset(
20-
["decr", "decrby", "get", "incr", "incrby", "pttl", "set", "setex", "setnx", "ttl"]
20+
["decr", "decrby", "get", "incr", "incrby", "pttl", "set", "setex", "setnx", "ttl"],
21+
)
22+
_MULTI_KEY_COMMANDS = frozenset(
23+
["del", "touch", "unlink"],
2124
)
22-
_MULTI_KEY_COMMANDS = frozenset(["del", "touch", "unlink"])
23-
2425
_COMMANDS_INCLUDING_SENSITIVE_DATA = [
2526
"auth",
2627
]
27-
2828
_MAX_NUM_ARGS = 10 # Trim argument lists to this many values
2929
_MAX_NUM_COMMANDS = 10 # Trim command lists to this many values
30-
3130
_DEFAULT_MAX_DATA_SIZE = 1024
3231

3332

@@ -59,6 +58,26 @@ def _get_safe_command(name, args):
5958
return command
6059

6160

61+
def _get_span_description(name, *args):
62+
# type: (str, *Any) -> str
63+
description = name
64+
65+
with capture_internal_exceptions():
66+
description = _get_safe_command(name, args)
67+
68+
return description
69+
70+
71+
def _get_redis_command_args(command):
72+
# type: (Any) -> Sequence[Any]
73+
return command[0]
74+
75+
76+
def _parse_rediscluster_command(command):
77+
# type: (Any) -> Sequence[Any]
78+
return command.args
79+
80+
6281
def _set_pipeline_data(
6382
span, is_cluster, get_command_args_fn, is_transaction, command_stack
6483
):
@@ -84,6 +103,38 @@ def _set_pipeline_data(
84103
)
85104

86105

106+
def _set_client_data(span, is_cluster, name, *args):
107+
# type: (Span, bool, str, *Any) -> None
108+
span.set_tag("redis.is_cluster", is_cluster)
109+
if name:
110+
span.set_tag("redis.command", name)
111+
span.set_tag(SPANDATA.DB_OPERATION, name)
112+
113+
if name and args:
114+
name_low = name.lower()
115+
if (name_low in _SINGLE_KEY_COMMANDS) or (
116+
name_low in _MULTI_KEY_COMMANDS and len(args) == 1
117+
):
118+
span.set_tag("redis.key", args[0])
119+
120+
121+
def _set_db_data(span, connection_params):
122+
# type: (Span, Dict[str, Any]) -> None
123+
span.set_data(SPANDATA.DB_SYSTEM, "redis")
124+
125+
db = connection_params.get("db")
126+
if db is not None:
127+
span.set_data(SPANDATA.DB_NAME, text_type(db))
128+
129+
host = connection_params.get("host")
130+
if host is not None:
131+
span.set_data(SPANDATA.SERVER_ADDRESS, host)
132+
133+
port = connection_params.get("port")
134+
if port is not None:
135+
span.set_data(SPANDATA.SERVER_PORT, port)
136+
137+
87138
def patch_redis_pipeline(pipeline_cls, is_cluster, get_command_args_fn):
88139
# type: (Any, bool, Any) -> None
89140
old_execute = pipeline_cls.execute
@@ -99,28 +150,51 @@ def sentry_patched_execute(self, *args, **kwargs):
99150
op=OP.DB_REDIS, description="redis.pipeline.execute"
100151
) as span:
101152
with capture_internal_exceptions():
153+
_set_db_data(span, self.connection_pool.connection_kwargs)
102154
_set_pipeline_data(
103155
span,
104156
is_cluster,
105157
get_command_args_fn,
106158
self.transaction,
107159
self.command_stack,
108160
)
109-
span.set_data(SPANDATA.DB_SYSTEM, "redis")
110161

111162
return old_execute(self, *args, **kwargs)
112163

113164
pipeline_cls.execute = sentry_patched_execute
114165

115166

116-
def _get_redis_command_args(command):
117-
# type: (Any) -> Sequence[Any]
118-
return command[0]
167+
def patch_redis_client(cls, is_cluster):
168+
# type: (Any, bool) -> None
169+
"""
170+
This function can be used to instrument custom redis client classes or
171+
subclasses.
172+
"""
173+
old_execute_command = cls.execute_command
119174

175+
def sentry_patched_execute_command(self, name, *args, **kwargs):
176+
# type: (Any, str, *Any, **Any) -> Any
177+
hub = Hub.current
178+
integration = hub.get_integration(RedisIntegration)
120179

121-
def _parse_rediscluster_command(command):
122-
# type: (Any) -> Sequence[Any]
123-
return command.args
180+
if integration is None:
181+
return old_execute_command(self, name, *args, **kwargs)
182+
183+
description = _get_span_description(name, *args)
184+
185+
data_should_be_truncated = (
186+
integration.max_data_size and len(description) > integration.max_data_size
187+
)
188+
if data_should_be_truncated:
189+
description = description[: integration.max_data_size - len("...")] + "..."
190+
191+
with hub.start_span(op=OP.DB_REDIS, description=description) as span:
192+
_set_db_data(span, self.connection_pool.connection_kwargs)
193+
_set_client_data(span, is_cluster, name, *args)
194+
195+
return old_execute_command(self, name, *args, **kwargs)
196+
197+
cls.execute_command = sentry_patched_execute_command
124198

125199

126200
def _patch_redis(StrictRedis, client): # noqa: N803
@@ -206,61 +280,3 @@ def setup_once():
206280
_patch_rediscluster()
207281
except Exception:
208282
logger.exception("Error occurred while patching `rediscluster` library")
209-
210-
211-
def _get_span_description(name, *args):
212-
# type: (str, *Any) -> str
213-
description = name
214-
215-
with capture_internal_exceptions():
216-
description = _get_safe_command(name, args)
217-
218-
return description
219-
220-
221-
def _set_client_data(span, is_cluster, name, *args):
222-
# type: (Span, bool, str, *Any) -> None
223-
span.set_data(SPANDATA.DB_SYSTEM, "redis")
224-
span.set_tag("redis.is_cluster", is_cluster)
225-
if name:
226-
span.set_tag("redis.command", name)
227-
span.set_tag(SPANDATA.DB_OPERATION, name)
228-
229-
if name and args:
230-
name_low = name.lower()
231-
if (name_low in _SINGLE_KEY_COMMANDS) or (
232-
name_low in _MULTI_KEY_COMMANDS and len(args) == 1
233-
):
234-
span.set_tag("redis.key", args[0])
235-
236-
237-
def patch_redis_client(cls, is_cluster):
238-
# type: (Any, bool) -> None
239-
"""
240-
This function can be used to instrument custom redis client classes or
241-
subclasses.
242-
"""
243-
old_execute_command = cls.execute_command
244-
245-
def sentry_patched_execute_command(self, name, *args, **kwargs):
246-
# type: (Any, str, *Any, **Any) -> Any
247-
hub = Hub.current
248-
integration = hub.get_integration(RedisIntegration)
249-
250-
if integration is None:
251-
return old_execute_command(self, name, *args, **kwargs)
252-
253-
description = _get_span_description(name, *args)
254-
255-
data_should_be_truncated = (
256-
integration.max_data_size and len(description) > integration.max_data_size
257-
)
258-
if data_should_be_truncated:
259-
description = description[: integration.max_data_size - len("...")] + "..."
260-
261-
with hub.start_span(op=OP.DB_REDIS, description=description) as span:
262-
_set_client_data(span, is_cluster, name, *args)
263-
264-
return old_execute_command(self, name, *args, **kwargs)
265-
266-
cls.execute_command = sentry_patched_execute_command

sentry_sdk/integrations/redis/asyncio.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22

33
from sentry_sdk import Hub
44
from sentry_sdk.consts import OP
5-
from sentry_sdk.utils import capture_internal_exceptions
65
from sentry_sdk.integrations.redis import (
76
RedisIntegration,
87
_get_redis_command_args,
98
_get_span_description,
109
_set_client_data,
10+
_set_db_data,
1111
_set_pipeline_data,
1212
)
13+
from sentry_sdk._types import TYPE_CHECKING
14+
from sentry_sdk.utils import capture_internal_exceptions
1315

14-
15-
from sentry_sdk._types import MYPY
16-
17-
if MYPY:
16+
if TYPE_CHECKING:
1817
from typing import Any
1918

2019

@@ -33,6 +32,7 @@ async def _sentry_execute(self, *args, **kwargs):
3332
op=OP.DB_REDIS, description="redis.pipeline.execute"
3433
) as span:
3534
with capture_internal_exceptions():
35+
_set_db_data(span, self.connection_pool.connection_kwargs)
3636
_set_pipeline_data(
3737
span,
3838
False,
@@ -60,6 +60,7 @@ async def _sentry_execute_command(self, name, *args, **kwargs):
6060
description = _get_span_description(name, *args)
6161

6262
with hub.start_span(op=OP.DB_REDIS, description=description) as span:
63+
_set_db_data(span, self.connection_pool.connection_kwargs)
6364
_set_client_data(span, False, name, *args)
6465

6566
return await old_execute_command(self, name, *args, **kwargs)

tests/integrations/redis/asyncio/test_redis_asyncio.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from sentry_sdk import capture_message, start_transaction
4+
from sentry_sdk.consts import SPANDATA
45
from sentry_sdk.integrations.redis import RedisIntegration
56

67
from fakeredis.aioredis import FakeRedis
@@ -67,7 +68,13 @@ async def test_async_redis_pipeline(
6768
"redis.commands": {
6869
"count": 3,
6970
"first_ten": expected_first_ten,
70-
}
71+
},
72+
SPANDATA.DB_SYSTEM: "redis",
73+
SPANDATA.DB_NAME: "0",
74+
SPANDATA.SERVER_ADDRESS: connection.connection_pool.connection_kwargs.get(
75+
"host"
76+
),
77+
SPANDATA.SERVER_PORT: 6379,
7178
}
7279
assert span["tags"] == {
7380
"redis.transaction": is_transaction,

tests/integrations/redis/test_redis.py

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
import mock # python < 3.3
1313

1414

15+
MOCK_CONNECTION_POOL = mock.MagicMock()
16+
MOCK_CONNECTION_POOL.connection_kwargs = {
17+
"host": "localhost",
18+
"port": 63791,
19+
"db": 1,
20+
}
21+
22+
1523
def test_basic(sentry_init, capture_events):
1624
sentry_init(integrations=[RedisIntegration()])
1725
events = capture_events()
@@ -67,12 +75,10 @@ def test_redis_pipeline(
6775
(span,) = event["spans"]
6876
assert span["op"] == "db.redis"
6977
assert span["description"] == "redis.pipeline.execute"
70-
assert span["data"] == {
71-
"redis.commands": {
72-
"count": 3,
73-
"first_ten": expected_first_ten,
74-
},
75-
SPANDATA.DB_SYSTEM: "redis",
78+
assert span["data"][SPANDATA.DB_SYSTEM] == "redis"
79+
assert span["data"]["redis.commands"] == {
80+
"count": 3,
81+
"first_ten": expected_first_ten,
7682
}
7783
assert span["tags"] == {
7884
"redis.transaction": is_transaction,
@@ -242,3 +248,51 @@ def test_breadcrumbs(sentry_init, capture_events):
242248
},
243249
"timestamp": crumbs[1]["timestamp"],
244250
}
251+
252+
253+
def test_db_connection_attributes_client(sentry_init, capture_events):
254+
sentry_init(
255+
traces_sample_rate=1.0,
256+
integrations=[RedisIntegration()],
257+
)
258+
events = capture_events()
259+
260+
with start_transaction():
261+
connection = FakeStrictRedis(connection_pool=MOCK_CONNECTION_POOL)
262+
connection.get("foobar")
263+
264+
(event,) = events
265+
(span,) = event["spans"]
266+
267+
assert span["op"] == "db.redis"
268+
assert span["description"] == "GET 'foobar'"
269+
assert span["data"][SPANDATA.DB_SYSTEM] == "redis"
270+
assert span["data"][SPANDATA.DB_NAME] == "1"
271+
assert span["data"][SPANDATA.SERVER_ADDRESS] == "localhost"
272+
assert span["data"][SPANDATA.SERVER_PORT] == 63791
273+
274+
275+
def test_db_connection_attributes_pipeline(sentry_init, capture_events):
276+
sentry_init(
277+
traces_sample_rate=1.0,
278+
integrations=[RedisIntegration()],
279+
)
280+
events = capture_events()
281+
282+
with start_transaction():
283+
connection = FakeStrictRedis(connection_pool=MOCK_CONNECTION_POOL)
284+
pipeline = connection.pipeline(transaction=False)
285+
pipeline.get("foo")
286+
pipeline.set("bar", 1)
287+
pipeline.set("baz", 2)
288+
pipeline.execute()
289+
290+
(event,) = events
291+
(span,) = event["spans"]
292+
293+
assert span["op"] == "db.redis"
294+
assert span["description"] == "redis.pipeline.execute"
295+
assert span["data"][SPANDATA.DB_SYSTEM] == "redis"
296+
assert span["data"][SPANDATA.DB_NAME] == "1"
297+
assert span["data"][SPANDATA.SERVER_ADDRESS] == "localhost"
298+
assert span["data"][SPANDATA.SERVER_PORT] == 63791

0 commit comments

Comments
 (0)