Skip to content

Commit 6bbcc99

Browse files
committed
Add Redis instrumentation query sanitization
Add a query sanitizer to the Redis instrumentation. This can be disabled with the `sanitize_query = False` config option. Given the query `SET key value`, the sanitized query becomes `SET ? ?`. Both the keys and values are sanitized, as both can contain PII data. The Redis queries are sanitized by default. This changes the default behavior of this instrumentation. Previously it reported unsanitized Redis queries. This was previously discussed in the previous implementation of this PR in PR open-telemetry#1571 Closes open-telemetry#1548
1 parent 494bf09 commit 6bbcc99

File tree

4 files changed

+72
-19
lines changed

4 files changed

+72
-19
lines changed

Diff for: CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Added
11+
12+
- `opentelemetry-instrumentation-redis` Add `sanitize_query` config option to allow query sanitization. Enabled by default.
13+
([#1572](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1572))
14+
1015
## Fixed
1116

1217
- Fix aiopg instrumentation to work with aiopg < 2.0.0

Diff for: instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ async def redis_get():
6464
response_hook (Callable) - a function with extra user-defined logic to be performed after performing the request
6565
this function signature is: def response_hook(span: Span, instance: redis.connection.Connection, response) -> None
6666
67+
sanitize_query True(Default) or False - enable the Redis query sanization
68+
6769
for example:
6870
6971
.. code: python
@@ -139,9 +141,11 @@ def _instrument(
139141
tracer,
140142
request_hook: _RequestHookT = None,
141143
response_hook: _ResponseHookT = None,
144+
sanitize_query: bool = True,
142145
):
143146
def _traced_execute_command(func, instance, args, kwargs):
144-
query = _format_command_args(args)
147+
query = _format_command_args(args, sanitize_query)
148+
145149
if len(args) > 0 and args[0]:
146150
name = args[0]
147151
else:
@@ -169,7 +173,7 @@ def _traced_execute_pipeline(func, instance, args, kwargs):
169173
)
170174

171175
cmds = [
172-
_format_command_args(c.args if hasattr(c, "args") else c[0])
176+
_format_command_args(c.args if hasattr(c, "args") else c[0], sanitize_query)
173177
for c in command_stack
174178
]
175179
resource = "\n".join(cmds)
@@ -281,6 +285,7 @@ def _instrument(self, **kwargs):
281285
tracer,
282286
request_hook=kwargs.get("request_hook"),
283287
response_hook=kwargs.get("response_hook"),
288+
sanitize_query=kwargs.get("sanitize_query", True),
284289
)
285290

286291
def _uninstrument(self, **kwargs):

Diff for: instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/util.py

+21-17
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,29 @@ def _extract_conn_attributes(conn_kwargs):
4848
return attributes
4949

5050

51-
def _format_command_args(args):
52-
"""Format command arguments and trim them as needed"""
53-
value_max_len = 100
54-
value_too_long_mark = "..."
55-
cmd_max_len = 1000
56-
length = 0
57-
out = []
58-
for arg in args:
59-
cmd = str(arg)
51+
def _format_command_args(args, sanitize_query):
52+
"""Format and sanitize command arguments, and trim them as needed"""
53+
if sanitize_query:
54+
# Sanitized query format: "COMMAND ? ?"
55+
out = [str(args[0])] + ["?"] * (len(args) - 1)
56+
else:
57+
value_max_len = 100
58+
value_too_long_mark = "..."
59+
cmd_max_len = 1000
60+
length = 0
61+
out = []
62+
for arg in args:
63+
cmd = str(arg)
6064

61-
if len(cmd) > value_max_len:
62-
cmd = cmd[:value_max_len] + value_too_long_mark
65+
if len(cmd) > value_max_len:
66+
cmd = cmd[:value_max_len] + value_too_long_mark
6367

64-
if length + len(cmd) > cmd_max_len:
65-
prefix = cmd[: cmd_max_len - length]
66-
out.append(f"{prefix}{value_too_long_mark}")
67-
break
68+
if length + len(cmd) > cmd_max_len:
69+
prefix = cmd[: cmd_max_len - length]
70+
out.append(f"{prefix}{value_too_long_mark}")
71+
break
6872

69-
out.append(cmd)
70-
length += len(cmd)
73+
out.append(cmd)
74+
length += len(cmd)
7175

7276
return " ".join(out)

Diff for: instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py

+39
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,45 @@ def request_hook(span, conn, args, kwargs):
148148
span = spans[0]
149149
self.assertEqual(span.attributes.get(custom_attribute_name), "GET")
150150

151+
def test_query_sanitizer_enabled(self):
152+
redis_client = redis.Redis()
153+
connection = redis.connection.Connection()
154+
redis_client.connection = connection
155+
156+
RedisInstrumentor().uninstrument()
157+
RedisInstrumentor().instrument(
158+
tracer_provider=self.tracer_provider
159+
)
160+
161+
with mock.patch.object(redis_client, "connection"):
162+
redis_client.set("key", "value")
163+
164+
spans = self.memory_exporter.get_finished_spans()
165+
self.assertEqual(len(spans), 1)
166+
167+
span = spans[0]
168+
self.assertEqual(span.attributes.get("db.statement"), "SET ? ?")
169+
170+
def test_query_sanitizer_disabled(self):
171+
redis_client = redis.Redis()
172+
connection = redis.connection.Connection()
173+
redis_client.connection = connection
174+
175+
RedisInstrumentor().uninstrument()
176+
RedisInstrumentor().instrument(
177+
tracer_provider=self.tracer_provider,
178+
sanitize_query=False,
179+
)
180+
181+
with mock.patch.object(redis_client, "connection"):
182+
redis_client.set("key", "value")
183+
184+
spans = self.memory_exporter.get_finished_spans()
185+
self.assertEqual(len(spans), 1)
186+
187+
span = spans[0]
188+
self.assertEqual(span.attributes.get("db.statement"), "SET key value")
189+
151190
def test_no_op_tracer_provider(self):
152191
RedisInstrumentor().uninstrument()
153192
tracer_provider = trace.NoOpTracerProvider

0 commit comments

Comments
 (0)