Skip to content

Commit b561657

Browse files
committed
Add all needed spans, and add support of instrumentation and uninstrumentation
1 parent 6acb1c5 commit b561657

File tree

4 files changed

+147
-50
lines changed

4 files changed

+147
-50
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,57 @@
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.
14+
"""
15+
Instrument `pika` to trace RabbitMQ applications.
16+
17+
Usage
18+
-----
19+
20+
* Start broker backend
21+
22+
.. code-block:: python
23+
24+
docker run -p 5672:5672 rabbitmq
25+
26+
27+
* Run instrumented task
28+
29+
.. code:: python
30+
import pika
31+
from opentelemetry.instrumentation.pika import PikaInstrumentation
32+
33+
connection = pika.BlockingConnection(pika.URLParameters('amqp://localhost'))
34+
channel = connection.channel()
35+
channel.queue_declare(queue='hello')
36+
37+
pika_instrumentation = PikaInstrumentation()
38+
pika_instrumentation.instrument(channel=channel)
39+
40+
41+
channel.basic_publish(exchange='', routing_key='hello', body=b'Hello World!')
42+
43+
pika_instrumentation.uninstrument(channel=channel)
44+
45+
46+
PikaInstrumentation also supports instrumentation without creating an object, and receiving a tracer_provider
47+
48+
.. code:: Python
49+
PikaInstrumentation.instrument_channel(channel, tracer_provider=tracer_provider)
50+
51+
API
52+
---
53+
"""
54+
55+
156
from .version import __version__
257
from .pika_instrumentor import PikaInstrumentation

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
from typing import Collection
1415

1516

16-
_instruments = ("pika >= 1.1.0",)
17+
_instruments: Collection[str] = ("pika >= 1.1.0",)
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import pika
22
from logging import getLogger
3-
from opentelemetry import trace
4-
from typing import Dict, Callable
5-
from typing import Collection, Any
3+
from pika.channel import Channel
64
from pika.adapters import BaseConnection
5+
from typing import Dict, Callable, Optional, Collection, Any
6+
from opentelemetry import trace
77
from opentelemetry.propagate import inject
88
from opentelemetry.instrumentation.pika import utils
99
from opentelemetry.trace import Tracer, TracerProvider
10+
from opentelemetry.instrumentation.pika import __version__
1011
from opentelemetry.semconv.trace import MessagingOperationValues
1112
from opentelemetry.instrumentation.pika.package import _instruments
1213
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
@@ -15,16 +16,18 @@
1516
_LOG = getLogger(__name__)
1617
CTX_KEY = "__otel_task_span"
1718

19+
FUNCTIONS_TO_UNINSTRUMENT = ["basic_publish"]
1820

19-
class PikaInstrumentation(BaseInstrumentor):
21+
22+
class PikaInstrumentation(BaseInstrumentor): # type: ignore
2023
@staticmethod
2124
def _instrument_consumers(
2225
consumers_dict: Dict[str, Callable[..., Any]], tracer: Tracer
2326
) -> Any:
2427
for key, callback in consumers_dict.items():
2528

2629
def decorated_callback(
27-
channel: pika.channel.Channel,
30+
channel: Channel,
2831
method: pika.spec.Basic.Deliver,
2932
properties: pika.spec.BasicProperties,
3033
body: bytes,
@@ -47,12 +50,16 @@ def decorated_callback(
4750
consumers_dict[key] = decorated_callback
4851

4952
@staticmethod
50-
def _instrument_publish(channel: Any, tracer: Tracer) -> None:
51-
original_basic_publish = channel.basic_publish
53+
def _instrument_basic_publish(channel: Channel, tracer: Tracer) -> None:
54+
original_function = getattr(channel, "basic_publish")
5255

53-
def decorated_basic_publish(
54-
exchange, routing_key, body, properties=None, mandatory=False
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:
5663
if not properties:
5764
properties = pika.spec.BasicProperties()
5865
span = utils.get_span(
@@ -64,45 +71,69 @@ def decorated_basic_publish(
6471
)
6572
with trace.use_span(span, end_on_exit=True):
6673
inject(properties.headers)
67-
retval = original_basic_publish(
74+
retval = original_function(
6875
exchange, routing_key, body, properties, mandatory
6976
)
7077
return retval
7178

72-
decorated_basic_publish.__setattr__(
73-
"_original_function", original_basic_publish
74-
)
75-
channel.basic_publish = decorated_basic_publish
79+
decorated_function.__setattr__("_original_function", original_function)
80+
channel.__setattr__("basic_publish", decorated_function)
81+
channel.basic_publish = decorated_function
82+
83+
@staticmethod
84+
def _instrument_channel_functions(
85+
channel: Channel, tracer: Tracer
86+
) -> None:
87+
if hasattr(channel, "basic_publish"):
88+
PikaInstrumentation._instrument_basic_publish(channel, tracer)
89+
90+
@staticmethod
91+
def _uninstrument_channel_functions(channel: Channel) -> None:
92+
for function_name in FUNCTIONS_TO_UNINSTRUMENT:
93+
if not hasattr(channel, function_name):
94+
continue
95+
function = getattr(channel, function_name)
96+
if hasattr(function, "_original_function"):
97+
channel.__setattr__(function_name, function._original_function)
7698

7799
@staticmethod
78100
def instrument_channel(
79-
channel: Any, tracer_provider: TracerProvider
101+
channel: Channel,
102+
tracer_provider: Optional[TracerProvider] = None,
80103
) -> None:
81104
if not hasattr(channel, "_impl") or not isinstance(
82-
channel._impl, pika.channel.Channel
105+
channel._impl, Channel
83106
):
84107
_LOG.error("Could not find implementation for provided channel!")
85108
return
86-
tracer = trace.get_tracer(__name__, pika.__version__, tracer_provider)
109+
tracer = trace.get_tracer(__name__, __version__, tracer_provider)
110+
channel.__setattr__("__opentelemetry_tracer", tracer)
87111
if channel._impl._consumers:
88112
PikaInstrumentation._instrument_consumers(
89113
channel._impl._consumers, tracer
90114
)
91-
PikaInstrumentation._instrument_publish(channel, tracer)
115+
PikaInstrumentation._instrument_channel_functions(channel, tracer)
116+
117+
def _instrument(self, **kwargs: Dict[str, Any]) -> None:
118+
channel: Channel = kwargs.get("channel", None)
119+
if not channel or not isinstance(channel, Channel):
120+
return
121+
tracer_provider: TracerProvider = kwargs.get("tracer_provider", None)
122+
PikaInstrumentation.instrument_channel(channel, tracer_provider=tracer_provider)
92123

93-
def _uninstrument(self, connection: Any, **kwargs: Dict[str, Any]) -> None:
94-
if not hasattr(connection, "_impl") or not isinstance(
95-
connection._impl, BaseConnection
124+
def _uninstrument(self, **kwargs: Dict[str, Any]) -> None:
125+
channel: Channel = kwargs.get("channel", None)
126+
if not channel or not isinstance(channel, Channel):
127+
return
128+
if not hasattr(channel, "_impl") or not isinstance(
129+
channel._impl, BaseConnection
96130
):
97131
_LOG.error("Could not find implementation for provided channel!")
98132
return
99-
for key, callback in connection._impl._consumers:
133+
for key, callback in channel._impl._consumers:
100134
if hasattr(callback, "_original_callback"):
101-
connection._consumers[key] = callback._original_callback
102-
if hasattr(connection.basic_publish, "_original_function"):
103-
connection.basic_publish = (
104-
connection.basic_publish._original_function
105-
)
135+
channel._consumers[key] = callback._original_callback
136+
PikaInstrumentation._uninstrument_channel_functions(channel)
106137

107138
def instrumentation_dependencies(self) -> Collection[str]:
108139
return _instruments
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
from typing import Optional
21
from pika.channel import Channel
2+
from typing import Optional, List
33
from pika.spec import BasicProperties
44
from opentelemetry.trace import Tracer
55
from opentelemetry.trace.span import Span
66
from opentelemetry.propagate import extract
7-
from opentelemetry.propagators.textmap import Getter
7+
from opentelemetry.propagators.textmap import Getter, CarrierT
88
from opentelemetry.semconv.trace import (
99
SpanAttributes,
1010
MessagingOperationValues,
1111
)
1212

1313

14-
class PikaGetter(Getter):
15-
def get(self, carrier, key):
14+
class PikaGetter(Getter): # type: ignore
15+
def get(self, carrier: CarrierT, key: str) -> Optional[List[str]]:
1616
value = carrier.get(key, None)
1717
if value is None:
1818
return None
19-
return (value,)
19+
return [value]
2020

21-
def keys(self, carrier):
21+
def keys(self, carrier: CarrierT) -> List[str]:
2222
return []
2323

2424

@@ -35,6 +35,7 @@ def get_span(
3535
if properties.headers is None:
3636
properties.headers = {}
3737
ctx = extract(properties.headers, getter=pika_getter)
38+
task_name = properties.type if properties.type else task_name
3839
span = tracer.start_span(
3940
context=ctx, name=generate_span_name(task_name, operation)
4041
)
@@ -45,6 +46,8 @@ def get_span(
4546
def generate_span_name(
4647
task_name: str, operation: MessagingOperationValues
4748
) -> str:
49+
if not operation:
50+
return f"{task_name} send"
4851
return f"{task_name} {operation.value}"
4952

5053

@@ -61,18 +64,25 @@ def enrich_span(
6164
else:
6265
span.set_attribute(SpanAttributes.MESSAGING_TEMP_DESTINATION, True)
6366
span.set_attribute(SpanAttributes.MESSAGING_DESTINATION, task_destination)
64-
span.set_attribute(
65-
SpanAttributes.MESSAGING_DESTINATION_KIND, properties.type
66-
)
67-
span.set_attribute(
68-
SpanAttributes.MESSAGING_MESSAGE_ID, properties.message_id
69-
)
70-
span.set_attribute(
71-
SpanAttributes.MESSAGING_CONVERSATION_ID, properties.correlation_id
72-
)
73-
span.set_attribute(
74-
SpanAttributes.NET_PEER_NAME, channel.connection.params.host
75-
)
76-
span.set_attribute(
77-
SpanAttributes.NET_PEER_PORT, channel.connection.params.port
78-
)
67+
if properties.message_id:
68+
span.set_attribute(
69+
SpanAttributes.MESSAGING_MESSAGE_ID, properties.message_id
70+
)
71+
if properties.correlation_id:
72+
span.set_attribute(
73+
SpanAttributes.MESSAGING_CONVERSATION_ID, properties.correlation_id
74+
)
75+
if not hasattr(channel.connection, "params"):
76+
span.set_attribute(
77+
SpanAttributes.NET_PEER_NAME, channel.connection._impl.params.host
78+
)
79+
span.set_attribute(
80+
SpanAttributes.NET_PEER_PORT, channel.connection._impl.params.port
81+
)
82+
else:
83+
span.set_attribute(
84+
SpanAttributes.NET_PEER_NAME, channel.connection.params.host
85+
)
86+
span.set_attribute(
87+
SpanAttributes.NET_PEER_PORT, channel.connection.params.port
88+
)

0 commit comments

Comments
 (0)