mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-01 17:34:38 +08:00
opentelemetry-instrumentation: add unwrapping from dotted paths strings (#2919)
This commit is contained in:

committed by
GitHub

parent
5145a07fd1
commit
39bd7fa79a
@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
([#2082](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2082))
|
([#2082](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2082))
|
||||||
- `opentelemetry-instrumentation-redis` Add additional attributes for methods create_index and search, rename those spans
|
- `opentelemetry-instrumentation-redis` Add additional attributes for methods create_index and search, rename those spans
|
||||||
([#2635](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2635))
|
([#2635](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2635))
|
||||||
|
- `opentelemetry-instrumentation` Add support for string based dotted module paths in unwrap
|
||||||
|
([#2919](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2919))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -14,8 +14,9 @@
|
|||||||
|
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from importlib import import_module
|
||||||
from re import escape, sub
|
from re import escape, sub
|
||||||
from typing import Dict, Iterable, Sequence
|
from typing import Dict, Iterable, Sequence, Union
|
||||||
|
|
||||||
from wrapt import ObjectProxy
|
from wrapt import ObjectProxy
|
||||||
|
|
||||||
@ -80,13 +81,30 @@ def http_status_to_status_code(
|
|||||||
return StatusCode.ERROR
|
return StatusCode.ERROR
|
||||||
|
|
||||||
|
|
||||||
def unwrap(obj, attr: str):
|
def unwrap(obj: Union[object, str], attr: str):
|
||||||
"""Given a function that was wrapped by wrapt.wrap_function_wrapper, unwrap it
|
"""Given a function that was wrapped by wrapt.wrap_function_wrapper, unwrap it
|
||||||
|
|
||||||
|
The object containing the function to unwrap may be passed as dotted module path string.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj: Object that holds a reference to the wrapped function
|
obj: Object that holds a reference to the wrapped function or dotted import path as string
|
||||||
attr (str): Name of the wrapped function
|
attr (str): Name of the wrapped function
|
||||||
"""
|
"""
|
||||||
|
if isinstance(obj, str):
|
||||||
|
try:
|
||||||
|
module_path, class_name = obj.rsplit(".", 1)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
f"Cannot parse '{obj}' as dotted import path"
|
||||||
|
) from exc
|
||||||
|
module = import_module(module_path)
|
||||||
|
try:
|
||||||
|
obj = getattr(module, class_name)
|
||||||
|
except AttributeError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
f"Cannot import '{class_name}' from '{module}'"
|
||||||
|
) from exc
|
||||||
|
|
||||||
func = getattr(obj, attr, None)
|
func = getattr(obj, attr, None)
|
||||||
if func and isinstance(func, ObjectProxy) and hasattr(func, "__wrapped__"):
|
if func and isinstance(func, ObjectProxy) and hasattr(func, "__wrapped__"):
|
||||||
setattr(obj, attr, func.__wrapped__)
|
setattr(obj, attr, func.__wrapped__)
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from wrapt import ObjectProxy, wrap_function_wrapper
|
||||||
|
|
||||||
from opentelemetry.context import (
|
from opentelemetry.context import (
|
||||||
_SUPPRESS_HTTP_INSTRUMENTATION_KEY,
|
_SUPPRESS_HTTP_INSTRUMENTATION_KEY,
|
||||||
_SUPPRESS_INSTRUMENTATION_KEY,
|
_SUPPRESS_INSTRUMENTATION_KEY,
|
||||||
@ -29,10 +31,19 @@ from opentelemetry.instrumentation.utils import (
|
|||||||
is_instrumentation_enabled,
|
is_instrumentation_enabled,
|
||||||
suppress_http_instrumentation,
|
suppress_http_instrumentation,
|
||||||
suppress_instrumentation,
|
suppress_instrumentation,
|
||||||
|
unwrap,
|
||||||
)
|
)
|
||||||
from opentelemetry.trace import StatusCode
|
from opentelemetry.trace import StatusCode
|
||||||
|
|
||||||
|
|
||||||
|
class WrappedClass:
|
||||||
|
def method(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def wrapper_method(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestUtils(unittest.TestCase):
|
class TestUtils(unittest.TestCase):
|
||||||
# See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status
|
# See https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status
|
||||||
def test_http_status_to_status_code(self):
|
def test_http_status_to_status_code(self):
|
||||||
@ -240,3 +251,75 @@ class TestUtils(unittest.TestCase):
|
|||||||
self.assertTrue(get_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY))
|
self.assertTrue(get_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY))
|
||||||
|
|
||||||
self.assertIsNone(get_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY))
|
self.assertIsNone(get_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY))
|
||||||
|
|
||||||
|
|
||||||
|
class UnwrapTestCase(unittest.TestCase):
|
||||||
|
@staticmethod
|
||||||
|
def _wrap_method():
|
||||||
|
return wrap_function_wrapper(
|
||||||
|
WrappedClass, "method", WrappedClass.wrapper_method
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_can_unwrap_object_attribute(self):
|
||||||
|
self._wrap_method()
|
||||||
|
instance = WrappedClass()
|
||||||
|
self.assertTrue(isinstance(instance.method, ObjectProxy))
|
||||||
|
|
||||||
|
unwrap(WrappedClass, "method")
|
||||||
|
self.assertFalse(isinstance(instance.method, ObjectProxy))
|
||||||
|
|
||||||
|
def test_can_unwrap_object_attribute_as_string(self):
|
||||||
|
self._wrap_method()
|
||||||
|
instance = WrappedClass()
|
||||||
|
self.assertTrue(isinstance(instance.method, ObjectProxy))
|
||||||
|
|
||||||
|
unwrap("tests.test_utils.WrappedClass", "method")
|
||||||
|
self.assertFalse(isinstance(instance.method, ObjectProxy))
|
||||||
|
|
||||||
|
def test_raises_import_error_if_path_not_well_formed(self):
|
||||||
|
self._wrap_method()
|
||||||
|
instance = WrappedClass()
|
||||||
|
self.assertTrue(isinstance(instance.method, ObjectProxy))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
ImportError, "Cannot parse '' as dotted import path"
|
||||||
|
):
|
||||||
|
unwrap("", "method")
|
||||||
|
|
||||||
|
unwrap(WrappedClass, "method")
|
||||||
|
self.assertFalse(isinstance(instance.method, ObjectProxy))
|
||||||
|
|
||||||
|
def test_raises_import_error_if_cannot_find_module(self):
|
||||||
|
self._wrap_method()
|
||||||
|
instance = WrappedClass()
|
||||||
|
self.assertTrue(isinstance(instance.method, ObjectProxy))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ImportError, "No module named 'does'"):
|
||||||
|
unwrap("does.not.exist.WrappedClass", "method")
|
||||||
|
|
||||||
|
unwrap(WrappedClass, "method")
|
||||||
|
self.assertFalse(isinstance(instance.method, ObjectProxy))
|
||||||
|
|
||||||
|
def test_raises_import_error_if_cannot_find_object(self):
|
||||||
|
self._wrap_method()
|
||||||
|
instance = WrappedClass()
|
||||||
|
self.assertTrue(isinstance(instance.method, ObjectProxy))
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
ImportError, "Cannot import 'NotWrappedClass' from"
|
||||||
|
):
|
||||||
|
unwrap("tests.test_utils.NotWrappedClass", "method")
|
||||||
|
|
||||||
|
unwrap(WrappedClass, "method")
|
||||||
|
self.assertFalse(isinstance(instance.method, ObjectProxy))
|
||||||
|
|
||||||
|
# pylint: disable=no-self-use
|
||||||
|
def test_does_nothing_if_cannot_find_attribute(self):
|
||||||
|
instance = WrappedClass()
|
||||||
|
unwrap(instance, "method_not_found")
|
||||||
|
|
||||||
|
def test_does_nothing_if_attribute_is_not_from_wrapt(self):
|
||||||
|
instance = WrappedClass()
|
||||||
|
self.assertFalse(isinstance(instance.method, ObjectProxy))
|
||||||
|
unwrap(WrappedClass, "method")
|
||||||
|
self.assertFalse(isinstance(instance.method, ObjectProxy))
|
||||||
|
Reference in New Issue
Block a user