|
1 | 1 | from __future__ import annotations |
2 | 2 | from datetime import datetime |
| 3 | +import functools |
| 4 | +import inspect |
3 | 5 | import json |
4 | 6 | import warnings |
5 | 7 |
|
| 8 | +import sentry_sdk |
| 9 | + |
6 | 10 | from opentelemetry import trace as otel_trace, context |
7 | 11 | from opentelemetry.trace import ( |
8 | 12 | format_trace_id, |
|
20 | 24 | DEFAULT_SPAN_NAME, |
21 | 25 | DEFAULT_SPAN_ORIGIN, |
22 | 26 | BAGGAGE_HEADER_NAME, |
| 27 | + OP, |
23 | 28 | SENTRY_TRACE_HEADER_NAME, |
24 | 29 | SPANSTATUS, |
25 | 30 | SPANDATA, |
@@ -574,3 +579,95 @@ async def my_async_function(): |
574 | 579 | return start_child_span_decorator(func) |
575 | 580 | else: |
576 | 581 | return start_child_span_decorator |
| 582 | + |
| 583 | + |
| 584 | +def set_input_attributes(span, as_type, args, kwargs): |
| 585 | + # depending on `as_type` set some attributes on the span derived from args and kwargs |
| 586 | + pass |
| 587 | + |
| 588 | + |
| 589 | +def set_output_attributes(span, as_type, result): |
| 590 | + # depending on `as_type` set some attributes on the span derived from result |
| 591 | + pass |
| 592 | + |
| 593 | + |
| 594 | +DEFAULT_SPAN_OP = "function" |
| 595 | + |
| 596 | + |
| 597 | +def new_trace(func=None, *, as_type=None, name=None): |
| 598 | + def span_decorator(f, *a, **kw): |
| 599 | + @functools.wraps(f) |
| 600 | + async def async_wrapper(*args, **kwargs): |
| 601 | + op = kw.get("op", DEFAULT_SPAN_OP) |
| 602 | + span_name = name or f.__name__ |
| 603 | + |
| 604 | + with sentry_sdk.start_span( |
| 605 | + op=op, |
| 606 | + name=span_name, |
| 607 | + ) as span: |
| 608 | + set_input_attributes(span, as_type, args, kwargs) |
| 609 | + |
| 610 | + # run wrapped function |
| 611 | + result = await f(*args, **kwargs) |
| 612 | + |
| 613 | + set_output_attributes(span, as_type, result) |
| 614 | + return result |
| 615 | + |
| 616 | + @functools.wraps(f) |
| 617 | + def sync_wrapper(*args, **kwargs): |
| 618 | + op = kw.get("op", DEFAULT_SPAN_OP) |
| 619 | + span_name = name or f.__name__ |
| 620 | + |
| 621 | + with sentry_sdk.start_span( |
| 622 | + op=op, |
| 623 | + name=span_name, |
| 624 | + ) as span: |
| 625 | + set_input_attributes(span, as_type, args, kwargs) |
| 626 | + |
| 627 | + # run wrapped function |
| 628 | + result = f(*args, **kwargs) |
| 629 | + |
| 630 | + set_output_attributes(span, as_type, result) |
| 631 | + return result |
| 632 | + |
| 633 | + if inspect.iscoroutinefunction(f): |
| 634 | + wrapper = async_wrapper |
| 635 | + else: |
| 636 | + wrapper = sync_wrapper |
| 637 | + |
| 638 | + return wrapper |
| 639 | + |
| 640 | + def ai_chat_decorator(f): |
| 641 | + return span_decorator(f, op=OP.GEN_AI_CHAT) |
| 642 | + |
| 643 | + def ai_agent_decorator(f): |
| 644 | + return span_decorator(f, op=OP.GEN_AI_INVOKE_AGENT) |
| 645 | + |
| 646 | + def ai_tool_decorator(f): |
| 647 | + return span_decorator(f, op=OP.GEN_AI_EXECUTE_TOOL) |
| 648 | + |
| 649 | + decorator_for_type = { |
| 650 | + "ai_chat": ai_chat_decorator, |
| 651 | + "ai_agent": ai_agent_decorator, |
| 652 | + "ai_tool": ai_tool_decorator, |
| 653 | + } |
| 654 | + decorator = decorator_for_type.get(as_type, span_decorator) |
| 655 | + |
| 656 | + if func: |
| 657 | + return decorator(func) |
| 658 | + else: |
| 659 | + return decorator |
| 660 | + |
| 661 | + |
| 662 | +def update_current_span(op=None, name=None, attributes=None) -> None: |
| 663 | + current_span = sentry_sdk.get_current_span() |
| 664 | + |
| 665 | + if op is not None: |
| 666 | + current_span.op = op |
| 667 | + |
| 668 | + if name is not None: |
| 669 | + current_span.name = name |
| 670 | + |
| 671 | + if attributes is not None: |
| 672 | + for key, value in attributes.items(): |
| 673 | + current_span.set_attribute(key, value) |
0 commit comments