Skip to content

Commit 89926cc

Browse files
feat(pymemcached): add DD_TRACE_MEMCACHED_COMMAND_ENABLED (#7092)
This adds environment variable `DD_TRACE_MEMCACHED_COMMAND_ENABLED` to configure collection of memcached commands from pymemcache integration. By default, this is disabled. For those that require it and for whom the contents of keys are not an issue, they can retain the existing behavior by setting `DD_TRACE_MEMCACHED_COMMAND_ENABLED=true`. ## Motivation According to the [memcached protocol](https://github.com/memcached/memcached/blob/master/doc/protocol.txt), a client sends messages to the server with commands along with several arguments, one of which can be the key which uniquely identifies the data stored and retrieved by a client. These keys can have sensitive information that uniquely identifies a client's data. ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) - [x] If this PR touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. - [x] This PR doesn't touch any of that. --------- Co-authored-by: Emmett Butler <[email protected]>
1 parent 974f580 commit 89926cc

File tree

5 files changed

+74
-3
lines changed

5 files changed

+74
-3
lines changed

ddtrace/contrib/pymemcache/client.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import sys
23
from typing import Iterable
34

@@ -28,12 +29,21 @@
2829
from ...internal.compat import reraise
2930
from ...internal.logger import get_logger
3031
from ...internal.schema import schematize_cache_operation
32+
from ...internal.utils.formats import asbool
3133
from ...pin import Pin
3234

3335

3436
log = get_logger(__name__)
3537

3638

39+
config._add(
40+
"pymemcache",
41+
{
42+
"command_enabled": asbool(os.getenv("DD_TRACE_MEMCACHED_COMMAND_ENABLED", default=False)),
43+
},
44+
)
45+
46+
3747
# keep a reference to the original unpatched clients
3848
_Client = Client
3949
_HashClient = HashClient
@@ -317,9 +327,10 @@ def _trace(func, p, method_name, *args, **kwargs):
317327
# with the application
318328
try:
319329
span.set_tags(p.tags)
320-
vals = _get_query_string(args)
321-
query = "{}{}{}".format(method_name, " " if vals else "", vals)
322-
span.set_tag_str(memcachedx.QUERY, query)
330+
if config.pymemcache.command_enabled:
331+
vals = _get_query_string(args)
332+
query = "{}{}{}".format(method_name, " " if vals else "", vals)
333+
span.set_tag_str(memcachedx.QUERY, query)
323334
except Exception:
324335
log.debug("Error setting relevant pymemcache tags")
325336

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
features:
3+
- |
4+
pymemcache: add ``DD_TRACE_MEMCACHED_COMMAND_ENABLED`` environment variable for configuring the collection of memcached commands. This feature is disabled by default.
5+
upgrade:
6+
- |
7+
pymemcache: The ``memcached.query`` span tag will no longer be set by the integration. This command includes keys that can potentially contain sensitive information. If you require this span tag, you can retain the existing functionality by setting ``DD_TRACE_MEMCACHED_COMMAND_ENABLED=true``. This span tag can be redacted using ``DD_APM_REPLACE_TAGS`` in your Agent configuration.

tests/contrib/pymemcache/test_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME
1818
from tests.utils import DummyTracer
1919
from tests.utils import TracerTestCase
20+
from tests.utils import override_config
2021

2122
from .test_client_mixin import PYMEMCACHE_VERSION
2223
from .test_client_mixin import PymemcacheClientTestCaseMixin
@@ -29,6 +30,13 @@
2930
_Client = pymemcache.client.base.Client
3031

3132

33+
# Manually configure pymemcached to collect command
34+
@pytest.fixture(scope="module", autouse=True)
35+
def command_enabled():
36+
with override_config("pymemcache", dict(command_enabled=True)):
37+
yield
38+
39+
3240
class PymemcacheClientTestCase(PymemcacheClientTestCaseMixin):
3341
"""Tests for a patched pymemcache.client.base.Client."""
3442

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# 3p
2+
import pymemcache
3+
import pytest
4+
5+
# project
6+
from ddtrace import Pin
7+
from ddtrace.contrib.pymemcache.patch import patch
8+
from ddtrace.contrib.pymemcache.patch import unpatch
9+
from tests.utils import override_config
10+
11+
from .test_client_mixin import TEST_HOST
12+
from .test_client_mixin import TEST_PORT
13+
from .utils import MockSocket
14+
15+
16+
@pytest.fixture()
17+
def client(tracer):
18+
try:
19+
patch()
20+
Pin.override(pymemcache, tracer=tracer)
21+
with override_config("pymemcache", dict(command_enabled=False)):
22+
client = pymemcache.client.base.Client((TEST_HOST, TEST_PORT))
23+
yield client
24+
finally:
25+
unpatch()
26+
27+
28+
def test_query_default(client, tracer):
29+
client.sock = MockSocket([b"STORED\r\n"])
30+
result = client.set(b"key", b"value", noreply=False)
31+
assert result is True
32+
33+
traces = tracer.pop_traces()
34+
assert 1 == len(traces)
35+
assert 1 == len(traces[0])
36+
assert traces[0][0].get_tag("memcached.query") is None

tests/contrib/pymemcache/test_client_mixin.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# 3p
22
import pymemcache
3+
import pytest
34

45
# project
56
from ddtrace import Pin
@@ -10,6 +11,7 @@
1011
from ddtrace.ext import net
1112
from tests.utils import DummyTracer
1213
from tests.utils import TracerTestCase
14+
from tests.utils import override_config
1315

1416
from .utils import MockSocket
1517

@@ -22,6 +24,13 @@
2224
PYMEMCACHE_VERSION = tuple(int(_) for _ in pymemcache.__version__.split("."))
2325

2426

27+
# Manually configure pymemcached to collect command
28+
@pytest.fixture(scope="module", autouse=True)
29+
def command_enabled():
30+
with override_config("pymemcache", dict(command_enabled=True)):
31+
yield
32+
33+
2534
class PymemcacheClientTestCaseMixin(TracerTestCase):
2635
"""Tests for a patched pymemcache.client.base.Client."""
2736

0 commit comments

Comments
 (0)