|
1 | 1 | import contextlib
|
| 2 | +import functools |
2 | 3 | import inspect
|
3 | 4 | import os
|
4 | 5 | import re
|
5 | 6 | import sys
|
6 | 7 | from collections.abc import Mapping
|
7 | 8 | from datetime import timedelta
|
8 | 9 | from decimal import ROUND_DOWN, Decimal, DefaultContext, localcontext
|
9 |
| -from functools import wraps |
10 | 10 | from random import Random
|
11 | 11 | from urllib.parse import quote, unquote
|
12 | 12 | import uuid
|
@@ -770,70 +770,86 @@ def normalize_incoming_data(incoming_data):
|
770 | 770 | return data
|
771 | 771 |
|
772 | 772 |
|
773 |
| -def start_child_span_decorator(func): |
774 |
| - # type: (Any) -> Any |
| 773 | +def create_span_decorator(op=None, name=None, attributes=None): |
| 774 | + # type: (Optional[str], Optional[str], Optional[dict[str, Any]]) -> Any |
775 | 775 | """
|
776 |
| - Decorator to add child spans for functions. |
| 776 | + Create a span decorator that can wrap both sync and async functions. |
777 | 777 |
|
778 |
| - See also ``sentry_sdk.tracing.trace()``. |
| 778 | + :param op: The operation type for the span. |
| 779 | + :param name: The name of the span. |
| 780 | + :param attributes: Additional attributes to set on the span. |
779 | 781 | """
|
780 |
| - # Asynchronous case |
781 |
| - if inspect.iscoroutinefunction(func): |
782 | 782 |
|
783 |
| - @wraps(func) |
784 |
| - async def func_with_tracing(*args, **kwargs): |
785 |
| - # type: (*Any, **Any) -> Any |
| 783 | + def span_decorator(f): |
| 784 | + # type: (Any) -> Any |
| 785 | + """ |
| 786 | + Decorator to create a span for the given function. |
| 787 | + """ |
786 | 788 |
|
787 |
| - span = get_current_span() |
| 789 | + @functools.wraps(f) |
| 790 | + async def async_wrapper(*args, **kwargs): |
| 791 | + # type: (*Any, **Any) -> Any |
| 792 | + current_span = get_current_span() |
788 | 793 |
|
789 |
| - if span is None: |
| 794 | + if current_span is None: |
790 | 795 | logger.debug(
|
791 | 796 | "Cannot create a child span for %s. "
|
792 | 797 | "Please start a Sentry transaction before calling this function.",
|
793 |
| - qualname_from_function(func), |
| 798 | + qualname_from_function(f), |
794 | 799 | )
|
795 |
| - return await func(*args, **kwargs) |
| 800 | + return await f(*args, **kwargs) |
| 801 | + |
| 802 | + span_op = op or OP.FUNCTION |
| 803 | + span_name = name or qualname_from_function(f) or "" |
796 | 804 |
|
797 |
| - with span.start_child( |
798 |
| - op=OP.FUNCTION, |
799 |
| - name=qualname_from_function(func), |
800 |
| - ): |
801 |
| - return await func(*args, **kwargs) |
| 805 | + with current_span.start_child( |
| 806 | + op=span_op, |
| 807 | + name=span_name, |
| 808 | + ) as span: |
| 809 | + span.update_data(attributes or {}) |
| 810 | + result = await f(*args, **kwargs) |
| 811 | + return result |
802 | 812 |
|
803 | 813 | try:
|
804 |
| - func_with_tracing.__signature__ = inspect.signature(func) # type: ignore[attr-defined] |
| 814 | + async_wrapper.__signature__ = inspect.signature(f) # type: ignore[attr-defined] |
805 | 815 | except Exception:
|
806 | 816 | pass
|
807 | 817 |
|
808 |
| - # Synchronous case |
809 |
| - else: |
810 |
| - |
811 |
| - @wraps(func) |
812 |
| - def func_with_tracing(*args, **kwargs): |
| 818 | + @functools.wraps(f) |
| 819 | + def sync_wrapper(*args, **kwargs): |
813 | 820 | # type: (*Any, **Any) -> Any
|
| 821 | + current_span = get_current_span() |
814 | 822 |
|
815 |
| - span = get_current_span() |
816 |
| - |
817 |
| - if span is None: |
| 823 | + if current_span is None: |
818 | 824 | logger.debug(
|
819 | 825 | "Cannot create a child span for %s. "
|
820 | 826 | "Please start a Sentry transaction before calling this function.",
|
821 |
| - qualname_from_function(func), |
| 827 | + qualname_from_function(f), |
822 | 828 | )
|
823 |
| - return func(*args, **kwargs) |
| 829 | + return f(*args, **kwargs) |
| 830 | + |
| 831 | + span_op = op or OP.FUNCTION |
| 832 | + span_name = name or qualname_from_function(f) or "" |
824 | 833 |
|
825 |
| - with span.start_child( |
826 |
| - op=OP.FUNCTION, |
827 |
| - name=qualname_from_function(func), |
828 |
| - ): |
829 |
| - return func(*args, **kwargs) |
| 834 | + with current_span.start_child( |
| 835 | + op=span_op, |
| 836 | + name=span_name, |
| 837 | + ) as span: |
| 838 | + span.update_data(attributes or {}) |
| 839 | + result = f(*args, **kwargs) |
| 840 | + return result |
830 | 841 |
|
831 | 842 | try:
|
832 |
| - func_with_tracing.__signature__ = inspect.signature(func) # type: ignore[attr-defined] |
| 843 | + sync_wrapper.__signature__ = inspect.signature(f) # type: ignore[attr-defined] |
833 | 844 | except Exception:
|
834 | 845 | pass
|
835 | 846 |
|
836 |
| - return func_with_tracing |
| 847 | + if inspect.iscoroutinefunction(f): |
| 848 | + return async_wrapper |
| 849 | + else: |
| 850 | + return sync_wrapper |
| 851 | + |
| 852 | + return span_decorator |
837 | 853 |
|
838 | 854 |
|
839 | 855 | def get_current_span(scope=None):
|
|
0 commit comments