Skip to content

Commit 1162254

Browse files
committed
First version of helpers for better manual instrumentation
1 parent 70e2b59 commit 1162254

File tree

3 files changed

+158
-2
lines changed

3 files changed

+158
-2
lines changed

sentry_sdk/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"start_session",
5151
"end_session",
5252
"set_transaction_name",
53+
"update_current_span",
5354
]
5455

5556
# Initialize the debug support after everything is loaded

sentry_sdk/api.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from sentry_sdk._init_implementation import init
77
from sentry_sdk.consts import INSTRUMENTER
88
from sentry_sdk.scope import Scope, _ScopeManager, new_scope, isolation_scope
9-
from sentry_sdk.tracing import NoOpSpan, Transaction, trace
9+
from sentry_sdk.tracing import NoOpSpan, Transaction, trace, new_trace
1010
from sentry_sdk.crons import monitor
1111

1212
from typing import TYPE_CHECKING
@@ -81,10 +81,12 @@ def overload(x):
8181
"start_span",
8282
"start_transaction",
8383
"trace",
84+
"new_trace",
8485
"monitor",
8586
"start_session",
8687
"end_session",
8788
"set_transaction_name",
89+
"update_current_span",
8890
]
8991

9092

@@ -473,3 +475,25 @@ def end_session():
473475
def set_transaction_name(name, source=None):
474476
# type: (str, Optional[str]) -> None
475477
return get_current_scope().set_transaction_name(name, source)
478+
479+
480+
def update_current_span(op=None, name=None, attributes=None):
481+
# type: (Optional[str], Optional[str], Optional[dict[str, Union[str, int, float, bool]]]) -> None
482+
"""
483+
Update the current span with the given parameters.
484+
485+
:param op: The operation of the span.
486+
:param name: The name of the span.
487+
:param attributes: The attributes of the span.
488+
"""
489+
current_span = get_current_span()
490+
491+
if op is not None:
492+
current_span.op = op
493+
494+
if name is not None:
495+
current_span.name = name
496+
497+
if attributes is not None:
498+
for key, value in attributes.items():
499+
current_span.set_data(key, value)

sentry_sdk/tracing.py

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from decimal import Decimal
2+
import functools
3+
import inspect
24
import uuid
35
import warnings
46
from datetime import datetime, timedelta, timezone
57
from enum import Enum
68

79
import sentry_sdk
8-
from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS, SPANDATA
10+
from sentry_sdk.consts import INSTRUMENTER, OP, SPANSTATUS, SPANDATA
911
from sentry_sdk.profiler.continuous_profiler import get_profiler_id
1012
from sentry_sdk.utils import (
1113
get_current_thread_meta,
@@ -1371,6 +1373,135 @@ async def my_async_function():
13711373
return start_child_span_decorator
13721374

13731375

1376+
def set_span_attributes(span, attributes):
1377+
# type: (Span, dict[str, Any]) -> None
1378+
for key, value in attributes.items():
1379+
span.set_data(key, value)
1380+
1381+
1382+
def set_input_attributes(span, template, args, kwargs):
1383+
# depending on `template` set some attributes on the span derived from args and kwargs
1384+
pass
1385+
1386+
1387+
def set_output_attributes(span, template, result):
1388+
# depending on `template` set some attributes on the span derived from result
1389+
pass
1390+
1391+
1392+
def new_trace(func=None, *, as_type="span", name=None):
1393+
"""
1394+
Decorator to start a child span.
1395+
1396+
:param func: The function to trace.
1397+
:param as_type: The type of span to create.
1398+
:param name: The name of the span.
1399+
"""
1400+
1401+
def span_decorator(f, *a, **kw):
1402+
@functools.wraps(f)
1403+
async def async_wrapper(*args, **kwargs):
1404+
# type: (*Any, **Any) -> Any
1405+
op = kw.get("op", OP.FUNCTION)
1406+
span_name = kw.get("name", f.__name__)
1407+
attributes = kw.get("attributes", {})
1408+
1409+
with sentry_sdk.start_span(
1410+
op=op,
1411+
name=span_name,
1412+
) as span:
1413+
set_span_attributes(span, attributes)
1414+
set_input_attributes(span, as_type, args, kwargs)
1415+
1416+
result = await f(*args, **kwargs)
1417+
1418+
set_output_attributes(span, as_type, result)
1419+
1420+
return result
1421+
1422+
@functools.wraps(f)
1423+
def sync_wrapper(*args, **kwargs):
1424+
# type: (*Any, **Any) -> Any
1425+
op = kw.get("op", OP.FUNCTION)
1426+
span_name = kw.get("name", f.__name__)
1427+
attributes = kw.get("attributes", {})
1428+
1429+
with sentry_sdk.start_span(
1430+
op=op,
1431+
name=span_name,
1432+
) as span:
1433+
set_span_attributes(span, attributes)
1434+
set_input_attributes(span, as_type, args, kwargs)
1435+
1436+
result = f(*args, **kwargs)
1437+
1438+
set_output_attributes(span, as_type, result)
1439+
1440+
return result
1441+
1442+
if inspect.iscoroutinefunction(f):
1443+
wrapper = async_wrapper
1444+
else:
1445+
wrapper = sync_wrapper
1446+
1447+
return wrapper
1448+
1449+
def ai_chat_decorator(f):
1450+
# type: (Optional[Callable[P, R]]) -> Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]]
1451+
attributes = {
1452+
"gen_ai.operation.name": "chat",
1453+
}
1454+
1455+
return span_decorator(
1456+
f,
1457+
op=OP.GEN_AI_CHAT,
1458+
name="chat [MODEL]",
1459+
attributes=attributes,
1460+
)
1461+
1462+
def ai_agent_decorator(f):
1463+
# type: (Optional[Callable[P, R]]) -> Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]]
1464+
span_name = name or f.__name__
1465+
attributes = {
1466+
"gen_ai.agent.name": span_name,
1467+
"gen_ai.operation.name": "invoke_agent",
1468+
}
1469+
1470+
return span_decorator(
1471+
f,
1472+
op=OP.GEN_AI_INVOKE_AGENT,
1473+
name=f"invoke_agent {span_name}",
1474+
attributes=attributes,
1475+
)
1476+
1477+
def ai_tool_decorator(f):
1478+
# type: (Optional[Callable[P, R]]) -> Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]]
1479+
span_name = name or f.__name__
1480+
attributes = {
1481+
"gen_ai.tool.name": span_name,
1482+
"gen_ai.operation.name": "execute_tool",
1483+
}
1484+
1485+
return span_decorator(
1486+
f,
1487+
op=OP.GEN_AI_EXECUTE_TOOL,
1488+
name=f"execute_tool {span_name}",
1489+
attributes=attributes,
1490+
)
1491+
1492+
decorator_for_type = {
1493+
"ai_chat": ai_chat_decorator,
1494+
"ai_agent": ai_agent_decorator,
1495+
"ai_tool": ai_tool_decorator,
1496+
}
1497+
decorator = decorator_for_type.get(as_type, span_decorator)
1498+
1499+
if func:
1500+
return decorator(func)
1501+
else:
1502+
return decorator
1503+
1504+
13741505
# Circular imports
13751506

13761507
from sentry_sdk.tracing_utils import (

0 commit comments

Comments
 (0)