From fbbba0e49c7d11ca5e8a587532e313b6983e3766 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 29 Jul 2025 15:50:13 +0200 Subject: [PATCH 1/4] trying how this could look like --- sentry_sdk/__init__.py | 3 ++ sentry_sdk/tracing.py | 97 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/sentry_sdk/__init__.py b/sentry_sdk/__init__.py index a900f73c0b..d4d35a17e4 100644 --- a/sentry_sdk/__init__.py +++ b/sentry_sdk/__init__.py @@ -6,6 +6,8 @@ from sentry_sdk.api import * # noqa +from sentry_sdk.tracing import new_trace + from sentry_sdk.consts import VERSION # noqa __all__ = [ # noqa @@ -43,6 +45,7 @@ "start_span", "start_transaction", "trace", + "new_trace", "monitor", "logger", "start_session", diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 4cdec031e1..a23e2c8615 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -1,8 +1,12 @@ from __future__ import annotations from datetime import datetime +import functools +import inspect import json import warnings +import sentry_sdk + from opentelemetry import trace as otel_trace, context from opentelemetry.trace import ( format_trace_id, @@ -20,6 +24,7 @@ DEFAULT_SPAN_NAME, DEFAULT_SPAN_ORIGIN, BAGGAGE_HEADER_NAME, + OP, SENTRY_TRACE_HEADER_NAME, SPANSTATUS, SPANDATA, @@ -574,3 +579,95 @@ async def my_async_function(): return start_child_span_decorator(func) else: return start_child_span_decorator + + +def set_input_attributes(span, as_type, args, kwargs): + # depending on `as_type` set some attributes on the span derived from args and kwargs + pass + + +def set_output_attributes(span, as_type, result): + # depending on `as_type` set some attributes on the span derived from result + pass + + +DEFAULT_SPAN_OP = "function" + + +def new_trace(func=None, *, as_type=None, name=None): + def span_decorator(f, *a, **kw): + @functools.wraps(f) + async def async_wrapper(*args, **kwargs): + op = kw.get("op", DEFAULT_SPAN_OP) + span_name = name or f.__name__ + + with sentry_sdk.start_span( + op=op, + name=span_name, + ) as span: + set_input_attributes(span, as_type, args, kwargs) + + # run wrapped function + result = await f(*args, **kwargs) + + set_output_attributes(span, as_type, result) + return result + + @functools.wraps(f) + def sync_wrapper(*args, **kwargs): + op = kw.get("op", DEFAULT_SPAN_OP) + span_name = name or f.__name__ + + with sentry_sdk.start_span( + op=op, + name=span_name, + ) as span: + set_input_attributes(span, as_type, args, kwargs) + + # run wrapped function + result = f(*args, **kwargs) + + set_output_attributes(span, as_type, result) + return result + + if inspect.iscoroutinefunction(f): + wrapper = async_wrapper + else: + wrapper = sync_wrapper + + return wrapper + + def ai_chat_decorator(f): + return span_decorator(f, op=OP.GEN_AI_CHAT) + + def ai_agent_decorator(f): + return span_decorator(f, op=OP.GEN_AI_INVOKE_AGENT) + + def ai_tool_decorator(f): + return span_decorator(f, op=OP.GEN_AI_EXECUTE_TOOL) + + decorator_for_type = { + "ai_chat": ai_chat_decorator, + "ai_agent": ai_agent_decorator, + "ai_tool": ai_tool_decorator, + } + decorator = decorator_for_type.get(as_type, span_decorator) + + if func: + return decorator(func) + else: + return decorator + + +def update_current_span(op=None, name=None, attributes=None) -> None: + current_span = sentry_sdk.get_current_span() + + if op is not None: + current_span.op = op + + if name is not None: + current_span.name = name + + if attributes is not None: + for key, value in attributes.items(): + current_span.set_attribute(key, value) From 8e70e25cda25de0c6bb21dd4c888e0004c112f1e Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 29 Jul 2025 16:34:39 +0200 Subject: [PATCH 2/4] trying more --- sentry_sdk/tracing.py | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index a23e2c8615..22e88cf647 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -616,12 +616,16 @@ async def async_wrapper(*args, **kwargs): @functools.wraps(f) def sync_wrapper(*args, **kwargs): op = kw.get("op", DEFAULT_SPAN_OP) - span_name = name or f.__name__ + span_name = kw.get("name", f.__name__) + attributes = kw.get("attributes", {}) with sentry_sdk.start_span( op=op, name=span_name, ) as span: + for key, value in attributes.items(): + span.set_attribute(key, value) + set_input_attributes(span, as_type, args, kwargs) # run wrapped function @@ -638,13 +642,44 @@ def sync_wrapper(*args, **kwargs): return wrapper def ai_chat_decorator(f): - return span_decorator(f, op=OP.GEN_AI_CHAT) + attributes = { + "gen_ai.operation.name": "chat", + } + + return span_decorator( + f, + op=OP.GEN_AI_CHAT, + name="chat [MODEL]", + attributes=attributes, + ) def ai_agent_decorator(f): - return span_decorator(f, op=OP.GEN_AI_INVOKE_AGENT) + span_name = name or f.__name__ + attributes = { + "gen_ai.agent.name": span_name, + "gen_ai.operation.name": "invoke_agent", + } + + return span_decorator( + f, + op=OP.GEN_AI_INVOKE_AGENT, + name=f"invoke_agent {span_name}", + attributes=attributes, + ) def ai_tool_decorator(f): - return span_decorator(f, op=OP.GEN_AI_EXECUTE_TOOL) + span_name = name or f.__name__ + attributes = { + "gen_ai.tool.name": span_name, + "gen_ai.operation.name": "execute_tool", + } + + return span_decorator( + f, + op=OP.GEN_AI_EXECUTE_TOOL, + name=f"execute_tool {span_name}", + attributes=attributes, + ) decorator_for_type = { "ai_chat": ai_chat_decorator, From 1fe90379c90149d9dcb295104b7483bc1dee29b1 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Tue, 29 Jul 2025 16:45:14 +0200 Subject: [PATCH 3/4] more --- sentry_sdk/tracing.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 22e88cf647..3dba03f6b8 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -599,12 +599,16 @@ def span_decorator(f, *a, **kw): @functools.wraps(f) async def async_wrapper(*args, **kwargs): op = kw.get("op", DEFAULT_SPAN_OP) - span_name = name or f.__name__ + span_name = kw.get("name", f.__name__) + attributes = kw.get("attributes", {}) with sentry_sdk.start_span( op=op, name=span_name, ) as span: + for key, value in attributes.items(): + span.set_attribute(key, value) + set_input_attributes(span, as_type, args, kwargs) # run wrapped function From 635df8c535263f5ce2a097e151e7fcfa57b4f516 Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Wed, 30 Jul 2025 13:49:26 +0200 Subject: [PATCH 4/4] . --- sentry_sdk/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/__init__.py b/sentry_sdk/__init__.py index d4d35a17e4..50ab118d0d 100644 --- a/sentry_sdk/__init__.py +++ b/sentry_sdk/__init__.py @@ -6,7 +6,7 @@ from sentry_sdk.api import * # noqa -from sentry_sdk.tracing import new_trace +from sentry_sdk.tracing import new_trace, update_current_span from sentry_sdk.consts import VERSION # noqa @@ -46,6 +46,7 @@ "start_transaction", "trace", "new_trace", + "update_current_span", "monitor", "logger", "start_session",