Skip to content

Commit 4a8db0c

Browse files
committed
PYTHON-5947 Add OpenTelemetry operation spans
Give every public API call an operation span containing one command span per command sent to the server, per the OpenTelemetry driver specification. The span covers all retry attempts of one _retry_internal call, so retries appear as sibling command spans rather than being collapsed into one. killCursors and endSessions bypass the retry layer and start their spans at the call site instead, as does an unacknowledged client bulk write, which never reaches the command-span code that would fill in its namespace. Cursor-creating operations (find, aggregate, listCollections, listIndexes) are covered here, but only for the command that creates the cursor. Spans for caller-driven getMores come in a later change, as do transaction spans. _otel.py also takes over the specification's naming and attribute rules, so _telemetry.py deals only with span lifecycles and a specification change need not touch it. Namespace parsing moves to helpers_shared._split_namespace.
1 parent a5d3f6d commit 4a8db0c

32 files changed

Lines changed: 2319 additions & 311 deletions

.evergreen/generated_configs/variants.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,10 @@ buildvariants:
445445
# Otel tests
446446
- name: otel-rhel8
447447
tasks:
448-
- name: .test-non-standard .standalone-noauth-nossl
448+
- name: .test-non-standard .replica_set-noauth-ssl
449+
- name: .test-non-standard .sharded_cluster-auth-ssl .python-3.14
450+
- name: .test-non-standard .sharded_cluster-auth-ssl .python-pypy3.11
451+
- name: .test-non-standard .standalone-noauth-nossl .python-3.10
449452
display_name: OTel RHEL8
450453
run_on:
451454
- rhel87-small

.evergreen/scripts/generate_config.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,23 @@ def create_otel_variants():
458458
expansions = dict(TEST_NAME="otel", COVERAGE="1")
459459
return [
460460
create_variant(
461-
[".test-non-standard .standalone-noauth-nossl"],
461+
[
462+
# All three topologies, subset to keep the task count at 22.
463+
#
464+
# Replica set in full: the only topology where transaction spans
465+
# run at all (they are skipped on standalone and sharded), and
466+
# the only one covering free-threaded Python.
467+
".test-non-standard .replica_set-noauth-ssl",
468+
# Sharded adds mongos, which rewrites commands and reports a
469+
# different server.address, plus auth and ssl, which exercise
470+
# sensitive-command redaction. Newest CPython across server
471+
# versions, and PyPy for the alternate implementation.
472+
".test-non-standard .sharded_cluster-auth-ssl .python-3.14",
473+
".test-non-standard .sharded_cluster-auth-ssl .python-pypy3.11",
474+
# Standalone only for its min-deps tasks, which resolve
475+
# opentelemetry-api down to the floor in requirements/.
476+
".test-non-standard .standalone-noauth-nossl .python-3.10",
477+
],
462478
get_variant_name("OTel", host),
463479
host=host,
464480
tags=["pr"],

bson/json_util.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,20 +1115,18 @@ def _truncate_documents(obj: Any, max_length: int) -> tuple[Any, int]:
11151115
if hasattr(obj, "items"):
11161116
truncated: Any = {}
11171117
for k, v in obj.items():
1118-
truncated_v, remaining = _truncate_documents(v, remaining)
1119-
if truncated_v:
1120-
truncated[k] = truncated_v
11211118
if remaining <= 0:
11221119
break
1120+
truncated_v, remaining = _truncate_documents(v, remaining)
1121+
truncated[k] = truncated_v
11231122
return truncated, remaining
11241123
elif hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes)):
11251124
truncated: Any = [] # type:ignore[no-redef]
11261125
for v in obj:
1127-
truncated_v, remaining = _truncate_documents(v, remaining)
1128-
if truncated_v:
1129-
truncated.append(truncated_v)
11301126
if remaining <= 0:
11311127
break
1128+
truncated_v, remaining = _truncate_documents(v, remaining)
1129+
truncated.append(truncated_v)
11321130
return truncated, remaining
11331131
else:
11341132
return _truncate(obj, remaining)

doc/changelog.rst

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@ PyMongo 4.18 brings a number of changes including:
2929
attempts, so consumers can correlate a retried operation's events. As a
3030
result, ``operation_id`` is no longer equal to the per-attempt ``request_id``
3131
for these operations.
32-
- Added optional OpenTelemetry command-span support, conforming to the
32+
- Added optional OpenTelemetry tracing support, conforming to the
3333
`OpenTelemetry driver specification <https://github.com/mongodb/specifications/blob/master/source/open-telemetry/open-telemetry.md>`_.
34-
Enable it with the ``tracing`` :class:`~pymongo.mongo_client.MongoClient`
35-
option or the ``OTEL_PYTHON_INSTRUMENTATION_MONGODB_ENABLED`` environment
36-
variable. Install the ``opentelemetry-api`` package, or use the
34+
Every public API call produces an operation span, which contains one span
35+
per command sent to the server. Inside a transaction, those operation spans
36+
nest under a ``transaction`` span. Enable it with the
37+
``tracing`` :class:`~pymongo.mongo_client.MongoClient` option or the
38+
``OTEL_PYTHON_INSTRUMENTATION_MONGODB_ENABLED`` environment variable.
39+
Install the ``opentelemetry-api`` package, or use the
3740
``pymongo[opentelemetry]`` extra, to enable this feature.
3841
- Fixed a potential out-of-bounds read in the C extension when decoding an
3942
array of BSON documents. An embedded document whose declared length exceeds

0 commit comments

Comments
 (0)