pika: added instrumentation for pika.connection.Connection and pika.c… (#3584)
* pika: added instrumentation for pika.connection.Connection and pika.channel.Channel, thus added instrumentation support to all SelectConnection adapters. * updated changelog. --------- Co-authored-by: Riccardo Magliocchetti <riccardo.magliocchetti@gmail.com>
This commit is contained in:
parent
3c4d18cc13
commit
80c357bb16
|
|
@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `opentelemetry-instrumentation-pika` Added instrumentation for All `SelectConnection` adapters
|
||||||
|
([#3584](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3584))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- `opentelemetry-instrumentation-asgi`: fix excluded_urls in instrumentation-asgi
|
- `opentelemetry-instrumentation-asgi`: fix excluded_urls in instrumentation-asgi
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
# pylint: disable=unnecessary-dunder-call
|
# pylint: disable=unnecessary-dunder-call
|
||||||
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from typing import Any, Collection, Dict, Optional
|
from typing import Any, Collection, Dict, Optional, Union
|
||||||
|
|
||||||
import pika
|
import pika
|
||||||
import wrapt
|
import wrapt
|
||||||
|
|
@ -24,6 +24,8 @@ from pika.adapters.blocking_connection import (
|
||||||
BlockingChannel,
|
BlockingChannel,
|
||||||
_QueueConsumerGeneratorInfo,
|
_QueueConsumerGeneratorInfo,
|
||||||
)
|
)
|
||||||
|
from pika.channel import Channel
|
||||||
|
from pika.connection import Connection
|
||||||
|
|
||||||
from opentelemetry import trace
|
from opentelemetry import trace
|
||||||
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
||||||
|
|
@ -53,12 +55,16 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
|
||||||
|
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# pylint: disable=attribute-defined-outside-init
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _instrument_blocking_channel_consumers(
|
def _instrument_channel_consumers(
|
||||||
channel: BlockingChannel,
|
channel: Union[BlockingChannel, Channel],
|
||||||
tracer: Tracer,
|
tracer: Tracer,
|
||||||
consume_hook: utils.HookT = utils.dummy_callback,
|
consume_hook: utils.HookT = utils.dummy_callback,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
for consumer_tag, consumer_info in channel._consumer_infos.items():
|
if isinstance(channel, BlockingChannel):
|
||||||
|
consumer_infos = channel._consumer_infos
|
||||||
|
elif isinstance(channel, Channel):
|
||||||
|
consumer_infos = channel._consumers
|
||||||
|
for consumer_tag, consumer_info in consumer_infos.items():
|
||||||
callback_attr = PikaInstrumentor.CONSUMER_CALLBACK_ATTR
|
callback_attr = PikaInstrumentor.CONSUMER_CALLBACK_ATTR
|
||||||
consumer_callback = getattr(consumer_info, callback_attr, None)
|
consumer_callback = getattr(consumer_info, callback_attr, None)
|
||||||
if consumer_callback is None:
|
if consumer_callback is None:
|
||||||
|
|
@ -79,7 +85,7 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _instrument_basic_publish(
|
def _instrument_basic_publish(
|
||||||
channel: BlockingChannel,
|
channel: Union[BlockingChannel, Channel],
|
||||||
tracer: Tracer,
|
tracer: Tracer,
|
||||||
publish_hook: utils.HookT = utils.dummy_callback,
|
publish_hook: utils.HookT = utils.dummy_callback,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
@ -93,7 +99,7 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _instrument_channel_functions(
|
def _instrument_channel_functions(
|
||||||
channel: BlockingChannel,
|
channel: Union[BlockingChannel, Channel],
|
||||||
tracer: Tracer,
|
tracer: Tracer,
|
||||||
publish_hook: utils.HookT = utils.dummy_callback,
|
publish_hook: utils.HookT = utils.dummy_callback,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
@ -103,7 +109,9 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _uninstrument_channel_functions(channel: BlockingChannel) -> None:
|
def _uninstrument_channel_functions(
|
||||||
|
channel: Union[BlockingChannel, Channel],
|
||||||
|
) -> None:
|
||||||
for function_name in _FUNCTIONS_TO_UNINSTRUMENT:
|
for function_name in _FUNCTIONS_TO_UNINSTRUMENT:
|
||||||
if not hasattr(channel, function_name):
|
if not hasattr(channel, function_name):
|
||||||
continue
|
continue
|
||||||
|
|
@ -115,7 +123,7 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
|
||||||
@staticmethod
|
@staticmethod
|
||||||
# Make sure that the spans are created inside hash them set as parent and not as brothers
|
# Make sure that the spans are created inside hash them set as parent and not as brothers
|
||||||
def instrument_channel(
|
def instrument_channel(
|
||||||
channel: BlockingChannel,
|
channel: Union[BlockingChannel, Channel],
|
||||||
tracer_provider: Optional[TracerProvider] = None,
|
tracer_provider: Optional[TracerProvider] = None,
|
||||||
publish_hook: utils.HookT = utils.dummy_callback,
|
publish_hook: utils.HookT = utils.dummy_callback,
|
||||||
consume_hook: utils.HookT = utils.dummy_callback,
|
consume_hook: utils.HookT = utils.dummy_callback,
|
||||||
|
|
@ -133,7 +141,7 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
|
||||||
tracer_provider,
|
tracer_provider,
|
||||||
schema_url="https://opentelemetry.io/schemas/1.11.0",
|
schema_url="https://opentelemetry.io/schemas/1.11.0",
|
||||||
)
|
)
|
||||||
PikaInstrumentor._instrument_blocking_channel_consumers(
|
PikaInstrumentor._instrument_channel_consumers(
|
||||||
channel, tracer, consume_hook
|
channel, tracer, consume_hook
|
||||||
)
|
)
|
||||||
PikaInstrumentor._decorate_basic_consume(channel, tracer, consume_hook)
|
PikaInstrumentor._decorate_basic_consume(channel, tracer, consume_hook)
|
||||||
|
|
@ -178,16 +186,17 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
|
||||||
return channel
|
return channel
|
||||||
|
|
||||||
wrapt.wrap_function_wrapper(BlockingConnection, "channel", wrapper)
|
wrapt.wrap_function_wrapper(BlockingConnection, "channel", wrapper)
|
||||||
|
wrapt.wrap_function_wrapper(Connection, "channel", wrapper)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _decorate_basic_consume(
|
def _decorate_basic_consume(
|
||||||
channel: BlockingChannel,
|
channel: Union[BlockingChannel, Channel],
|
||||||
tracer: Optional[Tracer],
|
tracer: Optional[Tracer],
|
||||||
consume_hook: utils.HookT = utils.dummy_callback,
|
consume_hook: utils.HookT = utils.dummy_callback,
|
||||||
) -> None:
|
) -> None:
|
||||||
def wrapper(wrapped, instance, args, kwargs):
|
def wrapper(wrapped, instance, args, kwargs):
|
||||||
return_value = wrapped(*args, **kwargs)
|
return_value = wrapped(*args, **kwargs)
|
||||||
PikaInstrumentor._instrument_blocking_channel_consumers(
|
PikaInstrumentor._instrument_channel_consumers(
|
||||||
channel, tracer, consume_hook
|
channel, tracer, consume_hook
|
||||||
)
|
)
|
||||||
return return_value
|
return return_value
|
||||||
|
|
@ -236,6 +245,7 @@ class PikaInstrumentor(BaseInstrumentor): # type: ignore
|
||||||
if hasattr(self, "__opentelemetry_tracer_provider"):
|
if hasattr(self, "__opentelemetry_tracer_provider"):
|
||||||
delattr(self, "__opentelemetry_tracer_provider")
|
delattr(self, "__opentelemetry_tracer_provider")
|
||||||
unwrap(BlockingConnection, "channel")
|
unwrap(BlockingConnection, "channel")
|
||||||
|
unwrap(Connection, "channel")
|
||||||
unwrap(_QueueConsumerGeneratorInfo, "__init__")
|
unwrap(_QueueConsumerGeneratorInfo, "__init__")
|
||||||
|
|
||||||
def instrumentation_dependencies(self) -> Collection[str]:
|
def instrumentation_dependencies(self) -> Collection[str]:
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,13 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from unittest import TestCase, mock
|
from unittest import TestCase, mock
|
||||||
|
|
||||||
from pika.adapters import BlockingConnection
|
from pika.adapters import BaseConnection, BlockingConnection
|
||||||
from pika.adapters.blocking_connection import _QueueConsumerGeneratorInfo
|
from pika.adapters.blocking_connection import (
|
||||||
|
BlockingChannel,
|
||||||
|
_QueueConsumerGeneratorInfo,
|
||||||
|
)
|
||||||
from pika.channel import Channel
|
from pika.channel import Channel
|
||||||
|
from pika.connection import Connection
|
||||||
from wrapt import BoundFunctionWrapper
|
from wrapt import BoundFunctionWrapper
|
||||||
|
|
||||||
from opentelemetry.instrumentation.pika import PikaInstrumentor
|
from opentelemetry.instrumentation.pika import PikaInstrumentor
|
||||||
|
|
@ -31,11 +35,13 @@ from opentelemetry.trace import Tracer
|
||||||
|
|
||||||
class TestPika(TestCase):
|
class TestPika(TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
|
self.blocking_channel = mock.MagicMock(spec=BlockingChannel)
|
||||||
self.channel = mock.MagicMock(spec=Channel)
|
self.channel = mock.MagicMock(spec=Channel)
|
||||||
consumer_info = mock.MagicMock()
|
consumer_info = mock.MagicMock()
|
||||||
callback_attr = PikaInstrumentor.CONSUMER_CALLBACK_ATTR
|
callback_attr = PikaInstrumentor.CONSUMER_CALLBACK_ATTR
|
||||||
setattr(consumer_info, callback_attr, mock.MagicMock())
|
setattr(consumer_info, callback_attr, mock.MagicMock())
|
||||||
self.channel._consumer_infos = {"consumer-tag": consumer_info}
|
self.blocking_channel._consumer_infos = {"consumer-tag": consumer_info}
|
||||||
|
self.channel._consumers = {"consumer-tag": consumer_info}
|
||||||
self.mock_callback = mock.MagicMock()
|
self.mock_callback = mock.MagicMock()
|
||||||
|
|
||||||
def test_instrument_api(self) -> None:
|
def test_instrument_api(self) -> None:
|
||||||
|
|
@ -44,6 +50,10 @@ class TestPika(TestCase):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
isinstance(BlockingConnection.channel, BoundFunctionWrapper)
|
isinstance(BlockingConnection.channel, BoundFunctionWrapper)
|
||||||
)
|
)
|
||||||
|
self.assertTrue(isinstance(Connection.channel, BoundFunctionWrapper))
|
||||||
|
self.assertTrue(
|
||||||
|
isinstance(BaseConnection.channel, BoundFunctionWrapper)
|
||||||
|
)
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
isinstance(
|
isinstance(
|
||||||
_QueueConsumerGeneratorInfo.__init__, BoundFunctionWrapper
|
_QueueConsumerGeneratorInfo.__init__, BoundFunctionWrapper
|
||||||
|
|
@ -56,6 +66,10 @@ class TestPika(TestCase):
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
isinstance(BlockingConnection.channel, BoundFunctionWrapper)
|
isinstance(BlockingConnection.channel, BoundFunctionWrapper)
|
||||||
)
|
)
|
||||||
|
self.assertFalse(isinstance(Connection.channel, BoundFunctionWrapper))
|
||||||
|
self.assertFalse(
|
||||||
|
isinstance(BaseConnection.channel, BoundFunctionWrapper)
|
||||||
|
)
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
isinstance(
|
isinstance(
|
||||||
_QueueConsumerGeneratorInfo.__init__, BoundFunctionWrapper
|
_QueueConsumerGeneratorInfo.__init__, BoundFunctionWrapper
|
||||||
|
|
@ -69,11 +83,34 @@ class TestPika(TestCase):
|
||||||
"opentelemetry.instrumentation.pika.PikaInstrumentor._decorate_basic_consume"
|
"opentelemetry.instrumentation.pika.PikaInstrumentor._decorate_basic_consume"
|
||||||
)
|
)
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
"opentelemetry.instrumentation.pika.PikaInstrumentor._instrument_blocking_channel_consumers"
|
"opentelemetry.instrumentation.pika.PikaInstrumentor._instrument_channel_consumers"
|
||||||
|
)
|
||||||
|
def test_instrument_blocking_channel(
|
||||||
|
self,
|
||||||
|
instrument_channel_consumers: mock.MagicMock,
|
||||||
|
instrument_basic_consume: mock.MagicMock,
|
||||||
|
instrument_channel_functions: mock.MagicMock,
|
||||||
|
):
|
||||||
|
PikaInstrumentor.instrument_channel(channel=self.blocking_channel)
|
||||||
|
assert hasattr(
|
||||||
|
self.blocking_channel, "_is_instrumented_by_opentelemetry"
|
||||||
|
), "channel is not marked as instrumented!"
|
||||||
|
instrument_channel_consumers.assert_called_once()
|
||||||
|
instrument_basic_consume.assert_called_once()
|
||||||
|
instrument_channel_functions.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
"opentelemetry.instrumentation.pika.PikaInstrumentor._instrument_channel_functions"
|
||||||
|
)
|
||||||
|
@mock.patch(
|
||||||
|
"opentelemetry.instrumentation.pika.PikaInstrumentor._decorate_basic_consume"
|
||||||
|
)
|
||||||
|
@mock.patch(
|
||||||
|
"opentelemetry.instrumentation.pika.PikaInstrumentor._instrument_channel_consumers"
|
||||||
)
|
)
|
||||||
def test_instrument_channel(
|
def test_instrument_channel(
|
||||||
self,
|
self,
|
||||||
instrument_blocking_channel_consumers: mock.MagicMock,
|
instrument_channel_consumers: mock.MagicMock,
|
||||||
instrument_basic_consume: mock.MagicMock,
|
instrument_basic_consume: mock.MagicMock,
|
||||||
instrument_channel_functions: mock.MagicMock,
|
instrument_channel_functions: mock.MagicMock,
|
||||||
):
|
):
|
||||||
|
|
@ -81,12 +118,12 @@ class TestPika(TestCase):
|
||||||
assert hasattr(
|
assert hasattr(
|
||||||
self.channel, "_is_instrumented_by_opentelemetry"
|
self.channel, "_is_instrumented_by_opentelemetry"
|
||||||
), "channel is not marked as instrumented!"
|
), "channel is not marked as instrumented!"
|
||||||
instrument_blocking_channel_consumers.assert_called_once()
|
instrument_channel_consumers.assert_called_once()
|
||||||
instrument_basic_consume.assert_called_once()
|
instrument_basic_consume.assert_called_once()
|
||||||
instrument_channel_functions.assert_called_once()
|
instrument_channel_functions.assert_called_once()
|
||||||
|
|
||||||
@mock.patch("opentelemetry.instrumentation.pika.utils._decorate_callback")
|
@mock.patch("opentelemetry.instrumentation.pika.utils._decorate_callback")
|
||||||
def test_instrument_consumers(
|
def test_instrument_consumers_on_blocking_channel(
|
||||||
self, decorate_callback: mock.MagicMock
|
self, decorate_callback: mock.MagicMock
|
||||||
) -> None:
|
) -> None:
|
||||||
tracer = mock.MagicMock(spec=Tracer)
|
tracer = mock.MagicMock(spec=Tracer)
|
||||||
|
|
@ -95,23 +132,63 @@ class TestPika(TestCase):
|
||||||
mock.call(
|
mock.call(
|
||||||
getattr(value, callback_attr), tracer, key, dummy_callback
|
getattr(value, callback_attr), tracer, key, dummy_callback
|
||||||
)
|
)
|
||||||
for key, value in self.channel._consumer_infos.items()
|
for key, value in self.blocking_channel._consumer_infos.items()
|
||||||
]
|
]
|
||||||
PikaInstrumentor._instrument_blocking_channel_consumers(
|
PikaInstrumentor._instrument_channel_consumers(
|
||||||
self.channel, tracer
|
self.blocking_channel, tracer
|
||||||
)
|
)
|
||||||
decorate_callback.assert_has_calls(
|
decorate_callback.assert_has_calls(
|
||||||
calls=expected_decoration_calls, any_order=True
|
calls=expected_decoration_calls, any_order=True
|
||||||
)
|
)
|
||||||
assert all(
|
assert all(
|
||||||
hasattr(callback, "_original_callback")
|
hasattr(callback, "_original_callback")
|
||||||
for callback in self.channel._consumer_infos.values()
|
for callback in self.blocking_channel._consumer_infos.values()
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch("opentelemetry.instrumentation.pika.utils._decorate_callback")
|
||||||
|
def test_instrument_consumers_on_channel(
|
||||||
|
self, decorate_callback: mock.MagicMock
|
||||||
|
) -> None:
|
||||||
|
tracer = mock.MagicMock(spec=Tracer)
|
||||||
|
callback_attr = PikaInstrumentor.CONSUMER_CALLBACK_ATTR
|
||||||
|
expected_decoration_calls = [
|
||||||
|
mock.call(
|
||||||
|
getattr(value, callback_attr), tracer, key, dummy_callback
|
||||||
|
)
|
||||||
|
for key, value in self.channel._consumers.items()
|
||||||
|
]
|
||||||
|
PikaInstrumentor._instrument_channel_consumers(self.channel, tracer)
|
||||||
|
decorate_callback.assert_has_calls(
|
||||||
|
calls=expected_decoration_calls, any_order=True
|
||||||
|
)
|
||||||
|
assert all(
|
||||||
|
hasattr(callback, "_original_callback")
|
||||||
|
for callback in self.channel._consumers.values()
|
||||||
)
|
)
|
||||||
|
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
"opentelemetry.instrumentation.pika.utils._decorate_basic_publish"
|
"opentelemetry.instrumentation.pika.utils._decorate_basic_publish"
|
||||||
)
|
)
|
||||||
def test_instrument_basic_publish(
|
def test_instrument_basic_publish_on_blocking_channel(
|
||||||
|
self, decorate_basic_publish: mock.MagicMock
|
||||||
|
) -> None:
|
||||||
|
tracer = mock.MagicMock(spec=Tracer)
|
||||||
|
original_function = self.blocking_channel.basic_publish
|
||||||
|
PikaInstrumentor._instrument_basic_publish(
|
||||||
|
self.blocking_channel, tracer
|
||||||
|
)
|
||||||
|
decorate_basic_publish.assert_called_once_with(
|
||||||
|
original_function, self.blocking_channel, tracer, dummy_callback
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.blocking_channel.basic_publish,
|
||||||
|
decorate_basic_publish.return_value,
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
"opentelemetry.instrumentation.pika.utils._decorate_basic_publish"
|
||||||
|
)
|
||||||
|
def test_instrument_basic_publish_on_channel(
|
||||||
self, decorate_basic_publish: mock.MagicMock
|
self, decorate_basic_publish: mock.MagicMock
|
||||||
) -> None:
|
) -> None:
|
||||||
tracer = mock.MagicMock(spec=Tracer)
|
tracer = mock.MagicMock(spec=Tracer)
|
||||||
|
|
@ -141,6 +218,17 @@ class TestPika(TestCase):
|
||||||
isinstance(generator_info.pending_events, ReadyMessagesDequeProxy)
|
isinstance(generator_info.pending_events, ReadyMessagesDequeProxy)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_uninstrument_blocking_channel_functions(self) -> None:
|
||||||
|
original_function = self.blocking_channel.basic_publish
|
||||||
|
self.blocking_channel.basic_publish = mock.MagicMock()
|
||||||
|
self.blocking_channel.basic_publish._original_function = (
|
||||||
|
original_function
|
||||||
|
)
|
||||||
|
PikaInstrumentor._uninstrument_channel_functions(self.blocking_channel)
|
||||||
|
self.assertEqual(
|
||||||
|
self.blocking_channel.basic_publish, original_function
|
||||||
|
)
|
||||||
|
|
||||||
def test_uninstrument_channel_functions(self) -> None:
|
def test_uninstrument_channel_functions(self) -> None:
|
||||||
original_function = self.channel.basic_publish
|
original_function = self.channel.basic_publish
|
||||||
self.channel.basic_publish = mock.MagicMock()
|
self.channel.basic_publish = mock.MagicMock()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue