Skip to content

Commit 37d85f0

Browse files
nemoshlagsrikanthccvshalevr
authored
Sanitize redis db_statement by default (#1776)
Co-authored-by: Srikanth Chekuri <[email protected]> Co-authored-by: Shalev Roda <[email protected]>
1 parent 818ef43 commit 37d85f0

File tree

6 files changed

+36
-134
lines changed

6 files changed

+36
-134
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3838

3939
### Fixed
4040

41+
- Fix redis db.statements to be sanitized by default
42+
([#1778](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1778))
4143
- Fix elasticsearch db.statement attribute to be sanitized by default
4244
([#1758](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1758))
4345
- Fix `AttributeError` when AWS Lambda handler receives a list event

instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py

+2-33
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ 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 (Boolean) - default False, enable the Redis query sanitization
68-
6967
for example:
7068
7169
.. code: python
@@ -88,37 +86,18 @@ def response_hook(span, instance, response):
8886
client = redis.StrictRedis(host="localhost", port=6379)
8987
client.get("my-key")
9088
91-
Configuration
92-
-------------
93-
94-
Query sanitization
95-
******************
96-
To enable query sanitization with an environment variable, set
97-
``OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS`` to "true".
98-
99-
For example,
100-
101-
::
102-
103-
export OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS="true"
104-
105-
will result in traced queries like "SET ? ?".
10689
10790
API
10891
---
10992
"""
11093
import typing
111-
from os import environ
11294
from typing import Any, Collection
11395

11496
import redis
11597
from wrapt import wrap_function_wrapper
11698

11799
from opentelemetry import trace
118100
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
119-
from opentelemetry.instrumentation.redis.environment_variables import (
120-
OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS,
121-
)
122101
from opentelemetry.instrumentation.redis.package import _instruments
123102
from opentelemetry.instrumentation.redis.util import (
124103
_extract_conn_attributes,
@@ -161,10 +140,9 @@ def _instrument(
161140
tracer,
162141
request_hook: _RequestHookT = None,
163142
response_hook: _ResponseHookT = None,
164-
sanitize_query: bool = False,
165143
):
166144
def _traced_execute_command(func, instance, args, kwargs):
167-
query = _format_command_args(args, sanitize_query)
145+
query = _format_command_args(args)
168146

169147
if len(args) > 0 and args[0]:
170148
name = args[0]
@@ -194,7 +172,7 @@ def _traced_execute_pipeline(func, instance, args, kwargs):
194172

195173
cmds = [
196174
_format_command_args(
197-
c.args if hasattr(c, "args") else c[0], sanitize_query
175+
c.args if hasattr(c, "args") else c[0],
198176
)
199177
for c in command_stack
200178
]
@@ -307,15 +285,6 @@ def _instrument(self, **kwargs):
307285
tracer,
308286
request_hook=kwargs.get("request_hook"),
309287
response_hook=kwargs.get("response_hook"),
310-
sanitize_query=kwargs.get(
311-
"sanitize_query",
312-
environ.get(
313-
OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS, "false"
314-
)
315-
.lower()
316-
.strip()
317-
== "true",
318-
),
319288
)
320289

321290
def _uninstrument(self, **kwargs):

instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/environment_variables.py

-17
This file was deleted.

instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/util.py

+14-32
Original file line numberDiff line numberDiff line change
@@ -48,41 +48,23 @@ def _extract_conn_attributes(conn_kwargs):
4848
return attributes
4949

5050

51-
def _format_command_args(args, sanitize_query):
51+
def _format_command_args(args):
5252
"""Format and sanitize command arguments, and trim them as needed"""
5353
cmd_max_len = 1000
5454
value_too_long_mark = "..."
55-
if sanitize_query:
56-
# Sanitized query format: "COMMAND ? ?"
57-
args_length = len(args)
58-
if args_length > 0:
59-
out = [str(args[0])] + ["?"] * (args_length - 1)
60-
out_str = " ".join(out)
6155

62-
if len(out_str) > cmd_max_len:
63-
out_str = (
64-
out_str[: cmd_max_len - len(value_too_long_mark)]
65-
+ value_too_long_mark
66-
)
67-
else:
68-
out_str = ""
69-
return out_str
56+
# Sanitized query format: "COMMAND ? ?"
57+
args_length = len(args)
58+
if args_length > 0:
59+
out = [str(args[0])] + ["?"] * (args_length - 1)
60+
out_str = " ".join(out)
7061

71-
value_max_len = 100
72-
length = 0
73-
out = []
74-
for arg in args:
75-
cmd = str(arg)
62+
if len(out_str) > cmd_max_len:
63+
out_str = (
64+
out_str[: cmd_max_len - len(value_too_long_mark)]
65+
+ value_too_long_mark
66+
)
67+
else:
68+
out_str = ""
7669

77-
if len(cmd) > value_max_len:
78-
cmd = cmd[:value_max_len] + value_too_long_mark
79-
80-
if length + len(cmd) > cmd_max_len:
81-
prefix = cmd[: cmd_max_len - length]
82-
out.append(f"{prefix}{value_too_long_mark}")
83-
break
84-
85-
out.append(cmd)
86-
length += len(cmd)
87-
88-
return " ".join(out)
70+
return out_str

instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py

+1-27
Original file line numberDiff line numberDiff line change
@@ -168,22 +168,11 @@ def test_query_sanitizer_enabled(self):
168168
span = spans[0]
169169
self.assertEqual(span.attributes.get("db.statement"), "SET ? ?")
170170

171-
def test_query_sanitizer_enabled_env(self):
171+
def test_query_sanitizer(self):
172172
redis_client = redis.Redis()
173173
connection = redis.connection.Connection()
174174
redis_client.connection = connection
175175

176-
RedisInstrumentor().uninstrument()
177-
178-
env_patch = mock.patch.dict(
179-
"os.environ",
180-
{"OTEL_PYTHON_INSTRUMENTATION_SANITIZE_REDIS": "true"},
181-
)
182-
env_patch.start()
183-
RedisInstrumentor().instrument(
184-
tracer_provider=self.tracer_provider,
185-
)
186-
187176
with mock.patch.object(redis_client, "connection"):
188177
redis_client.set("key", "value")
189178

@@ -192,21 +181,6 @@ def test_query_sanitizer_enabled_env(self):
192181

193182
span = spans[0]
194183
self.assertEqual(span.attributes.get("db.statement"), "SET ? ?")
195-
env_patch.stop()
196-
197-
def test_query_sanitizer_disabled(self):
198-
redis_client = redis.Redis()
199-
connection = redis.connection.Connection()
200-
redis_client.connection = connection
201-
202-
with mock.patch.object(redis_client, "connection"):
203-
redis_client.set("key", "value")
204-
205-
spans = self.memory_exporter.get_finished_spans()
206-
self.assertEqual(len(spans), 1)
207-
208-
span = spans[0]
209-
self.assertEqual(span.attributes.get("db.statement"), "SET key value")
210184

211185
def test_no_op_tracer_provider(self):
212186
RedisInstrumentor().uninstrument()

tests/opentelemetry-docker-tests/tests/redis/test_redis_functional.py

+17-25
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,7 @@ def _check_span(self, span, name):
4747

4848
def test_long_command_sanitized(self):
4949
RedisInstrumentor().uninstrument()
50-
RedisInstrumentor().instrument(
51-
tracer_provider=self.tracer_provider, sanitize_query=True
52-
)
50+
RedisInstrumentor().instrument(tracer_provider=self.tracer_provider)
5351

5452
self.redis_client.mget(*range(2000))
5553

@@ -75,7 +73,7 @@ def test_long_command(self):
7573
self._check_span(span, "MGET")
7674
self.assertTrue(
7775
span.attributes.get(SpanAttributes.DB_STATEMENT).startswith(
78-
"MGET 0 1 2 3"
76+
"MGET ? ? ? ?"
7977
)
8078
)
8179
self.assertTrue(
@@ -84,9 +82,7 @@ def test_long_command(self):
8482

8583
def test_basics_sanitized(self):
8684
RedisInstrumentor().uninstrument()
87-
RedisInstrumentor().instrument(
88-
tracer_provider=self.tracer_provider, sanitize_query=True
89-
)
85+
RedisInstrumentor().instrument(tracer_provider=self.tracer_provider)
9086

9187
self.assertIsNone(self.redis_client.get("cheese"))
9288
spans = self.memory_exporter.get_finished_spans()
@@ -105,15 +101,13 @@ def test_basics(self):
105101
span = spans[0]
106102
self._check_span(span, "GET")
107103
self.assertEqual(
108-
span.attributes.get(SpanAttributes.DB_STATEMENT), "GET cheese"
104+
span.attributes.get(SpanAttributes.DB_STATEMENT), "GET ?"
109105
)
110106
self.assertEqual(span.attributes.get("db.redis.args_length"), 2)
111107

112108
def test_pipeline_traced_sanitized(self):
113109
RedisInstrumentor().uninstrument()
114-
RedisInstrumentor().instrument(
115-
tracer_provider=self.tracer_provider, sanitize_query=True
116-
)
110+
RedisInstrumentor().instrument(tracer_provider=self.tracer_provider)
117111

118112
with self.redis_client.pipeline(transaction=False) as pipeline:
119113
pipeline.set("blah", 32)
@@ -144,15 +138,13 @@ def test_pipeline_traced(self):
144138
self._check_span(span, "SET RPUSH HGETALL")
145139
self.assertEqual(
146140
span.attributes.get(SpanAttributes.DB_STATEMENT),
147-
"SET blah 32\nRPUSH foo éé\nHGETALL xxx",
141+
"SET ? ?\nRPUSH ? ?\nHGETALL ?",
148142
)
149143
self.assertEqual(span.attributes.get("db.redis.pipeline_length"), 3)
150144

151145
def test_pipeline_immediate_sanitized(self):
152146
RedisInstrumentor().uninstrument()
153-
RedisInstrumentor().instrument(
154-
tracer_provider=self.tracer_provider, sanitize_query=True
155-
)
147+
RedisInstrumentor().instrument(tracer_provider=self.tracer_provider)
156148

157149
with self.redis_client.pipeline() as pipeline:
158150
pipeline.set("a", 1)
@@ -182,7 +174,7 @@ def test_pipeline_immediate(self):
182174
span = spans[0]
183175
self._check_span(span, "SET")
184176
self.assertEqual(
185-
span.attributes.get(SpanAttributes.DB_STATEMENT), "SET b 2"
177+
span.attributes.get(SpanAttributes.DB_STATEMENT), "SET ? ?"
186178
)
187179

188180
def test_parent(self):
@@ -230,7 +222,7 @@ def test_basics(self):
230222
span = spans[0]
231223
self._check_span(span, "GET")
232224
self.assertEqual(
233-
span.attributes.get(SpanAttributes.DB_STATEMENT), "GET cheese"
225+
span.attributes.get(SpanAttributes.DB_STATEMENT), "GET ?"
234226
)
235227
self.assertEqual(span.attributes.get("db.redis.args_length"), 2)
236228

@@ -247,7 +239,7 @@ def test_pipeline_traced(self):
247239
self._check_span(span, "SET RPUSH HGETALL")
248240
self.assertEqual(
249241
span.attributes.get(SpanAttributes.DB_STATEMENT),
250-
"SET blah 32\nRPUSH foo éé\nHGETALL xxx",
242+
"SET ? ?\nRPUSH ? ?\nHGETALL ?",
251243
)
252244
self.assertEqual(span.attributes.get("db.redis.pipeline_length"), 3)
253245

@@ -308,7 +300,7 @@ def test_long_command(self):
308300
self._check_span(span, "MGET")
309301
self.assertTrue(
310302
span.attributes.get(SpanAttributes.DB_STATEMENT).startswith(
311-
"MGET 0 1 2 3"
303+
"MGET ? ? ? ?"
312304
)
313305
)
314306
self.assertTrue(
@@ -322,7 +314,7 @@ def test_basics(self):
322314
span = spans[0]
323315
self._check_span(span, "GET")
324316
self.assertEqual(
325-
span.attributes.get(SpanAttributes.DB_STATEMENT), "GET cheese"
317+
span.attributes.get(SpanAttributes.DB_STATEMENT), "GET ?"
326318
)
327319
self.assertEqual(span.attributes.get("db.redis.args_length"), 2)
328320

@@ -344,7 +336,7 @@ async def pipeline_simple():
344336
self._check_span(span, "SET RPUSH HGETALL")
345337
self.assertEqual(
346338
span.attributes.get(SpanAttributes.DB_STATEMENT),
347-
"SET blah 32\nRPUSH foo éé\nHGETALL xxx",
339+
"SET ? ?\nRPUSH ? ?\nHGETALL ?",
348340
)
349341
self.assertEqual(span.attributes.get("db.redis.pipeline_length"), 3)
350342

@@ -364,7 +356,7 @@ async def pipeline_immediate():
364356
span = spans[0]
365357
self._check_span(span, "SET")
366358
self.assertEqual(
367-
span.attributes.get(SpanAttributes.DB_STATEMENT), "SET b 2"
359+
span.attributes.get(SpanAttributes.DB_STATEMENT), "SET ? ?"
368360
)
369361

370362
def test_parent(self):
@@ -412,7 +404,7 @@ def test_basics(self):
412404
span = spans[0]
413405
self._check_span(span, "GET")
414406
self.assertEqual(
415-
span.attributes.get(SpanAttributes.DB_STATEMENT), "GET cheese"
407+
span.attributes.get(SpanAttributes.DB_STATEMENT), "GET ?"
416408
)
417409
self.assertEqual(span.attributes.get("db.redis.args_length"), 2)
418410

@@ -434,7 +426,7 @@ async def pipeline_simple():
434426
self._check_span(span, "SET RPUSH HGETALL")
435427
self.assertEqual(
436428
span.attributes.get(SpanAttributes.DB_STATEMENT),
437-
"SET blah 32\nRPUSH foo éé\nHGETALL xxx",
429+
"SET ? ?\nRPUSH ? ?\nHGETALL ?",
438430
)
439431
self.assertEqual(span.attributes.get("db.redis.pipeline_length"), 3)
440432

@@ -488,5 +480,5 @@ def test_get(self):
488480
span = spans[0]
489481
self._check_span(span, "GET")
490482
self.assertEqual(
491-
span.attributes.get(SpanAttributes.DB_STATEMENT), "GET foo"
483+
span.attributes.get(SpanAttributes.DB_STATEMENT), "GET ?"
492484
)

0 commit comments

Comments
 (0)