diff --git a/docs/api.rst b/docs/api.rst index a6fb49346d..7d59030033 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -41,6 +41,7 @@ Performance Monitoring .. autofunction:: sentry_sdk.api.get_current_span .. autofunction:: sentry_sdk.api.start_span .. autofunction:: sentry_sdk.api.start_transaction +.. autofunction:: sentry_sdk.api.update_current_span Distributed Tracing diff --git a/sentry_sdk/__init__.py b/sentry_sdk/__init__.py index 7b1eda172a..a37b52ff4e 100644 --- a/sentry_sdk/__init__.py +++ b/sentry_sdk/__init__.py @@ -50,6 +50,7 @@ "start_session", "end_session", "set_transaction_name", + "update_current_span", ] # Initialize the debug support after everything is loaded diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py index a4fb95e9a1..43758b4d78 100644 --- a/sentry_sdk/api.py +++ b/sentry_sdk/api.py @@ -85,6 +85,7 @@ def overload(x): "start_session", "end_session", "set_transaction_name", + "update_current_span", ] @@ -473,3 +474,82 @@ def end_session(): def set_transaction_name(name, source=None): # type: (str, Optional[str]) -> None return get_current_scope().set_transaction_name(name, source) + + +def update_current_span(op=None, name=None, attributes=None, data=None): + # type: (Optional[str], Optional[str], Optional[dict[str, Union[str, int, float, bool]]], Optional[dict[str, Any]]) -> None + """ + Update the current active span with the provided parameters. + + This function allows you to modify properties of the currently active span. + If no span is currently active, this function will do nothing. + + :param op: The operation name for the span. This is a high-level description + of what the span represents (e.g., "http.client", "db.query"). + You can use predefined constants from :py:class:`sentry_sdk.consts.OP` + or provide your own string. If not provided, the span's operation will + remain unchanged. + :type op: str or None + + :param name: The human-readable name/description for the span. This provides + more specific details about what the span represents (e.g., "GET /api/users", + "SELECT * FROM users"). If not provided, the span's name will remain unchanged. + :type name: str or None + + :param data: A dictionary of key-value pairs to add as data to the span. This + data will be merged with any existing span data. If not provided, + no data will be added. + + .. deprecated:: 2.35.0 + Use ``attributes`` instead. The ``data`` parameter will be removed + in a future version. + :type data: dict[str, Union[str, int, float, bool]] or None + + :param attributes: A dictionary of key-value pairs to add as attributes to the span. + Attribute values must be strings, integers, floats, or booleans. These + attributes will be merged with any existing span data. If not provided, + no attributes will be added. + :type attributes: dict[str, Union[str, int, float, bool]] or None + + :returns: None + + .. versionadded:: 2.35.0 + + Example:: + + import sentry_sdk + from sentry_sdk.consts import OP + + sentry_sdk.update_current_span( + op=OP.FUNCTION, + name="process_user_data", + attributes={"user_id": 123, "batch_size": 50} + ) + """ + current_span = get_current_span() + + if current_span is None: + return + + if op is not None: + current_span.op = op + + if name is not None: + # internally it is still description + current_span.description = name + + if data is not None and attributes is not None: + raise ValueError( + "Cannot provide both `data` and `attributes`. Please use only `attributes`." + ) + + if data is not None: + warnings.warn( + "The `data` parameter is deprecated. Please use `attributes` instead.", + DeprecationWarning, + stacklevel=2, + ) + attributes = data + + if attributes is not None: + current_span.update_data(attributes) diff --git a/tests/tracing/test_misc.py b/tests/tracing/test_misc.py index 651228b45e..e1de847102 100644 --- a/tests/tracing/test_misc.py +++ b/tests/tracing/test_misc.py @@ -540,3 +540,48 @@ def test_span_set_data_update_data(sentry_init, capture_events): "thread.id": mock.ANY, "thread.name": mock.ANY, } + + +def test_update_current_span(sentry_init, capture_events): + sentry_init(traces_sample_rate=1.0) + + events = capture_events() + + with sentry_sdk.start_transaction(name="test-transaction"): + with start_span(op="test-span-op", name="test-span-name"): + sentry_sdk.update_current_span( + op="updated-span-op", + name="updated-span-name", + attributes={ + "key0": "value0", + "key1": "value1", + }, + ) + + sentry_sdk.update_current_span( + op="updated-span-op-2", + ) + + sentry_sdk.update_current_span( + name="updated-span-name-3", + ) + + sentry_sdk.update_current_span( + attributes={ + "key1": "updated-value-4", + "key2": "value2", + }, + ) + + (event,) = events + span = event["spans"][0] + + assert span["op"] == "updated-span-op-2" + assert span["description"] == "updated-span-name-3" + assert span["data"] == { + "key0": "value0", + "key1": "updated-value-4", + "key2": "value2", + "thread.id": mock.ANY, + "thread.name": mock.ANY, + }