Skip to content

Commit f4babd5

Browse files
authored
Merge branch 'main' into main
2 parents d12e081 + 4bf3577 commit f4babd5

File tree

13 files changed

+905
-137
lines changed

13 files changed

+905
-137
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
- 'release/*'
77
pull_request:
88
env:
9-
CORE_REPO_SHA: 9831afaff5b4d371fd9a14266ab47884546bd971
9+
CORE_REPO_SHA: 35a021194787359324c46f5ca99d31802e4c92bd
1010

1111
jobs:
1212
build:

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
([#1987](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1987))
1414
- `opentelemetry-instrumentation-httpx` Fix mixing async and non async hooks
1515
([#1920](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1920))
16+
- `opentelemetry-instrumentation-requests` Implement new semantic convention opt-in with stable http semantic conventions
17+
([#2002](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2002))
18+
- `opentelemetry-instrument-grpc` Fix arity of context.abort for AIO RPCs
19+
([#2066](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2066))
1620

1721
### Fixed
1822

1923
- `opentelemetry-instrumentation-urllib`/`opentelemetry-instrumentation-urllib3` Fix metric descriptions to match semantic conventions
20-
([#1959]((https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1959))
24+
([#1959](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1959))
2125

2226
## Version 1.21.0/0.42b0 (2023-11-01)
2327

@@ -34,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3438

3539
### Fixed
3640

41+
- `opentelemetry-instrumentation-aio-pika` and `opentelemetry-instrumentation-pika` Fix missing trace context propagation when trace not recording.
42+
([#1969](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1969))
3743
- Fix version of Flask dependency `werkzeug`
3844
([#1980](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1980))
3945
- `opentelemetry-resource-detector-azure` Using new Cloud Resource ID attribute.

instrumentation/opentelemetry-instrumentation-aio-pika/src/opentelemetry/instrumentation/aio_pika/publish_decorator.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,7 @@ async def decorated_publish(
4545
if not span:
4646
return await publish(message, routing_key, **kwargs)
4747
with trace.use_span(span, end_on_exit=True):
48-
if span.is_recording():
49-
propagate.inject(message.properties.headers)
48+
propagate.inject(message.properties.headers)
5049
return_value = await publish(message, routing_key, **kwargs)
5150
return return_value
5251

instrumentation/opentelemetry-instrumentation-aio-pika/tests/test_publish_decorator.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import asyncio
1515
from typing import Type
1616
from unittest import TestCase, mock, skipIf
17+
from unittest.mock import MagicMock
1718

1819
from aio_pika import Exchange, RobustExchange
1920

@@ -92,6 +93,36 @@ def test_publish(self):
9293
def test_robust_publish(self):
9394
self._test_publish(RobustExchange)
9495

96+
def _test_publish_works_with_not_recording_span(self, exchange_type):
97+
exchange = exchange_type(CONNECTION_7, CHANNEL_7, EXCHANGE_NAME)
98+
with mock.patch.object(
99+
PublishDecorator, "_get_publish_span"
100+
) as mock_get_publish_span:
101+
mocked_not_recording_span = MagicMock()
102+
mocked_not_recording_span.is_recording.return_value = False
103+
mock_get_publish_span.return_value = mocked_not_recording_span
104+
with mock.patch.object(
105+
Exchange, "publish", return_value=asyncio.sleep(0)
106+
) as mock_publish:
107+
with mock.patch(
108+
"opentelemetry.instrumentation.aio_pika.publish_decorator.propagate.inject"
109+
) as mock_inject:
110+
decorated_publish = PublishDecorator(
111+
self.tracer, exchange
112+
).decorate(mock_publish)
113+
self.loop.run_until_complete(
114+
decorated_publish(MESSAGE, ROUTING_KEY)
115+
)
116+
mock_publish.assert_called_once()
117+
mock_get_publish_span.assert_called_once()
118+
mock_inject.assert_called_once()
119+
120+
def test_publish_works_with_not_recording_span(self):
121+
self._test_publish_works_with_not_recording_span(Exchange)
122+
123+
def test_publish_works_with_not_recording_span_robust(self):
124+
self._test_publish_works_with_not_recording_span(RobustExchange)
125+
95126

96127
@skipIf(AIOPIKA_VERSION_INFO <= (8, 0), "Only for aio_pika 8")
97128
class TestInstrumentedExchangeAioRmq8(TestCase):
@@ -144,3 +175,33 @@ def test_publish(self):
144175

145176
def test_robust_publish(self):
146177
self._test_publish(RobustExchange)
178+
179+
def _test_publish_works_with_not_recording_span(self, exchange_type):
180+
exchange = exchange_type(CONNECTION_7, CHANNEL_7, EXCHANGE_NAME)
181+
with mock.patch.object(
182+
PublishDecorator, "_get_publish_span"
183+
) as mock_get_publish_span:
184+
mocked_not_recording_span = MagicMock()
185+
mocked_not_recording_span.is_recording.return_value = False
186+
mock_get_publish_span.return_value = mocked_not_recording_span
187+
with mock.patch.object(
188+
Exchange, "publish", return_value=asyncio.sleep(0)
189+
) as mock_publish:
190+
with mock.patch(
191+
"opentelemetry.instrumentation.aio_pika.publish_decorator.propagate.inject"
192+
) as mock_inject:
193+
decorated_publish = PublishDecorator(
194+
self.tracer, exchange
195+
).decorate(mock_publish)
196+
self.loop.run_until_complete(
197+
decorated_publish(MESSAGE, ROUTING_KEY)
198+
)
199+
mock_publish.assert_called_once()
200+
mock_get_publish_span.assert_called_once()
201+
mock_inject.assert_called_once()
202+
203+
def test_publish_works_with_not_recording_span(self):
204+
self._test_publish_works_with_not_recording_span(Exchange)
205+
206+
def test_publish_works_with_not_recording_span_robust(self):
207+
self._test_publish_works_with_not_recording_span(RobustExchange)

instrumentation/opentelemetry-instrumentation-grpc/src/opentelemetry/instrumentation/grpc/_aio_server.py

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,63 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import grpc
1516
import grpc.aio
16-
17-
from ._server import (
18-
OpenTelemetryServerInterceptor,
19-
_OpenTelemetryServicerContext,
20-
_wrap_rpc_behavior,
21-
)
17+
import wrapt
18+
19+
from opentelemetry.semconv.trace import SpanAttributes
20+
from opentelemetry.trace.status import Status, StatusCode
21+
22+
from ._server import OpenTelemetryServerInterceptor, _wrap_rpc_behavior
23+
24+
25+
# pylint:disable=abstract-method
26+
class _OpenTelemetryAioServicerContext(wrapt.ObjectProxy):
27+
def __init__(self, servicer_context, active_span):
28+
super().__init__(servicer_context)
29+
self._self_active_span = active_span
30+
self._self_code = grpc.StatusCode.OK
31+
self._self_details = None
32+
33+
async def abort(self, code, details="", trailing_metadata=tuple()):
34+
self._self_code = code
35+
self._self_details = details
36+
self._self_active_span.set_attribute(
37+
SpanAttributes.RPC_GRPC_STATUS_CODE, code.value[0]
38+
)
39+
self._self_active_span.set_status(
40+
Status(
41+
status_code=StatusCode.ERROR,
42+
description=f"{code}:{details}",
43+
)
44+
)
45+
return await self.__wrapped__.abort(code, details, trailing_metadata)
46+
47+
def set_code(self, code):
48+
self._self_code = code
49+
details = self._self_details or code.value[1]
50+
self._self_active_span.set_attribute(
51+
SpanAttributes.RPC_GRPC_STATUS_CODE, code.value[0]
52+
)
53+
if code != grpc.StatusCode.OK:
54+
self._self_active_span.set_status(
55+
Status(
56+
status_code=StatusCode.ERROR,
57+
description=f"{code}:{details}",
58+
)
59+
)
60+
return self.__wrapped__.set_code(code)
61+
62+
def set_details(self, details):
63+
self._self_details = details
64+
if self._self_code != grpc.StatusCode.OK:
65+
self._self_active_span.set_status(
66+
Status(
67+
status_code=StatusCode.ERROR,
68+
description=f"{self._self_code}:{details}",
69+
)
70+
)
71+
return self.__wrapped__.set_details(details)
2272

2373

2474
class OpenTelemetryAioServerInterceptor(
@@ -66,7 +116,7 @@ async def _unary_interceptor(request_or_iterator, context):
66116
set_status_on_exception=False,
67117
) as span:
68118
# wrap the context
69-
context = _OpenTelemetryServicerContext(context, span)
119+
context = _OpenTelemetryAioServicerContext(context, span)
70120

71121
# And now we run the actual RPC.
72122
try:
@@ -91,7 +141,7 @@ async def _stream_interceptor(request_or_iterator, context):
91141
context,
92142
set_status_on_exception=False,
93143
) as span:
94-
context = _OpenTelemetryServicerContext(context, span)
144+
context = _OpenTelemetryAioServicerContext(context, span)
95145

96146
try:
97147
async for response in behavior(

instrumentation/opentelemetry-instrumentation-grpc/tests/test_aio_server_interceptor.py

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,11 @@ async def run_with_test_server(
8888
channel = grpc.aio.insecure_channel(f"localhost:{port:d}")
8989

9090
await server.start()
91-
resp = await runnable(channel)
92-
await server.stop(1000)
91+
92+
try:
93+
resp = await runnable(channel)
94+
finally:
95+
await server.stop(1000)
9396

9497
return resp
9598

@@ -514,9 +517,79 @@ async def request(channel):
514517
request = Request(client_id=1, request_data=failure_message)
515518
msg = request.SerializeToString()
516519

517-
with testcase.assertRaises(Exception):
520+
with testcase.assertRaises(grpc.RpcError) as cm:
521+
await channel.unary_unary(rpc_call)(msg)
522+
523+
self.assertEqual(
524+
cm.exception.code(), grpc.StatusCode.FAILED_PRECONDITION
525+
)
526+
self.assertEqual(cm.exception.details(), failure_message)
527+
528+
await run_with_test_server(request, servicer=AbortServicer())
529+
530+
spans_list = self.memory_exporter.get_finished_spans()
531+
self.assertEqual(len(spans_list), 1)
532+
span = spans_list[0]
533+
534+
self.assertEqual(span.name, rpc_call)
535+
self.assertIs(span.kind, trace.SpanKind.SERVER)
536+
537+
# Check version and name in span's instrumentation info
538+
self.assertEqualSpanInstrumentationInfo(
539+
span, opentelemetry.instrumentation.grpc
540+
)
541+
542+
# make sure this span errored, with the right status and detail
543+
self.assertEqual(span.status.status_code, StatusCode.ERROR)
544+
self.assertEqual(
545+
span.status.description,
546+
f"{grpc.StatusCode.FAILED_PRECONDITION}:{failure_message}",
547+
)
548+
549+
# Check attributes
550+
self.assertSpanHasAttributes(
551+
span,
552+
{
553+
SpanAttributes.NET_PEER_IP: "[::1]",
554+
SpanAttributes.NET_PEER_NAME: "localhost",
555+
SpanAttributes.RPC_METHOD: "SimpleMethod",
556+
SpanAttributes.RPC_SERVICE: "GRPCTestServer",
557+
SpanAttributes.RPC_SYSTEM: "grpc",
558+
SpanAttributes.RPC_GRPC_STATUS_CODE: grpc.StatusCode.FAILED_PRECONDITION.value[
559+
0
560+
],
561+
},
562+
)
563+
564+
async def test_abort_with_trailing_metadata(self):
565+
"""Check that we can catch an abort properly when trailing_metadata provided"""
566+
rpc_call = "/GRPCTestServer/SimpleMethod"
567+
failure_message = "failure message"
568+
569+
class AbortServicer(GRPCTestServerServicer):
570+
# pylint:disable=C0103
571+
async def SimpleMethod(self, request, context):
572+
metadata = (("meta", "data"),)
573+
await context.abort(
574+
grpc.StatusCode.FAILED_PRECONDITION,
575+
failure_message,
576+
trailing_metadata=metadata,
577+
)
578+
579+
testcase = self
580+
581+
async def request(channel):
582+
request = Request(client_id=1, request_data=failure_message)
583+
msg = request.SerializeToString()
584+
585+
with testcase.assertRaises(grpc.RpcError) as cm:
518586
await channel.unary_unary(rpc_call)(msg)
519587

588+
self.assertEqual(
589+
cm.exception.code(), grpc.StatusCode.FAILED_PRECONDITION
590+
)
591+
self.assertEqual(cm.exception.details(), failure_message)
592+
520593
await run_with_test_server(request, servicer=AbortServicer())
521594

522595
spans_list = self.memory_exporter.get_finished_spans()

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,11 @@ def decorated_function(
113113
exchange, routing_key, body, properties, mandatory
114114
)
115115
with trace.use_span(span, end_on_exit=True):
116-
if span.is_recording():
117-
propagate.inject(properties.headers)
118-
try:
119-
publish_hook(span, body, properties)
120-
except Exception as hook_exception: # pylint: disable=W0703
121-
_LOG.exception(hook_exception)
116+
propagate.inject(properties.headers)
117+
try:
118+
publish_hook(span, body, properties)
119+
except Exception as hook_exception: # pylint: disable=W0703
120+
_LOG.exception(hook_exception)
122121
retval = original_function(
123122
exchange, routing_key, body, properties, mandatory
124123
)

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

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,6 @@ def test_decorate_basic_publish(
292292
use_span.assert_called_once_with(
293293
get_span.return_value, end_on_exit=True
294294
)
295-
get_span.return_value.is_recording.assert_called_once()
296295
inject.assert_called_once_with(properties.headers)
297296
callback.assert_called_once_with(
298297
exchange_name, routing_key, mock_body, properties, False
@@ -323,7 +322,6 @@ def test_decorate_basic_publish_no_properties(
323322
use_span.assert_called_once_with(
324323
get_span.return_value, end_on_exit=True
325324
)
326-
get_span.return_value.is_recording.assert_called_once()
327325
inject.assert_called_once_with(basic_properties.return_value.headers)
328326
self.assertEqual(retval, callback.return_value)
329327

@@ -393,7 +391,55 @@ def test_decorate_basic_publish_with_hook(
393391
use_span.assert_called_once_with(
394392
get_span.return_value, end_on_exit=True
395393
)
396-
get_span.return_value.is_recording.assert_called_once()
394+
inject.assert_called_once_with(properties.headers)
395+
publish_hook.assert_called_once_with(
396+
get_span.return_value, mock_body, properties
397+
)
398+
callback.assert_called_once_with(
399+
exchange_name, routing_key, mock_body, properties, False
400+
)
401+
self.assertEqual(retval, callback.return_value)
402+
403+
@mock.patch("opentelemetry.instrumentation.pika.utils._get_span")
404+
@mock.patch("opentelemetry.propagate.inject")
405+
@mock.patch("opentelemetry.trace.use_span")
406+
def test_decorate_basic_publish_when_span_is_not_recording(
407+
self,
408+
use_span: mock.MagicMock,
409+
inject: mock.MagicMock,
410+
get_span: mock.MagicMock,
411+
) -> None:
412+
callback = mock.MagicMock()
413+
tracer = mock.MagicMock()
414+
channel = mock.MagicMock(spec=Channel)
415+
exchange_name = "test-exchange"
416+
routing_key = "test-routing-key"
417+
properties = mock.MagicMock()
418+
mock_body = b"mock_body"
419+
publish_hook = mock.MagicMock()
420+
421+
mocked_span = mock.MagicMock()
422+
mocked_span.is_recording.return_value = False
423+
get_span.return_value = mocked_span
424+
425+
decorated_basic_publish = utils._decorate_basic_publish(
426+
callback, channel, tracer, publish_hook
427+
)
428+
retval = decorated_basic_publish(
429+
exchange_name, routing_key, mock_body, properties
430+
)
431+
get_span.assert_called_once_with(
432+
tracer,
433+
channel,
434+
properties,
435+
destination=exchange_name,
436+
span_kind=SpanKind.PRODUCER,
437+
task_name="(temporary)",
438+
operation=None,
439+
)
440+
use_span.assert_called_once_with(
441+
get_span.return_value, end_on_exit=True
442+
)
397443
inject.assert_called_once_with(properties.headers)
398444
publish_hook.assert_called_once_with(
399445
get_span.return_value, mock_body, properties

0 commit comments

Comments
 (0)