-
Notifications
You must be signed in to change notification settings - Fork 1k
feat(kafka): add messaging.kafka.cluster.id to producer/consumer spans #4727
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
15e62a3
193777e
1fe9ae1
e4c312b
e1ceb1b
f26d610
d61b428
a419ba5
eefa7a5
f441b9b
909a79e
2632f87
905fe25
c1d8ae5
ab9849e
a55cef3
0f747c4
2fb2695
ccf030d
cccda08
c0fbf81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| `opentelemetry-instrumentation-aiokafka`, `opentelemetry-instrumentation-confluent-kafka`, `opentelemetry-instrumentation-kafka-python`: emit `messaging.kafka.cluster.id` on producer and consumer spans |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,6 +79,10 @@ async def __call__( | |
|
|
||
| _LOG = getLogger(__name__) | ||
|
|
||
| # TODO(semconv #3819): once generated in opentelemetry-semantic-conventions, | ||
| # use messaging_attributes.MESSAGING_KAFKA_CLUSTER_ID instead of this literal. | ||
| _MESSAGING_KAFKA_CLUSTER_ID = "messaging.kafka.cluster.id" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that the attribute has already been added to the semantic conventions, but it hasn’t been released yet. Could we wait for the release before using it here? I’ll raise this at the next SIG meeting to get guidance on whether instrumentations should mix attributes from different semantic convention versions without the opt-in gate.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we can wait until the next semantic-conventions release. I recently learned that if an attribute hasn't been added to semantic-conventions yet, we should add an opt-in experimental attribute instead. Once the attribute is present and released in sem-conv, we can directly replace the current TODO with messaging_attributes.MESSAGING_KAFKA_CLUSTER_ID. |
||
|
|
||
|
|
||
| def _extract_bootstrap_servers( | ||
| client: aiokafka.AIOKafkaClient, | ||
|
|
@@ -90,6 +94,13 @@ def _extract_client_id(client: aiokafka.AIOKafkaClient) -> str: | |
| return client._client_id | ||
|
|
||
|
|
||
| def _extract_cluster_id_from_client( | ||
| client: aiokafka.AIOKafkaClient, | ||
| ) -> str | None: | ||
| cluster_id: str | None = getattr(client, "_otel_cluster_id", None) | ||
| return cluster_id if cluster_id else None | ||
|
|
||
|
|
||
| def _extract_consumer_group( | ||
| consumer: aiokafka.AIOKafkaConsumer, | ||
| ) -> str | None: | ||
|
|
@@ -237,6 +248,7 @@ def _enrich_base_span( | |
| topic: str, | ||
| partition: int | None, | ||
| key: str | None, | ||
| cluster_id: str | None = None, | ||
| ) -> None: | ||
| span.set_attribute( | ||
| messaging_attributes.MESSAGING_SYSTEM, | ||
|
|
@@ -259,6 +271,9 @@ def _enrich_base_span( | |
| messaging_attributes.MESSAGING_KAFKA_MESSAGE_KEY, key | ||
| ) | ||
|
|
||
| if cluster_id is not None: | ||
| span.set_attribute(_MESSAGING_KAFKA_CLUSTER_ID, cluster_id) | ||
|
|
||
|
|
||
| def _enrich_send_span( | ||
| span: Span, | ||
|
|
@@ -268,6 +283,7 @@ def _enrich_send_span( | |
| topic: str, | ||
| partition: int | None, | ||
| key: str | None, | ||
| cluster_id: str | None = None, | ||
| ) -> None: | ||
| if not span.is_recording(): | ||
| return | ||
|
|
@@ -279,6 +295,7 @@ def _enrich_send_span( | |
| topic=topic, | ||
| partition=partition, | ||
| key=key, | ||
| cluster_id=cluster_id, | ||
| ) | ||
|
|
||
| span.set_attribute(messaging_attributes.MESSAGING_OPERATION_NAME, "send") | ||
|
|
@@ -298,6 +315,7 @@ def _enrich_getone_span( | |
| partition: int | None, | ||
| key: str | None, | ||
| offset: int, | ||
| cluster_id: str | None = None, | ||
| ) -> None: | ||
| if not span.is_recording(): | ||
| return | ||
|
|
@@ -309,6 +327,7 @@ def _enrich_getone_span( | |
| topic=topic, | ||
| partition=partition, | ||
| key=key, | ||
| cluster_id=cluster_id, | ||
| ) | ||
|
|
||
| if consumer_group is not None: | ||
|
|
@@ -344,6 +363,7 @@ def _enrich_getmany_poll_span( | |
| client_id: str, | ||
| consumer_group: str | None, | ||
| message_count: int, | ||
| cluster_id: str | None = None, | ||
| ) -> None: | ||
| if not span.is_recording(): | ||
| return | ||
|
|
@@ -357,6 +377,9 @@ def _enrich_getmany_poll_span( | |
| ) | ||
| span.set_attribute(messaging_attributes.MESSAGING_CLIENT_ID, client_id) | ||
|
|
||
| if cluster_id is not None: | ||
| span.set_attribute(_MESSAGING_KAFKA_CLUSTER_ID, cluster_id) | ||
|
|
||
| if consumer_group is not None: | ||
| span.set_attribute( | ||
| messaging_attributes.MESSAGING_CONSUMER_GROUP_NAME, consumer_group | ||
|
|
@@ -384,6 +407,7 @@ def _enrich_getmany_topic_span( | |
| topic: str, | ||
| partition: int, | ||
| message_count: int, | ||
| cluster_id: str | None = None, | ||
| ) -> None: | ||
| if not span.is_recording(): | ||
| return | ||
|
|
@@ -395,6 +419,7 @@ def _enrich_getmany_topic_span( | |
| topic=topic, | ||
| partition=partition, | ||
| key=None, | ||
| cluster_id=cluster_id, | ||
| ) | ||
|
|
||
| if consumer_group is not None: | ||
|
|
@@ -420,7 +445,8 @@ def _get_span_name(operation: str, topic: str): | |
|
|
||
|
|
||
| def _wrap_send( # type: ignore[reportUnusedFunction] | ||
| tracer: Tracer, async_produce_hook: ProduceHookT | None | ||
| tracer: Tracer, | ||
| async_produce_hook: ProduceHookT | None, | ||
| ) -> Callable[..., Awaitable[asyncio.Future[RecordMetadata]]]: | ||
| async def _traced_send( | ||
| func: AIOKafkaSendProto, | ||
|
|
@@ -439,6 +465,7 @@ async def _traced_send( | |
| client_id = _extract_client_id(instance.client) | ||
| key = _deserialize_key(_extract_send_key(args, kwargs)) | ||
| partition = await _extract_send_partition(instance, args, kwargs) | ||
| cluster_id = _extract_cluster_id_from_client(instance.client) | ||
| span_name = _get_span_name("send", topic) | ||
| with tracer.start_as_current_span( | ||
| span_name, kind=trace.SpanKind.PRODUCER | ||
|
|
@@ -450,6 +477,7 @@ async def _traced_send( | |
| topic=topic, | ||
| partition=partition, | ||
| key=key, | ||
| cluster_id=cluster_id, | ||
| ) | ||
| propagate.inject( | ||
| headers, | ||
|
|
@@ -461,8 +489,13 @@ async def _traced_send( | |
| await async_produce_hook(span, args, kwargs) | ||
| except Exception as hook_exception: # pylint: disable=W0703 | ||
| _LOG.exception(hook_exception) | ||
|
|
||
| return await func(*args, **kwargs) | ||
| result = await func(*args, **kwargs) | ||
| # After send(), broker has responded — refresh cluster ID in case | ||
| # metadata was not yet populated before the send started. | ||
| cluster_id = _extract_cluster_id_from_client(instance.client) | ||
| if cluster_id is not None and span.is_recording(): | ||
| span.set_attribute(_MESSAGING_KAFKA_CLUSTER_ID, cluster_id) | ||
| return result | ||
|
|
||
| return _traced_send | ||
|
|
||
|
|
@@ -475,6 +508,7 @@ async def _create_consumer_span( | |
| bootstrap_servers: str | list[str], | ||
| client_id: str, | ||
| consumer_group: str | None, | ||
| cluster_id: str | None, | ||
| args: tuple[aiokafka.TopicPartition, ...], | ||
| kwargs: dict[str, Any], | ||
| ) -> trace.Span: | ||
|
|
@@ -495,6 +529,7 @@ async def _create_consumer_span( | |
| partition=record.partition, | ||
| key=_deserialize_key(record.key), | ||
| offset=record.offset, | ||
| cluster_id=cluster_id, | ||
| ) | ||
| try: | ||
| if async_consume_hook is not None: | ||
|
|
@@ -508,7 +543,8 @@ async def _create_consumer_span( | |
|
|
||
|
|
||
| def _wrap_getone( # type: ignore[reportUnusedFunction] | ||
| tracer: Tracer, async_consume_hook: ConsumeHookT | None | ||
| tracer: Tracer, | ||
| async_consume_hook: ConsumeHookT | None, | ||
| ) -> Callable[..., Awaitable[aiokafka.ConsumerRecord[object, object]]]: | ||
| async def _traced_getone( | ||
| func: AIOKafkaGetOneProto, | ||
|
|
@@ -522,6 +558,7 @@ async def _traced_getone( | |
| bootstrap_servers = _extract_bootstrap_servers(instance._client) | ||
| client_id = _extract_client_id(instance._client) | ||
| consumer_group = _extract_consumer_group(instance) | ||
| cluster_id = _extract_cluster_id_from_client(instance._client) | ||
|
|
||
| extracted_context = propagate.extract( | ||
| record.headers, getter=_aiokafka_getter | ||
|
|
@@ -534,6 +571,7 @@ async def _traced_getone( | |
| bootstrap_servers, | ||
| client_id, | ||
| consumer_group, | ||
| cluster_id, | ||
| args, | ||
| kwargs, | ||
| ) | ||
|
|
@@ -543,7 +581,8 @@ async def _traced_getone( | |
|
|
||
|
|
||
| def _wrap_getmany( # type: ignore[reportUnusedFunction] | ||
| tracer: Tracer, async_consume_hook: ConsumeHookT | None | ||
| tracer: Tracer, | ||
| async_consume_hook: ConsumeHookT | None, | ||
| ) -> Callable[ | ||
| ..., | ||
| Awaitable[ | ||
|
|
@@ -567,6 +606,7 @@ async def _traced_getmany( | |
| bootstrap_servers = _extract_bootstrap_servers(instance._client) | ||
| client_id = _extract_client_id(instance._client) | ||
| consumer_group = _extract_consumer_group(instance) | ||
| cluster_id = _extract_cluster_id_from_client(instance._client) | ||
|
|
||
| span_name = _get_span_name( | ||
| "receive", | ||
|
|
@@ -581,6 +621,7 @@ async def _traced_getmany( | |
| client_id=client_id, | ||
| consumer_group=consumer_group, | ||
| message_count=sum(len(r) for r in records.values()), | ||
| cluster_id=cluster_id, | ||
| ) | ||
|
|
||
| for topic, topic_records in records.items(): | ||
|
|
@@ -596,6 +637,7 @@ async def _traced_getmany( | |
| topic=topic.topic, | ||
| partition=topic.partition, | ||
| message_count=len(topic_records), | ||
| cluster_id=cluster_id, | ||
| ) | ||
|
|
||
| for record in topic_records: | ||
|
|
@@ -610,6 +652,7 @@ async def _traced_getmany( | |
| bootstrap_servers, | ||
| client_id, | ||
| consumer_group, | ||
| cluster_id, | ||
| args, | ||
| kwargs, | ||
| ) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something is odd with aiokafka tests after wrapping start.