Skip to content

Commit 315b438

Browse files
committed
Added tests. Ready for PR
1 parent b561657 commit 315b438

File tree

12 files changed

+459
-92
lines changed

12 files changed

+459
-92
lines changed

instrumentation/opentelemetry-instrumentation-pika/README.rst

+38-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
OpenTelemetry <REPLACE ME> Instrumentation
2-
===========================
1+
OpenTelemetry pika Instrumentation
2+
==================================
33

44
|pypi|
55

66
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-pika.svg
77
:target: https://pypi.org/project/opentelemetry-instrumentation-pika/
88

9-
This library allows tracing requests made by the <REPLACE ME> library.
9+
This library allows tracing requests made by the pika library.
1010

1111
Installation
1212
------------
@@ -15,6 +15,41 @@ Installation
1515

1616
pip install opentelemetry-instrumentation-pika
1717

18+
Usage
19+
-----
20+
21+
* Start broker backend
22+
23+
.. code-block:: python
24+
25+
docker run -p 5672:5672 rabbitmq
26+
27+
28+
* Run instrumented task
29+
30+
.. code-block:: python
31+
32+
import pika
33+
from opentelemetry.instrumentation.pika import PikaInstrumentation
34+
35+
connection = pika.BlockingConnection(pika.URLParameters('amqp://localhost'))
36+
channel = connection.channel()
37+
channel.queue_declare(queue='hello')
38+
39+
pika_instrumentation = PikaInstrumentation()
40+
pika_instrumentation.instrument(channel=channel)
41+
42+
43+
channel.basic_publish(exchange='', routing_key='hello', body=b'Hello World!')
44+
45+
pika_instrumentation.uninstrument(channel=channel)
46+
47+
48+
* PikaInstrumentation also supports instrumentation without creating an object, and receiving a tracer_provider
49+
50+
.. code-block:: python
51+
52+
PikaInstrumentation.instrument_channel(channel, tracer_provider=tracer_provider)
1853
1954
References
2055
----------

instrumentation/opentelemetry-instrumentation-pika/setup.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ install_requires =
4444

4545
[options.extras_require]
4646
test =
47+
pytest
48+
opentelemetry-test == 0.24b0
4749

4850
[options.packages.find]
4951
where = src

instrumentation/opentelemetry-instrumentation-pika/setup.py

+1-6
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,7 @@
1717

1818
BASE_DIR = os.path.dirname(__file__)
1919
VERSION_FILENAME = os.path.join(
20-
BASE_DIR,
21-
"src",
22-
"opentelemetry",
23-
"instrumentation",
24-
"pika",
25-
"version.py",
20+
BASE_DIR, "src", "opentelemetry", "instrumentation", "pika", "version.py",
2621
)
2722
PACKAGE_INFO = {}
2823
with open(VERSION_FILENAME) as f:

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

+6-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
2727
* Run instrumented task
2828
29-
.. code:: python
29+
.. code-block:: python
30+
3031
import pika
3132
from opentelemetry.instrumentation.pika import PikaInstrumentation
3233
@@ -43,15 +44,16 @@
4344
pika_instrumentation.uninstrument(channel=channel)
4445
4546
46-
PikaInstrumentation also supports instrumentation without creating an object, and receiving a tracer_provider
47+
* PikaInstrumentation also supports instrumentation without creating an object, and receiving a tracer_provider
48+
49+
.. code-block:: python
4750
48-
.. code:: Python
4951
PikaInstrumentation.instrument_channel(channel, tracer_provider=tracer_provider)
5052
5153
API
5254
---
5355
"""
5456

5557

56-
from .version import __version__
5758
from .pika_instrumentor import PikaInstrumentation
59+
from .version import __version__

instrumentation/opentelemetry-instrumentation-pika/src/opentelemetry/instrumentation/pika/package.py

-1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,4 @@
1313
# limitations under the License.
1414
from typing import Collection
1515

16-
1716
_instruments: Collection[str] = ("pika >= 1.1.0",)

instrumentation/opentelemetry-instrumentation-pika/src/opentelemetry/instrumentation/pika/pika_instrumentor.py

+33-67
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
import pika
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
214
from logging import getLogger
15+
from typing import Any, Callable, Collection, Dict, Optional
16+
317
from pika.channel import Channel
4-
from pika.adapters import BaseConnection
5-
from typing import Dict, Callable, Optional, Collection, Any
18+
619
from opentelemetry import trace
7-
from opentelemetry.propagate import inject
20+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
821
from opentelemetry.instrumentation.pika import utils
9-
from opentelemetry.trace import Tracer, TracerProvider
10-
from opentelemetry.instrumentation.pika import __version__
11-
from opentelemetry.semconv.trace import MessagingOperationValues
1222
from opentelemetry.instrumentation.pika.package import _instruments
13-
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
14-
23+
from opentelemetry.instrumentation.pika.version import __version__
24+
from opentelemetry.trace import Tracer, TracerProvider
1525

1626
_LOG = getLogger(__name__)
1727
CTX_KEY = "__otel_task_span"
@@ -25,58 +35,17 @@ def _instrument_consumers(
2535
consumers_dict: Dict[str, Callable[..., Any]], tracer: Tracer
2636
) -> Any:
2737
for key, callback in consumers_dict.items():
28-
29-
def decorated_callback(
30-
channel: Channel,
31-
method: pika.spec.Basic.Deliver,
32-
properties: pika.spec.BasicProperties,
33-
body: bytes,
34-
) -> Any:
35-
if not properties:
36-
properties = pika.spec.BasicProperties()
37-
span = utils.get_span(
38-
tracer,
39-
channel,
40-
properties,
41-
task_name=key,
42-
operation=MessagingOperationValues.RECEIVE,
43-
)
44-
with trace.use_span(span, end_on_exit=True):
45-
inject(properties.headers)
46-
retval = callback(channel, method, properties, body)
47-
return retval
48-
49-
decorated_callback.__setattr__("_original_callback", callback)
38+
decorated_callback = utils.decorate_callback(callback, tracer, key)
39+
setattr(decorated_callback, "_original_callback", callback)
5040
consumers_dict[key] = decorated_callback
5141

5242
@staticmethod
5343
def _instrument_basic_publish(channel: Channel, tracer: Tracer) -> None:
5444
original_function = getattr(channel, "basic_publish")
55-
56-
def decorated_function(
57-
exchange: str,
58-
routing_key: str,
59-
body: bytes,
60-
properties: pika.spec.BasicProperties = None,
61-
mandatory: bool = False,
62-
) -> Any:
63-
if not properties:
64-
properties = pika.spec.BasicProperties()
65-
span = utils.get_span(
66-
tracer,
67-
channel,
68-
properties,
69-
task_name="(temporary)",
70-
operation=None,
71-
)
72-
with trace.use_span(span, end_on_exit=True):
73-
inject(properties.headers)
74-
retval = original_function(
75-
exchange, routing_key, body, properties, mandatory
76-
)
77-
return retval
78-
79-
decorated_function.__setattr__("_original_function", original_function)
45+
decorated_function = utils.decorate_basic_publish(
46+
original_function, channel, tracer
47+
)
48+
setattr(decorated_function, "_original_function", original_function)
8049
channel.__setattr__("basic_publish", decorated_function)
8150
channel.basic_publish = decorated_function
8251

@@ -98,12 +67,9 @@ def _uninstrument_channel_functions(channel: Channel) -> None:
9867

9968
@staticmethod
10069
def instrument_channel(
101-
channel: Channel,
102-
tracer_provider: Optional[TracerProvider] = None,
70+
channel: Channel, tracer_provider: Optional[TracerProvider] = None,
10371
) -> None:
104-
if not hasattr(channel, "_impl") or not isinstance(
105-
channel._impl, Channel
106-
):
72+
if not hasattr(channel, "_impl"):
10773
_LOG.error("Could not find implementation for provided channel!")
10874
return
10975
tracer = trace.get_tracer(__name__, __version__, tracer_provider)
@@ -119,20 +85,20 @@ def _instrument(self, **kwargs: Dict[str, Any]) -> None:
11985
if not channel or not isinstance(channel, Channel):
12086
return
12187
tracer_provider: TracerProvider = kwargs.get("tracer_provider", None)
122-
PikaInstrumentation.instrument_channel(channel, tracer_provider=tracer_provider)
88+
PikaInstrumentation.instrument_channel(
89+
channel, tracer_provider=tracer_provider
90+
)
12391

12492
def _uninstrument(self, **kwargs: Dict[str, Any]) -> None:
12593
channel: Channel = kwargs.get("channel", None)
12694
if not channel or not isinstance(channel, Channel):
12795
return
128-
if not hasattr(channel, "_impl") or not isinstance(
129-
channel._impl, BaseConnection
130-
):
96+
if not hasattr(channel, "_impl"):
13197
_LOG.error("Could not find implementation for provided channel!")
13298
return
133-
for key, callback in channel._impl._consumers:
99+
for key, callback in channel._impl._consumers.items():
134100
if hasattr(callback, "_original_callback"):
135-
channel._consumers[key] = callback._original_callback
101+
channel._impl._consumers[key] = callback._original_callback
136102
PikaInstrumentation._uninstrument_channel_functions(channel)
137103

138104
def instrumentation_dependencies(self) -> Collection[str]:

instrumentation/opentelemetry-instrumentation-pika/src/opentelemetry/instrumentation/pika/utils.py

+72-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
from typing import Any, Callable, List, Optional
2+
13
from pika.channel import Channel
2-
from typing import Optional, List
3-
from pika.spec import BasicProperties
4-
from opentelemetry.trace import Tracer
5-
from opentelemetry.trace.span import Span
6-
from opentelemetry.propagate import extract
7-
from opentelemetry.propagators.textmap import Getter, CarrierT
4+
from pika.spec import Basic, BasicProperties
5+
6+
from opentelemetry import propagate, trace
7+
from opentelemetry.propagators.textmap import CarrierT, Getter
88
from opentelemetry.semconv.trace import (
9-
SpanAttributes,
109
MessagingOperationValues,
10+
SpanAttributes,
1111
)
12+
from opentelemetry.trace import Tracer
13+
from opentelemetry.trace.span import Span
1214

1315

1416
class PikaGetter(Getter): # type: ignore
@@ -25,16 +27,75 @@ def keys(self, carrier: CarrierT) -> List[str]:
2527
pika_getter = PikaGetter()
2628

2729

30+
def decorate_callback(
31+
callback: Callable[[Channel, Basic.Deliver, BasicProperties, bytes], Any],
32+
tracer: Tracer,
33+
task_name: str,
34+
):
35+
def decorated_callback(
36+
channel: Channel,
37+
method: Basic.Deliver,
38+
properties: BasicProperties,
39+
body: bytes,
40+
) -> Any:
41+
if not properties:
42+
properties = BasicProperties()
43+
span = get_span(
44+
tracer,
45+
channel,
46+
properties,
47+
task_name=task_name,
48+
operation=MessagingOperationValues.RECEIVE,
49+
)
50+
with trace.use_span(span, end_on_exit=True):
51+
propagate.inject(properties.headers)
52+
retval = callback(channel, method, properties, body)
53+
return retval
54+
55+
return decorated_callback
56+
57+
58+
def decorate_basic_publish(
59+
original_function: Callable[[str, str, bytes, BasicProperties, bool], Any],
60+
channel: Channel,
61+
tracer: Tracer,
62+
):
63+
def decorated_function(
64+
exchange: str,
65+
routing_key: str,
66+
body: bytes,
67+
properties: BasicProperties = None,
68+
mandatory: bool = False,
69+
) -> Any:
70+
if not properties:
71+
properties = BasicProperties()
72+
span = get_span(
73+
tracer,
74+
channel,
75+
properties,
76+
task_name="(temporary)",
77+
operation=None,
78+
)
79+
with trace.use_span(span, end_on_exit=True):
80+
propagate.inject(properties.headers)
81+
retval = original_function(
82+
exchange, routing_key, body, properties, mandatory
83+
)
84+
return retval
85+
86+
return decorated_function
87+
88+
2889
def get_span(
2990
tracer: Tracer,
3091
channel: Channel,
3192
properties: BasicProperties,
3293
task_name: str,
33-
operation: Optional[MessagingOperationValues],
94+
operation: Optional[MessagingOperationValues] = None,
3495
) -> Span:
3596
if properties.headers is None:
3697
properties.headers = {}
37-
ctx = extract(properties.headers, getter=pika_getter)
98+
ctx = propagate.extract(properties.headers, getter=pika_getter)
3899
task_name = properties.type if properties.type else task_name
39100
span = tracer.start_span(
40101
context=ctx, name=generate_span_name(task_name, operation)
@@ -44,7 +105,7 @@ def get_span(
44105

45106

46107
def generate_span_name(
47-
task_name: str, operation: MessagingOperationValues
108+
task_name: str, operation: Optional[MessagingOperationValues]
48109
) -> str:
49110
if not operation:
50111
return f"{task_name} send"
@@ -56,7 +117,7 @@ def enrich_span(
56117
channel: Channel,
57118
properties: BasicProperties,
58119
task_destination: str,
59-
operation: Optional[MessagingOperationValues],
120+
operation: Optional[MessagingOperationValues] = None,
60121
) -> None:
61122
span.set_attribute(SpanAttributes.MESSAGING_SYSTEM, "rabbitmq")
62123
if operation:

instrumentation/opentelemetry-instrumentation-pika/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)