Skip to content

feat(kafka-python): honour the messaging semconv stability opt-in - #5318

Open
ocelotl wants to merge 4 commits into
open-telemetry:mainfrom
ocelotl:kafka_python_messaging_semconv_opt_in
Open

ocelotl wants to merge 4 commits into
open-telemetry:mainfrom
ocelotl:kafka_python_messaging_semconv_opt_in

Conversation

@ocelotl

@ocelotl ocelotl commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Description

opentelemetry-instrumentation-kafka-python emits the pre-1.17 messaging attributes unconditionally and passes https://opentelemetry.io/schemas/1.11.0 to get_tracer as a literal, so OTEL_SEMCONV_STABILITY_OPT_IN has no effect on it. The MESSAGING signal type and the messaging / messaging/dup modes already exist in opentelemetry-instrumentation/src/opentelemetry/instrumentation/_semconv.py; this wires the package up to them.

KafkaInstrumentor._instrument resolves the MESSAGING mode once and passes it to _wrap_send and _wrap_next, _get_attributes builds the attribute dict for the mode in force, and the schema URL comes from _get_schema_url_for_signal_types instead of a literal.

mode attributes emitted schema URL
unset (default) old only, unchanged from today https://opentelemetry.io/schemas/1.11.0
messaging stable only https://opentelemetry.io/schemas/1.44.0
messaging/dup both https://opentelemetry.io/schemas/1.44.0

The old and stable attributes correspond as follows:

old stable
messaging.destination messaging.destination.name
messaging.kafka.partition (int) messaging.destination.partition.id (str)
messaging.url server.address and server.port

messaging.url carries the whole bootstrap_servers value JSON-encoded. The stable conventions define server.address as one host and server.port as one port, so _bootstrap_servers_attributes parses the host and the port out of the first entry rather than copying the JSON across. It handles the spellings kafka-python accepts (one host:port entry, a comma-separated string of them, a list of them), strips a leading security protocol as in SASL_SSL://broker:9093, and keeps a bracketed or bare IPv6 literal whole rather than splitting it on its last colon. This follows _set_bootstrap_servers_attributes in opentelemetry-instrumentation-confluent-kafka.

messaging.system is emitted in every mode, since the key did not change. The stable modes also add messaging.operation.name and messaging.operation.type, which the old conventions had no equivalent of.

Default behaviour is unchanged. The four TestKafkaSampling tests added in #5301 assert the exact attribute dict handed to the sampler and pass without modification to their expectations, which is the check that the default path did not move.

Refs #3495

Decisions worth overturning

Two choices in _bootstrap_servers_attributes follow what opentelemetry-instrumentation-confluent-kafka already does. Both are easy to reverse.

  • Only the first bootstrap_servers entry is reported. server.address is singular, and a bootstrap list names candidates rather than the broker actually used.
  • server.port is left out when the first entry names no port, as with kafka-python's own default of the bare string localhost. kafka-python does substitute 9092 in that case, so emitting server.port = 9092 would describe the connection accurately. It is left out because that value comes from a library default rather than from anything the user configured.

Gaps

  • Span names are untouched: {topic} send and {topic} receive in every mode, matching opentelemetry-instrumentation-aiokafka and opentelemetry-instrumentation-confluent-kafka. The stable conventions want {operation} {destination}. That is a separate behaviour change.
  • messaging.client.id and messaging.consumer.group.name are not emitted. Adding them is new data rather than a migration of existing data.
  • The other messaging instrumentations with the same problem (aio-pika, pika, confluent-kafka, boto3sqs, celery, botocore) need the same treatment, one PR each.
  • opentelemetry-instrumentation-aiokafka is the inverse case: it emits the stable attributes unconditionally with no way to ask for the old ones. Not addressed here.

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Checklist:

  • I am a human and not a bot
  • I have run tox -e precommit
  • I have run tox -e typecheck
  • I have run the relevant tests and linting tox environments
  • This requires the following PR in core:

Notes on the checklist:

  • ruff 0.16.1 (check and format), the version pinned in .pre-commit-config.yaml, and pylint 4.0.5 against .pylintrc were run directly on instrumentation/opentelemetry-instrumentation-kafka-python rather than through tox -e precommit. Both are clean; pylint scores 10.00/10.
  • tox -e typecheck was not run. instrumentation/opentelemetry-instrumentation-kafka-python is not in the include list of [tool.pyright] in pyproject.toml, so the environment does not cover this package.
  • The tests were run against both test-requirements.oldest.txt (kafka-python 2.3.2, Python 3.10) and test-requirements.latest.txt (kafka-python 3.0.8, Python 3.12), with the core packages installed from opentelemetry-python@main the way tox.ini does. 32 passed in both.

The instrumentation emitted the pre-1.17 messaging attributes
unconditionally and passed a hardcoded 1.11.0 schema URL, so
OTEL_SEMCONV_STABILITY_OPT_IN had no effect on it.

Read the MESSAGING opt-in mode and pass it to the producer and consumer
wrappers. The default mode keeps emitting messaging.destination,
messaging.kafka.partition and messaging.url unchanged. The messaging
mode emits messaging.destination.name,
messaging.destination.partition.id, server.address, server.port,
messaging.operation.name and messaging.operation.type instead, and
messaging/dup emits both generations. messaging.system is emitted in
every mode.

messaging.url carries the whole bootstrap_servers value JSON-encoded.
The stable conventions define server.address as one host and
server.port as one port, so the host and the port of the first entry
are parsed out of it rather than the JSON being copied across, the way
opentelemetry-instrumentation-confluent-kafka already does it. An entry
naming no port, or a port that is not a number, leaves server.port
unset instead of recording a wrong one.

The schema URL now follows the mode in force rather than being a
literal, so a consumer is told which generation of attributes it
received.

Refs open-telemetry#3495
Instrument a producer and a consumer under each of the three modes and
assert the exact attribute names, the value types and the schema URL
the span carries. messaging.destination.partition.id is a string while
messaging.kafka.partition was an integer, so both are checked.
Describe the messaging opt-in values the instrumentation now reads, and
give the mapping from each old attribute to the stable one that
replaces it.
@ocelotl
ocelotl requested a review from a team as a code owner November 10, 2026 19:20
@opentelemetry-pr-dashboard

opentelemetry-pr-dashboard Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Pull request dashboard status

Waiting on reviewers · refreshed 2026-11-10 19:27 UTC

Review the latest changes.

Status above doesn't look right?
  • Just replied or pushed? Anything around or after the refresh time above may not be picked up yet — give it a few minutes.
  • Anything look wrong? Report it with what you expected; it helps us improve the dashboard.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant