Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7f138aa
Document opentelemetry.version
ocelotl Nov 1, 2026
96731e7
Document opentelemetry.environment_variables
ocelotl Nov 1, 2026
08e12c5
Document opentelemetry.util.types
ocelotl Nov 1, 2026
ef06f94
Document opentelemetry.util.re
ocelotl Nov 1, 2026
bcd12b3
Document opentelemetry.util._once
ocelotl Nov 1, 2026
35f5481
Document opentelemetry.util._decorator
ocelotl Nov 1, 2026
faf9277
Document opentelemetry.util._importlib_metadata
ocelotl Nov 1, 2026
ccbc144
Document opentelemetry.util._providers
ocelotl Nov 1, 2026
e739b52
Document opentelemetry.context
ocelotl Nov 1, 2026
d5b4297
Document opentelemetry.context.context
ocelotl Nov 1, 2026
3a49c0f
Document opentelemetry.context.contextvars_context
ocelotl Nov 1, 2026
fc80112
Document opentelemetry.attributes
ocelotl Nov 1, 2026
f18357c
Document opentelemetry.baggage
ocelotl Nov 1, 2026
5b00be5
Document opentelemetry.baggage.propagation
ocelotl Nov 1, 2026
2674bb7
Document opentelemetry.propagators.textmap
ocelotl Nov 1, 2026
2f9253b
Document opentelemetry.propagators.composite
ocelotl Nov 1, 2026
7c4d13c
Document opentelemetry.propagators._envcarrier
ocelotl Nov 1, 2026
b92edb2
Document opentelemetry.propagate
ocelotl Nov 1, 2026
42e2685
Document opentelemetry.trace
ocelotl Nov 1, 2026
23781bb
Document opentelemetry.trace.span
ocelotl Nov 1, 2026
405c42a
Document opentelemetry.trace.status
ocelotl Nov 1, 2026
7819937
Document opentelemetry.trace.propagation
ocelotl Nov 1, 2026
2d87079
Document opentelemetry.trace.propagation.tracecontext
ocelotl Nov 1, 2026
b06ace5
Document opentelemetry._logs._internal
ocelotl Nov 1, 2026
1298ee8
Document opentelemetry._logs.severity
ocelotl Nov 1, 2026
55707a2
Document opentelemetry.metrics._internal
ocelotl Nov 1, 2026
430e0d8
Document opentelemetry.metrics._internal.instrument
ocelotl Nov 1, 2026
43bd5d1
Document opentelemetry.metrics._internal.observation
ocelotl Nov 1, 2026
e831d77
Add the changelog fragment
ocelotl Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/5876.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-api`: document every hand-written module, class and callable
279 changes: 274 additions & 5 deletions opentelemetry-api/src/opentelemetry/_logs/_internal/__init__.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

"""Severity levels of a log record.

:class:`SeverityNumber` holds the numeric severity scale of the OpenTelemetry
log data model. The scale is ordered, so a larger value is a more severe
event, and it is finer grained than the levels of the standard :mod:`logging`
module: each familiar level has ``2``, ``3`` and ``4`` variants, for example
:attr:`~SeverityNumber.ERROR`, :attr:`~SeverityNumber.ERROR2`.
"""

import enum


Expand Down
153 changes: 153 additions & 0 deletions opentelemetry-api/src/opentelemetry/attributes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@

# pyright: reportUnnecessaryIsInstance=false

"""Validation and size-limiting of telemetry attributes.

The OpenTelemetry specification restricts which Python values may be used as
an attribute value, and lets an SDK cap both the number of attributes and the
length of each string. This module holds the two pieces that enforce those
rules: ``_clean_attribute_value``, which coerces or drops an invalid
value, and :class:`BoundedAttributes`, the mapping that spans, log records and
metric measurements store their attributes in.

A value that cannot be represented is coerced to :class:`str` when the object
defines ``__str__`` or ``__repr__``, and is replaced with ``None`` otherwise.
The pair stays in the mapping either way and ``dropped`` is not incremented. A
key is treated differently: a key of a nested mapping that cannot be converted
is dropped along with its value. A warning is logged in every case. Note that
coercion calls ``__str__`` on the object, so a ``__str__`` that raises does
propagate to the caller.
"""

import copy
import logging
import threading
Expand All @@ -21,6 +39,19 @@
# If neither is defined it uses the base class's `object.__repr__` method, which returns a string that is hard to understand.
# So in that case we drop the key/value pair.
def _is_non_custom_str(key: Any) -> bool:
"""Report whether converting ``key`` to a string would produce something useful.

Args:
key: The object about to be converted with :func:`str`. It is an
attribute key or an attribute value.

Returns:
``True`` when the type of ``key`` overrides ``__str__`` or
``__repr__``, so :func:`str` yields a meaningful result. ``False`` when
neither is overridden, in which case :func:`str` would return the
default ``<module.Class object at 0x...>`` form and the caller should
drop the object instead.
"""
return type(key).__str__ is not object.__str__ or type(key).__repr__ is not object.__repr__


Expand Down Expand Up @@ -49,6 +80,17 @@ def _clean_attribute_value(
If this fails, the value is replaced with None. Sequence's are converted to tuples and mappings
are copied into new dicts.

A mapping key that is empty, or that is not a string and cannot be
converted to one, is dropped from the result and a warning is logged.

Args:
value: The attribute value to clean. Any Python object is accepted;
one that is not a ``AnyValue`` is
coerced or replaced rather than rejected.
max_string_value_length: Maximum length of a string value, in
characters. A longer string is truncated to this length and a
warning is logged. Pass ``None`` to apply no limit.

Returns:
The recursively cleaned AnyValue.
"""
Expand Down Expand Up @@ -144,6 +186,14 @@ def __init__(
max_value_len: int | None = None,
extended_attributes: bool = False,
) -> None:
"""Build the mapping and store ``attributes`` in it.

Every parameter is documented on the class itself, since that is the
form the API reference renders.

Raises:
ValueError: If ``maxlen`` or ``max_value_len`` is negative.
"""
if maxlen is not None and maxlen < 0:
raise ValueError("maxlen must be valid int greater or equal to 0")
if max_value_len is not None and max_value_len < 0:
Expand All @@ -163,16 +213,52 @@ def __init__(
self._immutable = immutable

def __repr__(self) -> str:
"""Render the stored attributes as a plain dict literal.

Returns:
The current attributes formatted as a :class:`dict` would be. The
count of dropped attributes is not included.
"""
return f"{dict(self._dict)}"

def __getitem__(self, key: str) -> types.AnyValue:
"""Return the value stored under ``key``.

Args:
key: The attribute name to look up.

Returns:
The cleaned value stored under ``key``.

Raises:
KeyError: If ``key`` is absent, which includes the case where it
was dropped for exceeding the capacity.
"""
return self._dict[key]

def _raise_if_immutable(self) -> None:
"""Reject a mutation when this instance was created immutable.

Raises:
TypeError: If this instance is immutable.
"""
if self._immutable:
raise TypeError("Cannot mutate immutable BoundedAttributes")

def __setitem__(self, key: str, value: types.AnyValue) -> None:
"""Clean ``value`` and store it under ``key`` if there is capacity.

An invalid key, or a key that does not fit within the capacity, is
counted in ``dropped`` rather than stored, and no exception is raised.

Args:
key: The attribute name. It must be a non-empty :class:`str`.
value: The attribute value, cleaned by
``_clean_attribute_value`` before being stored.

Raises:
TypeError: If this instance is immutable.
"""
self._raise_if_immutable()
if self.maxlen is not None and self.maxlen == 0:
with self._lock:
Expand All @@ -191,6 +277,16 @@ def __setitem__(self, key: str, value: types.AnyValue) -> None:
self._setitem_locked(key, cleaned)

def _set_items(self, attributes: Mapping[str, types.AnyValue]) -> None:
"""Clean and store several attributes under a single lock acquisition.

Args:
attributes: The attributes to store. Each pair is cleaned by
``_clean_attribute_value``, and any pair dropped during
cleaning is counted in ``dropped``.

Raises:
TypeError: If this instance is immutable.
"""
self._raise_if_immutable()
if self.maxlen is not None and self.maxlen == 0:
with self._lock:
Expand All @@ -203,6 +299,18 @@ def _set_items(self, attributes: Mapping[str, types.AnyValue]) -> None:
self._setitem_locked(key, value)

def _setitem_locked(self, key: str, value: types.AnyValue) -> None:
"""Store an already cleaned pair, assuming the lock is held.

Re-setting a key that is already present moves it to the end and never
counts against the capacity. Adding a new key once the mapping is full
increments ``dropped`` and stores nothing. The full-mapping warning is
logged at most once per instance.

Args:
key: The attribute name, already validated as a non-empty
:class:`str`.
value: The attribute value, already cleaned.
"""
if key in self._dict:
del self._dict[key]
elif self.maxlen is not None and len(self._dict) >= self.maxlen:
Expand All @@ -217,19 +325,56 @@ def _setitem_locked(self, key: str, value: types.AnyValue) -> None:
self._dict[key] = value

def __delitem__(self, key: str) -> None:
"""Remove the attribute stored under ``key``.

Args:
key: The attribute name to remove.

Raises:
TypeError: If this instance is immutable.
KeyError: If ``key`` is absent.
"""
self._raise_if_immutable()
del self._dict[key]

def __iter__(self):
"""Iterate over the attribute names.

A mutable instance is iterated over a snapshot of the names taken
under the lock, so adding an attribute from another thread during
iteration does not disturb it.

Returns:
An iterator over the attribute names, in insertion order.
"""
if self._immutable:
return iter(self._dict)
with self._lock:
return iter(list(self._dict))

def __len__(self) -> int:
"""Return how many attributes are stored.

Returns:
The number of attributes currently held. Dropped attributes are
not counted; read ``dropped`` for those.
"""
return len(self._dict)

def __deepcopy__(self, memo: dict[int, Any]) -> "BoundedAttributes":
"""Return a deep copy carrying the same limits and dropped count.

The stored values are copied directly rather than re-cleaned, since
they were cleaned when they were first stored.

Args:
memo: The memo dict :func:`copy.deepcopy` uses to track objects it
has already copied.

Returns:
A new instance holding deep copies of the values, with the same
``maxlen``, ``max_value_len``, immutability and ``dropped`` count.
"""
copy_ = BoundedAttributes(
maxlen=self.maxlen,
immutable=self._immutable,
Expand All @@ -244,4 +389,12 @@ def __deepcopy__(self, memo: dict[int, Any]) -> "BoundedAttributes":
return copy_

def copy(self) -> MutableMapping[str, types.AnyValue]:
"""Return the stored attributes as an ordinary mutable mapping.

The result is a shallow copy and carries no capacity limit, so writing
to it neither affects this instance nor drops anything.

Returns:
A plain :class:`dict` holding the current attributes.
"""
return self._dict.copy()
70 changes: 70 additions & 0 deletions opentelemetry-api/src/opentelemetry/baggage/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

"""Name/value pairs that travel alongside a trace.

Baggage is a set of name/value pairs carried in the current
:class:`~opentelemetry.context.Context` and propagated across process
boundaries by :class:`~opentelemetry.baggage.propagation.W3CBaggagePropagator`.
It lets a value set early in a request, such as an account identifier, be read
much later without threading it through every call.

Every function here that changes the baggage returns a new
:class:`~opentelemetry.context.Context` rather than modifying one, matching the
immutability of the context API. The reading functions, :func:`get_all` and
:func:`get_baggage`, return the pairs themselves.

Baggage is not added to spans automatically, and it is sent to every service
downstream, so a value that is sensitive or large does not belong in it.
"""

from collections.abc import Mapping
from logging import getLogger
from string import ascii_letters, digits
Expand Down Expand Up @@ -92,26 +109,79 @@ def clear(context: Context | None = None) -> Context:


def _get_baggage_value(context: Context | None = None) -> dict[str, object]:
"""Read the raw baggage dict out of a context.

Args:
context: The context to read from. If ``None``, the current context is
used.

Returns:
The baggage dict stored in the context, or an empty dict when the
context holds no baggage or holds something that is not a dict. The
returned dict is the stored one, so callers that intend to modify it
must copy it first.
"""
baggage = get_value(_BAGGAGE_KEY, context=context)
if isinstance(baggage, dict):
return baggage
return {}


def _is_valid_key(name: str) -> bool:
"""Report whether ``name`` is usable as a baggage name.

Args:
name: The candidate baggage name.

Returns:
``True`` when ``name`` is non-empty and made up only of RFC 7230 token
characters, which is what the W3C Baggage header format permits.
"""
return bool(name) and _KEY_CHARS.issuperset(name)


def _is_valid_property(property: str) -> bool:
"""Report whether ``property`` is a valid baggage list-member property.

A property is the optional metadata that may follow a baggage value after
a semicolon. It is either a bare key or a ``key=value`` pair.

Args:
property: One property, with no leading semicolon.

Returns:
``True`` when the key half is a valid baggage name and the value half,
if present, holds only permitted characters.
"""
key, separator, value = property.partition("=")
return _is_valid_key(key.strip(" \t")) and (not separator or _VALUE_CHARS.issuperset(value.strip(" \t")))


def _is_valid_value(value: object) -> bool:
"""Report whether ``value`` is usable as a baggage value.

Args:
value: The candidate baggage value. It is converted with :func:`str`
before being checked, and any semicolon-delimited property that
follows it is checked too.

Returns:
``True`` when the value holds only printable US-ASCII characters other
than the delimiters, and every property following it is valid.
"""
value, *properties = str(value).split(";")
# W3C Baggage allows optional whitespace between the value and ";"
return _VALUE_CHARS.issuperset(value.rstrip(" \t")) and all(map(_is_valid_property, properties))


def _is_valid_pair(key: str, value: str) -> bool:
"""Report whether a name/value pair may be put in a baggage header.

Args:
key: The candidate baggage name.
value: The candidate baggage value.

Returns:
``True`` when both halves are valid on their own.
"""
return _is_valid_key(key) and _is_valid_value(value)
Loading
Loading