From 17a5df1e1716d5d1c0d666adb1f7b8ae6ec970cf Mon Sep 17 00:00:00 2001 From: Surbhi Dharvan Date: Thu, 25 Jun 2026 13:58:24 +0530 Subject: [PATCH 1/7] opentelemetry-instrumentation-pymemcache: add semconv stability migration support Signed-off-by: Surbhi Dharvan --- .../instrumentation/pymemcache/__init__.py | 73 ++++++++++++----- .../instrumentation/pymemcache/package.py | 1 + .../tests/test_pymemcache.py | 78 +++++++++++++++++++ 3 files changed, 134 insertions(+), 18 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py index e748475dc0..3c695e358a 100644 --- a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py @@ -32,6 +32,13 @@ import pymemcache from wrapt import wrap_function_wrapper as _wrap +from opentelemetry.instrumentation._semconv import ( + _get_schema_url_for_signal_types, + _OpenTelemetrySemanticConventionStability, + _OpenTelemetryStabilitySignalType, + _report_new, + _report_old, +) from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.pymemcache.package import _instruments from opentelemetry.instrumentation.pymemcache.version import __version__ @@ -46,6 +53,14 @@ NET_TRANSPORT, NetTransportValues, ) +from opentelemetry.semconv.attributes.db_attributes import ( + DB_QUERY_TEXT, + DB_SYSTEM_NAME, +) +from opentelemetry.semconv.attributes.server_attributes import ( + SERVER_ADDRESS, + SERVER_PORT, +) from opentelemetry.trace import SpanKind, get_tracer logger = logging.getLogger(__name__) @@ -77,23 +92,25 @@ ] -def _set_connection_attributes(span, instance): +def _set_connection_attributes(span, instance, sem_conv_opt_in_mode): if not span.is_recording(): return - for key, value in _get_address_attributes(instance).items(): + for key, value in _get_address_attributes( + instance, sem_conv_opt_in_mode + ).items(): span.set_attribute(key, value) def _with_tracer_wrapper(func): """Helper for providing tracer for wrapper functions.""" - def _with_tracer(tracer, cmd): + def _with_tracer(tracer, cmd, sem_conv_opt_in_mode): def wrapper(wrapped, instance, args, kwargs): # prevent double wrapping if hasattr(wrapped, "__wrapped__"): return wrapped(*args, **kwargs) - return func(tracer, cmd, wrapped, instance, args, kwargs) + return func(tracer, cmd, sem_conv_opt_in_mode, wrapped, instance, args, kwargs) return wrapper @@ -101,7 +118,7 @@ def wrapper(wrapped, instance, args, kwargs): @_with_tracer_wrapper -def _wrap_cmd(tracer, cmd, wrapped, instance, args, kwargs): +def _wrap_cmd(tracer, cmd, sem_conv_opt_in_mode, wrapped, instance, args, kwargs): with tracer.start_as_current_span( cmd, kind=SpanKind.CLIENT, attributes={} ) as span: @@ -113,9 +130,13 @@ def _wrap_cmd(tracer, cmd, wrapped, instance, args, kwargs): vals = _get_query_string(args[0]) query = f"{cmd}{' ' if vals else ''}{vals}" - span.set_attribute(DB_STATEMENT, query) - _set_connection_attributes(span, instance) + if _report_old(sem_conv_opt_in_mode): + span.set_attribute(DB_STATEMENT, query) + if _report_new(sem_conv_opt_in_mode): + span.set_attribute(DB_QUERY_TEXT, query) + + _set_connection_attributes(span, instance, sem_conv_opt_in_mode) except Exception as ex: # pylint: disable=broad-except logger.warning( "Failed to set attributes for pymemcache span %s", str(ex) @@ -148,22 +169,31 @@ def _get_query_string(arg): return keys -def _get_address_attributes(instance): +def _get_address_attributes(instance, sem_conv_opt_in_mode): """Attempt to get host and port from Client instance.""" address_attributes = {} - address_attributes[DB_SYSTEM] = "memcached" - # client.base.Client contains server attribute which is either a host/port tuple, or unix socket path string - # https://github.com/pinterest/pymemcache/blob/f02ddf73a28c09256589b8afbb3ee50f1171cac7/pymemcache/client/base.py#L228 + if _report_old(sem_conv_opt_in_mode): + address_attributes[DB_SYSTEM] = "memcached" + if _report_new(sem_conv_opt_in_mode): + address_attributes[DB_SYSTEM_NAME] = "memcached" + if hasattr(instance, "server"): if isinstance(instance.server, tuple): host, port = instance.server - address_attributes[NET_PEER_NAME] = host - address_attributes[NET_PEER_PORT] = port - address_attributes[NET_TRANSPORT] = NetTransportValues.IP_TCP.value + if _report_old(sem_conv_opt_in_mode): + address_attributes[NET_PEER_NAME] = host + address_attributes[NET_PEER_PORT] = port + address_attributes[NET_TRANSPORT] = NetTransportValues.IP_TCP.value + if _report_new(sem_conv_opt_in_mode): + address_attributes[SERVER_ADDRESS] = host + address_attributes[SERVER_PORT] = port elif isinstance(instance.server, str): - address_attributes[NET_PEER_NAME] = instance.server - address_attributes[NET_TRANSPORT] = NetTransportValues.OTHER.value + if _report_old(sem_conv_opt_in_mode): + address_attributes[NET_PEER_NAME] = instance.server + address_attributes[NET_TRANSPORT] = NetTransportValues.OTHER.value + if _report_new(sem_conv_opt_in_mode): + address_attributes[SERVER_ADDRESS] = instance.server return address_attributes @@ -175,19 +205,26 @@ def instrumentation_dependencies(self) -> Collection[str]: return _instruments def _instrument(self, **kwargs): + _OpenTelemetrySemanticConventionStability._initialize() + sem_conv_opt_in_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode( + _OpenTelemetryStabilitySignalType.DATABASE, + ) + tracer_provider = kwargs.get("tracer_provider") tracer = get_tracer( __name__, __version__, tracer_provider, - schema_url="https://opentelemetry.io/schemas/1.11.0", + schema_url=_get_schema_url_for_signal_types( + [_OpenTelemetryStabilitySignalType.DATABASE] + ), ) for cmd in COMMANDS: _wrap( "pymemcache.client.base", f"Client.{cmd}", - _wrap_cmd(tracer, cmd), + _wrap_cmd(tracer, cmd, sem_conv_opt_in_mode), ) def _uninstrument(self, **kwargs): diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/package.py b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/package.py index f931558a26..78eb2b32ac 100644 --- a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/package.py +++ b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/package.py @@ -3,3 +3,4 @@ _instruments = ("pymemcache >= 1.3.5, < 5",) +_semconv_status = "migration" diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py b/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py index 92fd117d6b..8e7b3e024a 100644 --- a/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py +++ b/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py @@ -20,10 +20,21 @@ DB_STATEMENT, DB_SYSTEM, ) +from opentelemetry.instrumentation._semconv import ( + _OpenTelemetrySemanticConventionStability, +) from opentelemetry.semconv._incubating.attributes.net_attributes import ( NET_PEER_NAME, NET_PEER_PORT, ) +from opentelemetry.semconv.attributes.db_attributes import ( + DB_QUERY_TEXT, + DB_SYSTEM_NAME, +) +from opentelemetry.semconv.attributes.server_attributes import ( + SERVER_ADDRESS, + SERVER_PORT, +) from opentelemetry.test.test_base import TestBase from opentelemetry.trace import get_tracer @@ -474,6 +485,73 @@ def test_stats(self): spans = self.memory_exporter.get_finished_spans() self.check_spans(spans, 1, ["stats"]) + + def test_new_semconv(self): + PymemcacheInstrumentor().uninstrument() + with mock.patch.dict( + "os.environ", + {"OTEL_SEMCONV_STABILITY_OPT_IN": "database"}, + ): + _OpenTelemetrySemanticConventionStability._initialized = False + PymemcacheInstrumentor().instrument() + client = self.make_client([b"STORED\r\n"]) + client.set(b"key", b"value", noreply=False) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.attributes[DB_SYSTEM_NAME], "memcached") + self.assertEqual(span.attributes[DB_QUERY_TEXT], "set key") + self.assertEqual(span.attributes[SERVER_ADDRESS], TEST_HOST) + self.assertEqual(span.attributes[SERVER_PORT], TEST_PORT) + self.assertNotIn(DB_SYSTEM, span.attributes) + self.assertNotIn(DB_STATEMENT, span.attributes) + self.assertNotIn(NET_PEER_NAME, span.attributes) + self.assertNotIn(NET_PEER_PORT, span.attributes) + _OpenTelemetrySemanticConventionStability._initialized = False + + def test_dup_semconv(self): + PymemcacheInstrumentor().uninstrument() + with mock.patch.dict( + "os.environ", + {"OTEL_SEMCONV_STABILITY_OPT_IN": "database/dup"}, + ): + _OpenTelemetrySemanticConventionStability._initialized = False + PymemcacheInstrumentor().instrument() + client = self.make_client([b"STORED\r\n"]) + client.set(b"key", b"value", noreply=False) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.attributes[DB_SYSTEM_NAME], "memcached") + self.assertEqual(span.attributes[DB_QUERY_TEXT], "set key") + self.assertEqual(span.attributes[SERVER_ADDRESS], TEST_HOST) + self.assertEqual(span.attributes[SERVER_PORT], TEST_PORT) + self.assertEqual(span.attributes[DB_SYSTEM], "memcached") + self.assertEqual(span.attributes[DB_STATEMENT], "set key") + self.assertEqual(span.attributes[NET_PEER_NAME], TEST_HOST) + self.assertEqual(span.attributes[NET_PEER_PORT], TEST_PORT) + _OpenTelemetrySemanticConventionStability._initialized = False + + def test_default_semconv(self): + client = self.make_client([b"STORED\r\n"]) + client.set(b"key", b"value", noreply=False) + + spans = self.memory_exporter.get_finished_spans() + self.assertEqual(len(spans), 1) + span = spans[0] + + self.assertEqual(span.attributes[DB_SYSTEM], "memcached") + self.assertEqual(span.attributes[DB_STATEMENT], "set key") + self.assertEqual(span.attributes[NET_PEER_NAME], TEST_HOST) + self.assertEqual(span.attributes[NET_PEER_PORT], TEST_PORT) + self.assertNotIn(DB_SYSTEM_NAME, span.attributes) + self.assertNotIn(DB_QUERY_TEXT, span.attributes) + self.assertNotIn(SERVER_ADDRESS, span.attributes) + self.assertNotIn(SERVER_PORT, span.attributes) def test_uninstrumented(self): PymemcacheInstrumentor().uninstrument() From aabe68266a87017cb4215e3750b449b339d00d3d Mon Sep 17 00:00:00 2001 From: Surbhi Dharvan Date: Thu, 25 Jun 2026 14:37:28 +0530 Subject: [PATCH 2/7] add changelog for pymemcache semconv migration Signed-off-by: Surbhi Dharvan --- .changelog/4739.added | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/4739.added diff --git a/.changelog/4739.added b/.changelog/4739.added new file mode 100644 index 0000000000..1b213def18 --- /dev/null +++ b/.changelog/4739.added @@ -0,0 +1 @@ +opentelemetry-instrumentation-pymemcache: add semconv stability migration support From 4c28728a9bd1659071a61eee723a3c280c03c957 Mon Sep 17 00:00:00 2001 From: Surbhi Dharvan Date: Sat, 27 Jun 2026 17:17:57 +0530 Subject: [PATCH 3/7] fix(pymemcache): use _semconv helper functions for attribute assignment Signed-off-by: Surbhi Dharvan --- instrumentation/README.md | 2 +- .../instrumentation/pymemcache/__init__.py | 53 ++++++------------- 2 files changed, 18 insertions(+), 37 deletions(-) diff --git a/instrumentation/README.md b/instrumentation/README.md index 823b1929b2..6466bde4dc 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -33,7 +33,7 @@ | [opentelemetry-instrumentation-pika](./opentelemetry-instrumentation-pika) | pika >= 0.12.0 | No | development | [opentelemetry-instrumentation-psycopg](./opentelemetry-instrumentation-psycopg) | psycopg >= 3.1.0 | No | migration | [opentelemetry-instrumentation-psycopg2](./opentelemetry-instrumentation-psycopg2) | psycopg2 >= 2.7.3.1,psycopg2-binary >= 2.7.3.1 | No | migration -| [opentelemetry-instrumentation-pymemcache](./opentelemetry-instrumentation-pymemcache) | pymemcache >= 1.3.5, < 5 | No | development +| [opentelemetry-instrumentation-pymemcache](./opentelemetry-instrumentation-pymemcache) | pymemcache >= 1.3.5, < 5 | No | migration | [opentelemetry-instrumentation-pymongo](./opentelemetry-instrumentation-pymongo) | pymongo >= 3.1, < 5.0 | No | development | [opentelemetry-instrumentation-pymssql](./opentelemetry-instrumentation-pymssql) | pymssql >= 2.1.5, < 3 | No | migration | [opentelemetry-instrumentation-pymysql](./opentelemetry-instrumentation-pymysql) | PyMySQL < 2 | No | migration diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py index 3c695e358a..20a04e0af1 100644 --- a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py @@ -36,30 +36,21 @@ _get_schema_url_for_signal_types, _OpenTelemetrySemanticConventionStability, _OpenTelemetryStabilitySignalType, - _report_new, - _report_old, + _set_db_statement, + _set_db_system, + _set_http_net_peer_name_client, + _set_http_peer_port_client, + _set_net_transport, ) from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.pymemcache.package import _instruments from opentelemetry.instrumentation.pymemcache.version import __version__ from opentelemetry.instrumentation.utils import unwrap -from opentelemetry.semconv._incubating.attributes.db_attributes import ( - DB_STATEMENT, - DB_SYSTEM, -) from opentelemetry.semconv._incubating.attributes.net_attributes import ( - NET_PEER_NAME, - NET_PEER_PORT, - NET_TRANSPORT, NetTransportValues, ) -from opentelemetry.semconv.attributes.db_attributes import ( - DB_QUERY_TEXT, - DB_SYSTEM_NAME, -) -from opentelemetry.semconv.attributes.server_attributes import ( - SERVER_ADDRESS, - SERVER_PORT, +from opentelemetry.semconv.attributes.network_attributes import ( + NetworkTransportValues, ) from opentelemetry.trace import SpanKind, get_tracer @@ -131,10 +122,10 @@ def _wrap_cmd(tracer, cmd, sem_conv_opt_in_mode, wrapped, instance, args, kwargs query = f"{cmd}{' ' if vals else ''}{vals}" - if _report_old(sem_conv_opt_in_mode): - span.set_attribute(DB_STATEMENT, query) - if _report_new(sem_conv_opt_in_mode): - span.set_attribute(DB_QUERY_TEXT, query) + stmt_attrs = {} + _set_db_statement(stmt_attrs, query, sem_conv_opt_in_mode) + for k, v in stmt_attrs.items(): + span.set_attribute(k, v) _set_connection_attributes(span, instance, sem_conv_opt_in_mode) except Exception as ex: # pylint: disable=broad-except @@ -173,27 +164,17 @@ def _get_address_attributes(instance, sem_conv_opt_in_mode): """Attempt to get host and port from Client instance.""" address_attributes = {} - if _report_old(sem_conv_opt_in_mode): - address_attributes[DB_SYSTEM] = "memcached" - if _report_new(sem_conv_opt_in_mode): - address_attributes[DB_SYSTEM_NAME] = "memcached" + _set_db_system(address_attributes, "memcached", sem_conv_opt_in_mode) if hasattr(instance, "server"): if isinstance(instance.server, tuple): host, port = instance.server - if _report_old(sem_conv_opt_in_mode): - address_attributes[NET_PEER_NAME] = host - address_attributes[NET_PEER_PORT] = port - address_attributes[NET_TRANSPORT] = NetTransportValues.IP_TCP.value - if _report_new(sem_conv_opt_in_mode): - address_attributes[SERVER_ADDRESS] = host - address_attributes[SERVER_PORT] = port + _set_http_net_peer_name_client(address_attributes, host, sem_conv_opt_in_mode) + _set_http_peer_port_client(address_attributes, port, sem_conv_opt_in_mode) + _set_net_transport(address_attributes, NetTransportValues.IP_TCP.value, NetworkTransportValues.TCP.value, sem_conv_opt_in_mode) elif isinstance(instance.server, str): - if _report_old(sem_conv_opt_in_mode): - address_attributes[NET_PEER_NAME] = instance.server - address_attributes[NET_TRANSPORT] = NetTransportValues.OTHER.value - if _report_new(sem_conv_opt_in_mode): - address_attributes[SERVER_ADDRESS] = instance.server + _set_http_net_peer_name_client(address_attributes, instance.server, sem_conv_opt_in_mode) + _set_net_transport(address_attributes, NetTransportValues.OTHER.value, NetworkTransportValues.PIPE.value, sem_conv_opt_in_mode) return address_attributes From 71dea1f2f49a913805b85556d676a4cc667dcec9 Mon Sep 17 00:00:00 2001 From: Surbhi Dharvan Date: Wed, 1 Jul 2026 10:38:19 +0530 Subject: [PATCH 4/7] fix(pymemcache): fix linting issues Signed-off-by: Surbhi Dharvan --- .../instrumentation/pymemcache/__init__.py | 44 +++++++++++++++---- .../tests/test_pymemcache.py | 14 +++--- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py index 20a04e0af1..90d7dd3e99 100644 --- a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py @@ -101,7 +101,15 @@ def wrapper(wrapped, instance, args, kwargs): if hasattr(wrapped, "__wrapped__"): return wrapped(*args, **kwargs) - return func(tracer, cmd, sem_conv_opt_in_mode, wrapped, instance, args, kwargs) + return func( + tracer, + cmd, + sem_conv_opt_in_mode, + wrapped, + instance, + args, + kwargs, + ) return wrapper @@ -109,7 +117,9 @@ def wrapper(wrapped, instance, args, kwargs): @_with_tracer_wrapper -def _wrap_cmd(tracer, cmd, sem_conv_opt_in_mode, wrapped, instance, args, kwargs): +def _wrap_cmd( + tracer, cmd, sem_conv_opt_in_mode, wrapped, instance, args, kwargs +): with tracer.start_as_current_span( cmd, kind=SpanKind.CLIENT, attributes={} ) as span: @@ -127,7 +137,9 @@ def _wrap_cmd(tracer, cmd, sem_conv_opt_in_mode, wrapped, instance, args, kwargs for k, v in stmt_attrs.items(): span.set_attribute(k, v) - _set_connection_attributes(span, instance, sem_conv_opt_in_mode) + _set_connection_attributes( + span, instance, sem_conv_opt_in_mode + ) except Exception as ex: # pylint: disable=broad-except logger.warning( "Failed to set attributes for pymemcache span %s", str(ex) @@ -169,12 +181,28 @@ def _get_address_attributes(instance, sem_conv_opt_in_mode): if hasattr(instance, "server"): if isinstance(instance.server, tuple): host, port = instance.server - _set_http_net_peer_name_client(address_attributes, host, sem_conv_opt_in_mode) - _set_http_peer_port_client(address_attributes, port, sem_conv_opt_in_mode) - _set_net_transport(address_attributes, NetTransportValues.IP_TCP.value, NetworkTransportValues.TCP.value, sem_conv_opt_in_mode) + _set_http_net_peer_name_client( + address_attributes, host, sem_conv_opt_in_mode + ) + _set_http_peer_port_client( + address_attributes, port, sem_conv_opt_in_mode + ) + _set_net_transport( + address_attributes, + NetTransportValues.IP_TCP.value, + NetworkTransportValues.TCP.value, + sem_conv_opt_in_mode, + ) elif isinstance(instance.server, str): - _set_http_net_peer_name_client(address_attributes, instance.server, sem_conv_opt_in_mode) - _set_net_transport(address_attributes, NetTransportValues.OTHER.value, NetworkTransportValues.PIPE.value, sem_conv_opt_in_mode) + _set_http_net_peer_name_client( + address_attributes, instance.server, sem_conv_opt_in_mode + ) + _set_net_transport( + address_attributes, + NetTransportValues.OTHER.value, + NetworkTransportValues.PIPE.value, + sem_conv_opt_in_mode, + ) return address_attributes diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py b/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py index 8e7b3e024a..a47c0328b3 100644 --- a/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py +++ b/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py @@ -15,14 +15,14 @@ ) from opentelemetry import trace as trace_api +from opentelemetry.instrumentation._semconv import ( + _OpenTelemetrySemanticConventionStability, +) from opentelemetry.instrumentation.pymemcache import PymemcacheInstrumentor from opentelemetry.semconv._incubating.attributes.db_attributes import ( DB_STATEMENT, DB_SYSTEM, ) -from opentelemetry.instrumentation._semconv import ( - _OpenTelemetrySemanticConventionStability, -) from opentelemetry.semconv._incubating.attributes.net_attributes import ( NET_PEER_NAME, NET_PEER_PORT, @@ -485,7 +485,7 @@ def test_stats(self): spans = self.memory_exporter.get_finished_spans() self.check_spans(spans, 1, ["stats"]) - + def test_new_semconv(self): PymemcacheInstrumentor().uninstrument() with mock.patch.dict( @@ -509,7 +509,7 @@ def test_new_semconv(self): self.assertNotIn(DB_STATEMENT, span.attributes) self.assertNotIn(NET_PEER_NAME, span.attributes) self.assertNotIn(NET_PEER_PORT, span.attributes) - _OpenTelemetrySemanticConventionStability._initialized = False + _OpenTelemetrySemanticConventionStability._initialized = False def test_dup_semconv(self): PymemcacheInstrumentor().uninstrument() @@ -534,7 +534,7 @@ def test_dup_semconv(self): self.assertEqual(span.attributes[DB_STATEMENT], "set key") self.assertEqual(span.attributes[NET_PEER_NAME], TEST_HOST) self.assertEqual(span.attributes[NET_PEER_PORT], TEST_PORT) - _OpenTelemetrySemanticConventionStability._initialized = False + _OpenTelemetrySemanticConventionStability._initialized = False def test_default_semconv(self): client = self.make_client([b"STORED\r\n"]) @@ -551,7 +551,7 @@ def test_default_semconv(self): self.assertNotIn(DB_SYSTEM_NAME, span.attributes) self.assertNotIn(DB_QUERY_TEXT, span.attributes) self.assertNotIn(SERVER_ADDRESS, span.attributes) - self.assertNotIn(SERVER_PORT, span.attributes) + self.assertNotIn(SERVER_PORT, span.attributes) def test_uninstrumented(self): PymemcacheInstrumentor().uninstrument() From ac4dbc6022f2930ce396f1ecf2c55d46f4798f84 Mon Sep 17 00:00:00 2001 From: SurbhiD404 Date: Thu, 2 Jul 2026 12:06:27 +0530 Subject: [PATCH 5/7] Update .changelog/4739.added Co-authored-by: Riccardo Magliocchetti --- .changelog/4739.added | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/4739.added b/.changelog/4739.added index 1b213def18..c3e7255a45 100644 --- a/.changelog/4739.added +++ b/.changelog/4739.added @@ -1 +1 @@ -opentelemetry-instrumentation-pymemcache: add semconv stability migration support +`opentelemetry-instrumentation-pymemcache`: add database semconv stability migration support From 04cfe12d5b34a27463e487e3e0d6df06867a3517 Mon Sep 17 00:00:00 2001 From: Surbhi Dharvan Date: Thu, 2 Jul 2026 12:47:25 +0530 Subject: [PATCH 6/7] fix(pymemcache): drop _set_connection_attributes wrapper and restore comment Signed-off-by: Surbhi Dharvan --- .../instrumentation/pymemcache/__init__.py | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py index 90d7dd3e99..c433c88e73 100644 --- a/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-pymemcache/src/opentelemetry/instrumentation/pymemcache/__init__.py @@ -83,15 +83,6 @@ ] -def _set_connection_attributes(span, instance, sem_conv_opt_in_mode): - if not span.is_recording(): - return - for key, value in _get_address_attributes( - instance, sem_conv_opt_in_mode - ).items(): - span.set_attribute(key, value) - - def _with_tracer_wrapper(func): """Helper for providing tracer for wrapper functions.""" @@ -132,14 +123,9 @@ def _wrap_cmd( query = f"{cmd}{' ' if vals else ''}{vals}" - stmt_attrs = {} - _set_db_statement(stmt_attrs, query, sem_conv_opt_in_mode) - for k, v in stmt_attrs.items(): - span.set_attribute(k, v) - - _set_connection_attributes( - span, instance, sem_conv_opt_in_mode - ) + attrs = _get_address_attributes(instance, sem_conv_opt_in_mode) + _set_db_statement(attrs, query, sem_conv_opt_in_mode) + span.set_attributes(attrs) except Exception as ex: # pylint: disable=broad-except logger.warning( "Failed to set attributes for pymemcache span %s", str(ex) @@ -175,9 +161,9 @@ def _get_query_string(arg): def _get_address_attributes(instance, sem_conv_opt_in_mode): """Attempt to get host and port from Client instance.""" address_attributes = {} - _set_db_system(address_attributes, "memcached", sem_conv_opt_in_mode) - + # client.base.Client contains server attribute which is either a host/port tuple, or unix socket path string + # https://github.com/pinterest/pymemcache/blob/f02ddf73a28c09256589b8afbb3ee50f1171cac7/pymemcache/client/base.py#L228 if hasattr(instance, "server"): if isinstance(instance.server, tuple): host, port = instance.server From c9154847d39db6af6abf43f7e54eee06d9976145 Mon Sep 17 00:00:00 2001 From: Surbhi Dharvan Date: Fri, 3 Jul 2026 07:52:44 +0530 Subject: [PATCH 7/7] fix(pymemcache): assert schema_url in semconv tests Signed-off-by: Surbhi Dharvan --- .../tests/test_pymemcache.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py b/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py index a47c0328b3..5f0feda89f 100644 --- a/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py +++ b/instrumentation/opentelemetry-instrumentation-pymemcache/tests/test_pymemcache.py @@ -509,6 +509,10 @@ def test_new_semconv(self): self.assertNotIn(DB_STATEMENT, span.attributes) self.assertNotIn(NET_PEER_NAME, span.attributes) self.assertNotIn(NET_PEER_PORT, span.attributes) + self.assertEqual( + span.instrumentation_scope.schema_url, + "https://opentelemetry.io/schemas/1.25.0", + ) _OpenTelemetrySemanticConventionStability._initialized = False def test_dup_semconv(self): @@ -534,6 +538,10 @@ def test_dup_semconv(self): self.assertEqual(span.attributes[DB_STATEMENT], "set key") self.assertEqual(span.attributes[NET_PEER_NAME], TEST_HOST) self.assertEqual(span.attributes[NET_PEER_PORT], TEST_PORT) + self.assertEqual( + span.instrumentation_scope.schema_url, + "https://opentelemetry.io/schemas/1.25.0", + ) _OpenTelemetrySemanticConventionStability._initialized = False def test_default_semconv(self): @@ -552,6 +560,10 @@ def test_default_semconv(self): self.assertNotIn(DB_QUERY_TEXT, span.attributes) self.assertNotIn(SERVER_ADDRESS, span.attributes) self.assertNotIn(SERVER_PORT, span.attributes) + self.assertEqual( + span.instrumentation_scope.schema_url, + "https://opentelemetry.io/schemas/1.11.0", + ) def test_uninstrumented(self): PymemcacheInstrumentor().uninstrument()