tracing.py 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486
  1. import uuid
  2. import warnings
  3. from datetime import datetime, timedelta, timezone
  4. from enum import Enum
  5. import sentry_sdk
  6. from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS, SPANDATA, SPANTEMPLATE
  7. from sentry_sdk.profiler.continuous_profiler import get_profiler_id
  8. from sentry_sdk.utils import (
  9. capture_internal_exceptions,
  10. get_current_thread_meta,
  11. is_valid_sample_rate,
  12. logger,
  13. nanosecond_time,
  14. should_be_treated_as_error,
  15. )
  16. from typing import TYPE_CHECKING
  17. if TYPE_CHECKING:
  18. from collections.abc import Callable, Mapping, MutableMapping
  19. from typing import Any
  20. from typing import Dict
  21. from typing import Iterator
  22. from typing import List
  23. from typing import Optional
  24. from typing import overload
  25. from typing import ParamSpec
  26. from typing import Tuple
  27. from typing import Union
  28. from typing import TypeVar
  29. from typing import Set
  30. from typing_extensions import TypedDict, Unpack
  31. P = ParamSpec("P")
  32. R = TypeVar("R")
  33. from sentry_sdk.profiler.continuous_profiler import ContinuousProfile
  34. from sentry_sdk.profiler.transaction_profiler import Profile
  35. from sentry_sdk._types import (
  36. Event,
  37. MeasurementUnit,
  38. SamplingContext,
  39. MeasurementValue,
  40. )
  41. class SpanKwargs(TypedDict, total=False):
  42. trace_id: str
  43. """
  44. The trace ID of the root span. If this new span is to be the root span,
  45. omit this parameter, and a new trace ID will be generated.
  46. """
  47. span_id: str
  48. """The span ID of this span. If omitted, a new span ID will be generated."""
  49. parent_span_id: str
  50. """The span ID of the parent span, if applicable."""
  51. same_process_as_parent: bool
  52. """Whether this span is in the same process as the parent span."""
  53. sampled: bool
  54. """
  55. Whether the span should be sampled. Overrides the default sampling decision
  56. for this span when provided.
  57. """
  58. op: str
  59. """
  60. The span's operation. A list of recommended values is available here:
  61. https://develop.sentry.dev/sdk/performance/span-operations/
  62. """
  63. description: str
  64. """A description of what operation is being performed within the span. This argument is DEPRECATED. Please use the `name` parameter, instead."""
  65. hub: Optional["sentry_sdk.Hub"]
  66. """The hub to use for this span. This argument is DEPRECATED. Please use the `scope` parameter, instead."""
  67. status: str
  68. """The span's status. Possible values are listed at https://develop.sentry.dev/sdk/event-payloads/span/"""
  69. containing_transaction: Optional["Transaction"]
  70. """The transaction that this span belongs to."""
  71. start_timestamp: Optional[Union[datetime, float]]
  72. """
  73. The timestamp when the span started. If omitted, the current time
  74. will be used.
  75. """
  76. scope: "sentry_sdk.Scope"
  77. """The scope to use for this span. If not provided, we use the current scope."""
  78. origin: str
  79. """
  80. The origin of the span.
  81. See https://develop.sentry.dev/sdk/performance/trace-origin/
  82. Default "manual".
  83. """
  84. name: str
  85. """A string describing what operation is being performed within the span/transaction."""
  86. class TransactionKwargs(SpanKwargs, total=False):
  87. source: str
  88. """
  89. A string describing the source of the transaction name. This will be used to determine the transaction's type.
  90. See https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations for more information.
  91. Default "custom".
  92. """
  93. parent_sampled: bool
  94. """Whether the parent transaction was sampled. If True this transaction will be kept, if False it will be discarded."""
  95. baggage: "Baggage"
  96. """The W3C baggage header value. (see https://www.w3.org/TR/baggage/)"""
  97. ProfileContext = TypedDict(
  98. "ProfileContext",
  99. {
  100. "profiler_id": str,
  101. },
  102. )
  103. BAGGAGE_HEADER_NAME = "baggage"
  104. SENTRY_TRACE_HEADER_NAME = "sentry-trace"
  105. # Transaction source
  106. # see https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
  107. class TransactionSource(str, Enum):
  108. COMPONENT = "component"
  109. CUSTOM = "custom"
  110. ROUTE = "route"
  111. TASK = "task"
  112. URL = "url"
  113. VIEW = "view"
  114. def __str__(self):
  115. # type: () -> str
  116. return self.value
  117. # These are typically high cardinality and the server hates them
  118. LOW_QUALITY_TRANSACTION_SOURCES = [
  119. TransactionSource.URL,
  120. ]
  121. SOURCE_FOR_STYLE = {
  122. "endpoint": TransactionSource.COMPONENT,
  123. "function_name": TransactionSource.COMPONENT,
  124. "handler_name": TransactionSource.COMPONENT,
  125. "method_and_path_pattern": TransactionSource.ROUTE,
  126. "path": TransactionSource.URL,
  127. "route_name": TransactionSource.COMPONENT,
  128. "route_pattern": TransactionSource.ROUTE,
  129. "uri_template": TransactionSource.ROUTE,
  130. "url": TransactionSource.ROUTE,
  131. }
  132. def get_span_status_from_http_code(http_status_code):
  133. # type: (int) -> str
  134. """
  135. Returns the Sentry status corresponding to the given HTTP status code.
  136. See: https://develop.sentry.dev/sdk/event-payloads/contexts/#trace-context
  137. """
  138. if http_status_code < 400:
  139. return SPANSTATUS.OK
  140. elif 400 <= http_status_code < 500:
  141. if http_status_code == 403:
  142. return SPANSTATUS.PERMISSION_DENIED
  143. elif http_status_code == 404:
  144. return SPANSTATUS.NOT_FOUND
  145. elif http_status_code == 429:
  146. return SPANSTATUS.RESOURCE_EXHAUSTED
  147. elif http_status_code == 413:
  148. return SPANSTATUS.FAILED_PRECONDITION
  149. elif http_status_code == 401:
  150. return SPANSTATUS.UNAUTHENTICATED
  151. elif http_status_code == 409:
  152. return SPANSTATUS.ALREADY_EXISTS
  153. else:
  154. return SPANSTATUS.INVALID_ARGUMENT
  155. elif 500 <= http_status_code < 600:
  156. if http_status_code == 504:
  157. return SPANSTATUS.DEADLINE_EXCEEDED
  158. elif http_status_code == 501:
  159. return SPANSTATUS.UNIMPLEMENTED
  160. elif http_status_code == 503:
  161. return SPANSTATUS.UNAVAILABLE
  162. else:
  163. return SPANSTATUS.INTERNAL_ERROR
  164. return SPANSTATUS.UNKNOWN_ERROR
  165. class _SpanRecorder:
  166. """Limits the number of spans recorded in a transaction."""
  167. __slots__ = ("maxlen", "spans", "dropped_spans")
  168. def __init__(self, maxlen):
  169. # type: (int) -> None
  170. # FIXME: this is `maxlen - 1` only to preserve historical behavior
  171. # enforced by tests.
  172. # Either this should be changed to `maxlen` or the JS SDK implementation
  173. # should be changed to match a consistent interpretation of what maxlen
  174. # limits: either transaction+spans or only child spans.
  175. self.maxlen = maxlen - 1
  176. self.spans = [] # type: List[Span]
  177. self.dropped_spans = 0 # type: int
  178. def add(self, span):
  179. # type: (Span) -> None
  180. if len(self.spans) > self.maxlen:
  181. span._span_recorder = None
  182. self.dropped_spans += 1
  183. else:
  184. self.spans.append(span)
  185. class Span:
  186. """A span holds timing information of a block of code.
  187. Spans can have multiple child spans thus forming a span tree.
  188. :param trace_id: The trace ID of the root span. If this new span is to be the root span,
  189. omit this parameter, and a new trace ID will be generated.
  190. :param span_id: The span ID of this span. If omitted, a new span ID will be generated.
  191. :param parent_span_id: The span ID of the parent span, if applicable.
  192. :param same_process_as_parent: Whether this span is in the same process as the parent span.
  193. :param sampled: Whether the span should be sampled. Overrides the default sampling decision
  194. for this span when provided.
  195. :param op: The span's operation. A list of recommended values is available here:
  196. https://develop.sentry.dev/sdk/performance/span-operations/
  197. :param description: A description of what operation is being performed within the span.
  198. .. deprecated:: 2.15.0
  199. Please use the `name` parameter, instead.
  200. :param name: A string describing what operation is being performed within the span.
  201. :param hub: The hub to use for this span.
  202. .. deprecated:: 2.0.0
  203. Please use the `scope` parameter, instead.
  204. :param status: The span's status. Possible values are listed at
  205. https://develop.sentry.dev/sdk/event-payloads/span/
  206. :param containing_transaction: The transaction that this span belongs to.
  207. :param start_timestamp: The timestamp when the span started. If omitted, the current time
  208. will be used.
  209. :param scope: The scope to use for this span. If not provided, we use the current scope.
  210. """
  211. __slots__ = (
  212. "_trace_id",
  213. "_span_id",
  214. "parent_span_id",
  215. "same_process_as_parent",
  216. "sampled",
  217. "op",
  218. "description",
  219. "_measurements",
  220. "start_timestamp",
  221. "_start_timestamp_monotonic_ns",
  222. "status",
  223. "timestamp",
  224. "_tags",
  225. "_data",
  226. "_span_recorder",
  227. "hub",
  228. "_context_manager_state",
  229. "_containing_transaction",
  230. "scope",
  231. "origin",
  232. "name",
  233. "_flags",
  234. "_flags_capacity",
  235. )
  236. def __init__(
  237. self,
  238. trace_id=None, # type: Optional[str]
  239. span_id=None, # type: Optional[str]
  240. parent_span_id=None, # type: Optional[str]
  241. same_process_as_parent=True, # type: bool
  242. sampled=None, # type: Optional[bool]
  243. op=None, # type: Optional[str]
  244. description=None, # type: Optional[str]
  245. hub=None, # type: Optional[sentry_sdk.Hub] # deprecated
  246. status=None, # type: Optional[str]
  247. containing_transaction=None, # type: Optional[Transaction]
  248. start_timestamp=None, # type: Optional[Union[datetime, float]]
  249. scope=None, # type: Optional[sentry_sdk.Scope]
  250. origin="manual", # type: str
  251. name=None, # type: Optional[str]
  252. ):
  253. # type: (...) -> None
  254. self._trace_id = trace_id
  255. self._span_id = span_id
  256. self.parent_span_id = parent_span_id
  257. self.same_process_as_parent = same_process_as_parent
  258. self.sampled = sampled
  259. self.op = op
  260. self.description = name or description
  261. self.status = status
  262. self.hub = hub # backwards compatibility
  263. self.scope = scope
  264. self.origin = origin
  265. self._measurements = {} # type: Dict[str, MeasurementValue]
  266. self._tags = {} # type: MutableMapping[str, str]
  267. self._data = {} # type: Dict[str, Any]
  268. self._containing_transaction = containing_transaction
  269. self._flags = {} # type: Dict[str, bool]
  270. self._flags_capacity = 10
  271. if hub is not None:
  272. warnings.warn(
  273. "The `hub` parameter is deprecated. Please use `scope` instead.",
  274. DeprecationWarning,
  275. stacklevel=2,
  276. )
  277. self.scope = self.scope or hub.scope
  278. if start_timestamp is None:
  279. start_timestamp = datetime.now(timezone.utc)
  280. elif isinstance(start_timestamp, float):
  281. start_timestamp = datetime.fromtimestamp(start_timestamp, timezone.utc)
  282. self.start_timestamp = start_timestamp
  283. try:
  284. # profiling depends on this value and requires that
  285. # it is measured in nanoseconds
  286. self._start_timestamp_monotonic_ns = nanosecond_time()
  287. except AttributeError:
  288. pass
  289. #: End timestamp of span
  290. self.timestamp = None # type: Optional[datetime]
  291. self._span_recorder = None # type: Optional[_SpanRecorder]
  292. self.update_active_thread()
  293. self.set_profiler_id(get_profiler_id())
  294. # TODO this should really live on the Transaction class rather than the Span
  295. # class
  296. def init_span_recorder(self, maxlen):
  297. # type: (int) -> None
  298. if self._span_recorder is None:
  299. self._span_recorder = _SpanRecorder(maxlen)
  300. @property
  301. def trace_id(self):
  302. # type: () -> str
  303. if not self._trace_id:
  304. self._trace_id = uuid.uuid4().hex
  305. return self._trace_id
  306. @trace_id.setter
  307. def trace_id(self, value):
  308. # type: (str) -> None
  309. self._trace_id = value
  310. @property
  311. def span_id(self):
  312. # type: () -> str
  313. if not self._span_id:
  314. self._span_id = uuid.uuid4().hex[16:]
  315. return self._span_id
  316. @span_id.setter
  317. def span_id(self, value):
  318. # type: (str) -> None
  319. self._span_id = value
  320. def __repr__(self):
  321. # type: () -> str
  322. return (
  323. "<%s(op=%r, description:%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r, origin=%r)>"
  324. % (
  325. self.__class__.__name__,
  326. self.op,
  327. self.description,
  328. self.trace_id,
  329. self.span_id,
  330. self.parent_span_id,
  331. self.sampled,
  332. self.origin,
  333. )
  334. )
  335. def __enter__(self):
  336. # type: () -> Span
  337. scope = self.scope or sentry_sdk.get_current_scope()
  338. old_span = scope.span
  339. scope.span = self
  340. self._context_manager_state = (scope, old_span)
  341. return self
  342. def __exit__(self, ty, value, tb):
  343. # type: (Optional[Any], Optional[Any], Optional[Any]) -> None
  344. if value is not None and should_be_treated_as_error(ty, value):
  345. if self.status != SPANSTATUS.ERROR:
  346. self.set_status(SPANSTATUS.INTERNAL_ERROR)
  347. with capture_internal_exceptions():
  348. scope, old_span = self._context_manager_state
  349. del self._context_manager_state
  350. self.finish(scope)
  351. scope.span = old_span
  352. @property
  353. def containing_transaction(self):
  354. # type: () -> Optional[Transaction]
  355. """The ``Transaction`` that this span belongs to.
  356. The ``Transaction`` is the root of the span tree,
  357. so one could also think of this ``Transaction`` as the "root span"."""
  358. # this is a getter rather than a regular attribute so that transactions
  359. # can return `self` here instead (as a way to prevent them circularly
  360. # referencing themselves)
  361. return self._containing_transaction
  362. def start_child(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs):
  363. # type: (str, **Any) -> Span
  364. """
  365. Start a sub-span from the current span or transaction.
  366. Takes the same arguments as the initializer of :py:class:`Span`. The
  367. trace id, sampling decision, transaction pointer, and span recorder are
  368. inherited from the current span/transaction.
  369. The instrumenter parameter is deprecated for user code, and it will
  370. be removed in the next major version. Going forward, it should only
  371. be used by the SDK itself.
  372. """
  373. if kwargs.get("description") is not None:
  374. warnings.warn(
  375. "The `description` parameter is deprecated. Please use `name` instead.",
  376. DeprecationWarning,
  377. stacklevel=2,
  378. )
  379. configuration_instrumenter = sentry_sdk.get_client().options["instrumenter"]
  380. if instrumenter != configuration_instrumenter:
  381. return NoOpSpan()
  382. kwargs.setdefault("sampled", self.sampled)
  383. child = Span(
  384. trace_id=self.trace_id,
  385. parent_span_id=self.span_id,
  386. containing_transaction=self.containing_transaction,
  387. **kwargs,
  388. )
  389. span_recorder = (
  390. self.containing_transaction and self.containing_transaction._span_recorder
  391. )
  392. if span_recorder:
  393. span_recorder.add(child)
  394. return child
  395. @classmethod
  396. def continue_from_environ(
  397. cls,
  398. environ, # type: Mapping[str, str]
  399. **kwargs, # type: Any
  400. ):
  401. # type: (...) -> Transaction
  402. """
  403. Create a Transaction with the given params, then add in data pulled from
  404. the ``sentry-trace`` and ``baggage`` headers from the environ (if any)
  405. before returning the Transaction.
  406. This is different from :py:meth:`~sentry_sdk.tracing.Span.continue_from_headers`
  407. in that it assumes header names in the form ``HTTP_HEADER_NAME`` -
  408. such as you would get from a WSGI/ASGI environ -
  409. rather than the form ``header-name``.
  410. :param environ: The ASGI/WSGI environ to pull information from.
  411. """
  412. if cls is Span:
  413. logger.warning(
  414. "Deprecated: use Transaction.continue_from_environ "
  415. "instead of Span.continue_from_environ."
  416. )
  417. return Transaction.continue_from_headers(EnvironHeaders(environ), **kwargs)
  418. @classmethod
  419. def continue_from_headers(
  420. cls,
  421. headers, # type: Mapping[str, str]
  422. *,
  423. _sample_rand=None, # type: Optional[str]
  424. **kwargs, # type: Any
  425. ):
  426. # type: (...) -> Transaction
  427. """
  428. Create a transaction with the given params (including any data pulled from
  429. the ``sentry-trace`` and ``baggage`` headers).
  430. :param headers: The dictionary with the HTTP headers to pull information from.
  431. :param _sample_rand: If provided, we override the sample_rand value from the
  432. incoming headers with this value. (internal use only)
  433. """
  434. # TODO move this to the Transaction class
  435. if cls is Span:
  436. logger.warning(
  437. "Deprecated: use Transaction.continue_from_headers "
  438. "instead of Span.continue_from_headers."
  439. )
  440. # TODO-neel move away from this kwargs stuff, it's confusing and opaque
  441. # make more explicit
  442. baggage = Baggage.from_incoming_header(
  443. headers.get(BAGGAGE_HEADER_NAME), _sample_rand=_sample_rand
  444. )
  445. kwargs.update({BAGGAGE_HEADER_NAME: baggage})
  446. sentrytrace_kwargs = extract_sentrytrace_data(
  447. headers.get(SENTRY_TRACE_HEADER_NAME)
  448. )
  449. if sentrytrace_kwargs is not None:
  450. kwargs.update(sentrytrace_kwargs)
  451. # If there's an incoming sentry-trace but no incoming baggage header,
  452. # for instance in traces coming from older SDKs,
  453. # baggage will be empty and immutable and won't be populated as head SDK.
  454. baggage.freeze()
  455. transaction = Transaction(**kwargs)
  456. transaction.same_process_as_parent = False
  457. return transaction
  458. def iter_headers(self):
  459. # type: () -> Iterator[Tuple[str, str]]
  460. """
  461. Creates a generator which returns the span's ``sentry-trace`` and ``baggage`` headers.
  462. If the span's containing transaction doesn't yet have a ``baggage`` value,
  463. this will cause one to be generated and stored.
  464. """
  465. if not self.containing_transaction:
  466. # Do not propagate headers if there is no containing transaction. Otherwise, this
  467. # span ends up being the root span of a new trace, and since it does not get sent
  468. # to Sentry, the trace will be missing a root transaction. The dynamic sampling
  469. # context will also be missing, breaking dynamic sampling & traces.
  470. return
  471. yield SENTRY_TRACE_HEADER_NAME, self.to_traceparent()
  472. baggage = self.containing_transaction.get_baggage().serialize()
  473. if baggage:
  474. yield BAGGAGE_HEADER_NAME, baggage
  475. @classmethod
  476. def from_traceparent(
  477. cls,
  478. traceparent, # type: Optional[str]
  479. **kwargs, # type: Any
  480. ):
  481. # type: (...) -> Optional[Transaction]
  482. """
  483. DEPRECATED: Use :py:meth:`sentry_sdk.tracing.Span.continue_from_headers`.
  484. Create a ``Transaction`` with the given params, then add in data pulled from
  485. the given ``sentry-trace`` header value before returning the ``Transaction``.
  486. """
  487. logger.warning(
  488. "Deprecated: Use Transaction.continue_from_headers(headers, **kwargs) "
  489. "instead of from_traceparent(traceparent, **kwargs)"
  490. )
  491. if not traceparent:
  492. return None
  493. return cls.continue_from_headers(
  494. {SENTRY_TRACE_HEADER_NAME: traceparent}, **kwargs
  495. )
  496. def to_traceparent(self):
  497. # type: () -> str
  498. if self.sampled is True:
  499. sampled = "1"
  500. elif self.sampled is False:
  501. sampled = "0"
  502. else:
  503. sampled = None
  504. traceparent = "%s-%s" % (self.trace_id, self.span_id)
  505. if sampled is not None:
  506. traceparent += "-%s" % (sampled,)
  507. return traceparent
  508. def to_baggage(self):
  509. # type: () -> Optional[Baggage]
  510. """Returns the :py:class:`~sentry_sdk.tracing_utils.Baggage`
  511. associated with this ``Span``, if any. (Taken from the root of the span tree.)
  512. """
  513. if self.containing_transaction:
  514. return self.containing_transaction.get_baggage()
  515. return None
  516. def set_tag(self, key, value):
  517. # type: (str, Any) -> None
  518. self._tags[key] = value
  519. def set_data(self, key, value):
  520. # type: (str, Any) -> None
  521. self._data[key] = value
  522. def update_data(self, data):
  523. # type: (Dict[str, Any]) -> None
  524. self._data.update(data)
  525. def set_flag(self, flag, result):
  526. # type: (str, bool) -> None
  527. if len(self._flags) < self._flags_capacity:
  528. self._flags[flag] = result
  529. def set_status(self, value):
  530. # type: (str) -> None
  531. self.status = value
  532. def set_measurement(self, name, value, unit=""):
  533. # type: (str, float, MeasurementUnit) -> None
  534. """
  535. .. deprecated:: 2.28.0
  536. This function is deprecated and will be removed in the next major release.
  537. """
  538. warnings.warn(
  539. "`set_measurement()` is deprecated and will be removed in the next major version. Please use `set_data()` instead.",
  540. DeprecationWarning,
  541. stacklevel=2,
  542. )
  543. self._measurements[name] = {"value": value, "unit": unit}
  544. def set_thread(self, thread_id, thread_name):
  545. # type: (Optional[int], Optional[str]) -> None
  546. if thread_id is not None:
  547. self.set_data(SPANDATA.THREAD_ID, str(thread_id))
  548. if thread_name is not None:
  549. self.set_data(SPANDATA.THREAD_NAME, thread_name)
  550. def set_profiler_id(self, profiler_id):
  551. # type: (Optional[str]) -> None
  552. if profiler_id is not None:
  553. self.set_data(SPANDATA.PROFILER_ID, profiler_id)
  554. def set_http_status(self, http_status):
  555. # type: (int) -> None
  556. self.set_tag(
  557. "http.status_code", str(http_status)
  558. ) # we keep this for backwards compatibility
  559. self.set_data(SPANDATA.HTTP_STATUS_CODE, http_status)
  560. self.set_status(get_span_status_from_http_code(http_status))
  561. def is_success(self):
  562. # type: () -> bool
  563. return self.status == "ok"
  564. def finish(self, scope=None, end_timestamp=None):
  565. # type: (Optional[sentry_sdk.Scope], Optional[Union[float, datetime]]) -> Optional[str]
  566. """
  567. Sets the end timestamp of the span.
  568. Additionally it also creates a breadcrumb from the span,
  569. if the span represents a database or HTTP request.
  570. :param scope: The scope to use for this transaction.
  571. If not provided, the current scope will be used.
  572. :param end_timestamp: Optional timestamp that should
  573. be used as timestamp instead of the current time.
  574. :return: Always ``None``. The type is ``Optional[str]`` to match
  575. the return value of :py:meth:`sentry_sdk.tracing.Transaction.finish`.
  576. """
  577. if self.timestamp is not None:
  578. # This span is already finished, ignore.
  579. return None
  580. try:
  581. if end_timestamp:
  582. if isinstance(end_timestamp, float):
  583. end_timestamp = datetime.fromtimestamp(end_timestamp, timezone.utc)
  584. self.timestamp = end_timestamp
  585. else:
  586. elapsed = nanosecond_time() - self._start_timestamp_monotonic_ns
  587. self.timestamp = self.start_timestamp + timedelta(
  588. microseconds=elapsed / 1000
  589. )
  590. except AttributeError:
  591. self.timestamp = datetime.now(timezone.utc)
  592. scope = scope or sentry_sdk.get_current_scope()
  593. maybe_create_breadcrumbs_from_span(scope, self)
  594. return None
  595. def to_json(self):
  596. # type: () -> Dict[str, Any]
  597. """Returns a JSON-compatible representation of the span."""
  598. rv = {
  599. "trace_id": self.trace_id,
  600. "span_id": self.span_id,
  601. "parent_span_id": self.parent_span_id,
  602. "same_process_as_parent": self.same_process_as_parent,
  603. "op": self.op,
  604. "description": self.description,
  605. "start_timestamp": self.start_timestamp,
  606. "timestamp": self.timestamp,
  607. "origin": self.origin,
  608. } # type: Dict[str, Any]
  609. if self.status:
  610. self._tags["status"] = self.status
  611. if len(self._measurements) > 0:
  612. rv["measurements"] = self._measurements
  613. tags = self._tags
  614. if tags:
  615. rv["tags"] = tags
  616. data = {}
  617. data.update(self._flags)
  618. data.update(self._data)
  619. if data:
  620. rv["data"] = data
  621. return rv
  622. def get_trace_context(self):
  623. # type: () -> Any
  624. rv = {
  625. "trace_id": self.trace_id,
  626. "span_id": self.span_id,
  627. "parent_span_id": self.parent_span_id,
  628. "op": self.op,
  629. "description": self.description,
  630. "origin": self.origin,
  631. } # type: Dict[str, Any]
  632. if self.status:
  633. rv["status"] = self.status
  634. if self.containing_transaction:
  635. rv["dynamic_sampling_context"] = (
  636. self.containing_transaction.get_baggage().dynamic_sampling_context()
  637. )
  638. data = {}
  639. thread_id = self._data.get(SPANDATA.THREAD_ID)
  640. if thread_id is not None:
  641. data["thread.id"] = thread_id
  642. thread_name = self._data.get(SPANDATA.THREAD_NAME)
  643. if thread_name is not None:
  644. data["thread.name"] = thread_name
  645. if data:
  646. rv["data"] = data
  647. return rv
  648. def get_profile_context(self):
  649. # type: () -> Optional[ProfileContext]
  650. profiler_id = self._data.get(SPANDATA.PROFILER_ID)
  651. if profiler_id is None:
  652. return None
  653. return {
  654. "profiler_id": profiler_id,
  655. }
  656. def update_active_thread(self):
  657. # type: () -> None
  658. thread_id, thread_name = get_current_thread_meta()
  659. self.set_thread(thread_id, thread_name)
  660. class Transaction(Span):
  661. """The Transaction is the root element that holds all the spans
  662. for Sentry performance instrumentation.
  663. :param name: Identifier of the transaction.
  664. Will show up in the Sentry UI.
  665. :param parent_sampled: Whether the parent transaction was sampled.
  666. If True this transaction will be kept, if False it will be discarded.
  667. :param baggage: The W3C baggage header value.
  668. (see https://www.w3.org/TR/baggage/)
  669. :param source: A string describing the source of the transaction name.
  670. This will be used to determine the transaction's type.
  671. See https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
  672. for more information. Default "custom".
  673. :param kwargs: Additional arguments to be passed to the Span constructor.
  674. See :py:class:`sentry_sdk.tracing.Span` for available arguments.
  675. """
  676. __slots__ = (
  677. "name",
  678. "source",
  679. "parent_sampled",
  680. # used to create baggage value for head SDKs in dynamic sampling
  681. "sample_rate",
  682. "_measurements",
  683. "_contexts",
  684. "_profile",
  685. "_continuous_profile",
  686. "_baggage",
  687. "_sample_rand",
  688. )
  689. def __init__( # type: ignore[misc]
  690. self,
  691. name="", # type: str
  692. parent_sampled=None, # type: Optional[bool]
  693. baggage=None, # type: Optional[Baggage]
  694. source=TransactionSource.CUSTOM, # type: str
  695. **kwargs, # type: Unpack[SpanKwargs]
  696. ):
  697. # type: (...) -> None
  698. super().__init__(**kwargs)
  699. self.name = name
  700. self.source = source
  701. self.sample_rate = None # type: Optional[float]
  702. self.parent_sampled = parent_sampled
  703. self._measurements = {} # type: Dict[str, MeasurementValue]
  704. self._contexts = {} # type: Dict[str, Any]
  705. self._profile = None # type: Optional[Profile]
  706. self._continuous_profile = None # type: Optional[ContinuousProfile]
  707. self._baggage = baggage
  708. baggage_sample_rand = (
  709. None if self._baggage is None else self._baggage._sample_rand()
  710. )
  711. if baggage_sample_rand is not None:
  712. self._sample_rand = baggage_sample_rand
  713. else:
  714. self._sample_rand = _generate_sample_rand(self.trace_id)
  715. def __repr__(self):
  716. # type: () -> str
  717. return (
  718. "<%s(name=%r, op=%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r, source=%r, origin=%r)>"
  719. % (
  720. self.__class__.__name__,
  721. self.name,
  722. self.op,
  723. self.trace_id,
  724. self.span_id,
  725. self.parent_span_id,
  726. self.sampled,
  727. self.source,
  728. self.origin,
  729. )
  730. )
  731. def _possibly_started(self):
  732. # type: () -> bool
  733. """Returns whether the transaction might have been started.
  734. If this returns False, we know that the transaction was not started
  735. with sentry_sdk.start_transaction, and therefore the transaction will
  736. be discarded.
  737. """
  738. # We must explicitly check self.sampled is False since self.sampled can be None
  739. return self._span_recorder is not None or self.sampled is False
  740. def __enter__(self):
  741. # type: () -> Transaction
  742. if not self._possibly_started():
  743. logger.debug(
  744. "Transaction was entered without being started with sentry_sdk.start_transaction."
  745. "The transaction will not be sent to Sentry. To fix, start the transaction by"
  746. "passing it to sentry_sdk.start_transaction."
  747. )
  748. super().__enter__()
  749. if self._profile is not None:
  750. self._profile.__enter__()
  751. return self
  752. def __exit__(self, ty, value, tb):
  753. # type: (Optional[Any], Optional[Any], Optional[Any]) -> None
  754. if self._profile is not None:
  755. self._profile.__exit__(ty, value, tb)
  756. if self._continuous_profile is not None:
  757. self._continuous_profile.stop()
  758. super().__exit__(ty, value, tb)
  759. @property
  760. def containing_transaction(self):
  761. # type: () -> Transaction
  762. """The root element of the span tree.
  763. In the case of a transaction it is the transaction itself.
  764. """
  765. # Transactions (as spans) belong to themselves (as transactions). This
  766. # is a getter rather than a regular attribute to avoid having a circular
  767. # reference.
  768. return self
  769. def _get_scope_from_finish_args(
  770. self,
  771. scope_arg, # type: Optional[Union[sentry_sdk.Scope, sentry_sdk.Hub]]
  772. hub_arg, # type: Optional[Union[sentry_sdk.Scope, sentry_sdk.Hub]]
  773. ):
  774. # type: (...) -> Optional[sentry_sdk.Scope]
  775. """
  776. Logic to get the scope from the arguments passed to finish. This
  777. function exists for backwards compatibility with the old finish.
  778. TODO: Remove this function in the next major version.
  779. """
  780. scope_or_hub = scope_arg
  781. if hub_arg is not None:
  782. warnings.warn(
  783. "The `hub` parameter is deprecated. Please use the `scope` parameter, instead.",
  784. DeprecationWarning,
  785. stacklevel=3,
  786. )
  787. scope_or_hub = hub_arg
  788. if isinstance(scope_or_hub, sentry_sdk.Hub):
  789. warnings.warn(
  790. "Passing a Hub to finish is deprecated. Please pass a Scope, instead.",
  791. DeprecationWarning,
  792. stacklevel=3,
  793. )
  794. return scope_or_hub.scope
  795. return scope_or_hub
  796. def _get_log_representation(self):
  797. # type: () -> str
  798. return "{op}transaction <{name}>".format(
  799. op=("<" + self.op + "> " if self.op else ""), name=self.name
  800. )
  801. def finish(
  802. self,
  803. scope=None, # type: Optional[sentry_sdk.Scope]
  804. end_timestamp=None, # type: Optional[Union[float, datetime]]
  805. *,
  806. hub=None, # type: Optional[sentry_sdk.Hub]
  807. ):
  808. # type: (...) -> Optional[str]
  809. """Finishes the transaction and sends it to Sentry.
  810. All finished spans in the transaction will also be sent to Sentry.
  811. :param scope: The Scope to use for this transaction.
  812. If not provided, the current Scope will be used.
  813. :param end_timestamp: Optional timestamp that should
  814. be used as timestamp instead of the current time.
  815. :param hub: The hub to use for this transaction.
  816. This argument is DEPRECATED. Please use the `scope`
  817. parameter, instead.
  818. :return: The event ID if the transaction was sent to Sentry,
  819. otherwise None.
  820. """
  821. if self.timestamp is not None:
  822. # This transaction is already finished, ignore.
  823. return None
  824. # For backwards compatibility, we must handle the case where `scope`
  825. # or `hub` could both either be a `Scope` or a `Hub`.
  826. scope = self._get_scope_from_finish_args(scope, hub) # type: Optional[sentry_sdk.Scope]
  827. scope = scope or self.scope or sentry_sdk.get_current_scope()
  828. client = sentry_sdk.get_client()
  829. if not client.is_active():
  830. # We have no active client and therefore nowhere to send this transaction.
  831. return None
  832. if self._span_recorder is None:
  833. # Explicit check against False needed because self.sampled might be None
  834. if self.sampled is False:
  835. logger.debug("Discarding transaction because sampled = False")
  836. else:
  837. logger.debug(
  838. "Discarding transaction because it was not started with sentry_sdk.start_transaction"
  839. )
  840. # This is not entirely accurate because discards here are not
  841. # exclusively based on sample rate but also traces sampler, but
  842. # we handle this the same here.
  843. if client.transport and has_tracing_enabled(client.options):
  844. if client.monitor and client.monitor.downsample_factor > 0:
  845. reason = "backpressure"
  846. else:
  847. reason = "sample_rate"
  848. client.transport.record_lost_event(reason, data_category="transaction")
  849. # Only one span (the transaction itself) is discarded, since we did not record any spans here.
  850. client.transport.record_lost_event(reason, data_category="span")
  851. return None
  852. if not self.name:
  853. logger.warning(
  854. "Transaction has no name, falling back to `<unlabeled transaction>`."
  855. )
  856. self.name = "<unlabeled transaction>"
  857. super().finish(scope, end_timestamp)
  858. status_code = self._data.get(SPANDATA.HTTP_STATUS_CODE)
  859. if (
  860. status_code is not None
  861. and status_code in client.options["trace_ignore_status_codes"]
  862. ):
  863. logger.debug(
  864. "[Tracing] Discarding {transaction_description} because the HTTP status code {status_code} is matched by trace_ignore_status_codes: {trace_ignore_status_codes}".format(
  865. transaction_description=self._get_log_representation(),
  866. status_code=self._data[SPANDATA.HTTP_STATUS_CODE],
  867. trace_ignore_status_codes=client.options[
  868. "trace_ignore_status_codes"
  869. ],
  870. )
  871. )
  872. if client.transport:
  873. client.transport.record_lost_event(
  874. "event_processor", data_category="transaction"
  875. )
  876. num_spans = len(self._span_recorder.spans) + 1
  877. client.transport.record_lost_event(
  878. "event_processor", data_category="span", quantity=num_spans
  879. )
  880. self.sampled = False
  881. if not self.sampled:
  882. # At this point a `sampled = None` should have already been resolved
  883. # to a concrete decision.
  884. if self.sampled is None:
  885. logger.warning("Discarding transaction without sampling decision.")
  886. return None
  887. finished_spans = [
  888. span.to_json()
  889. for span in self._span_recorder.spans
  890. if span.timestamp is not None
  891. ]
  892. len_diff = len(self._span_recorder.spans) - len(finished_spans)
  893. dropped_spans = len_diff + self._span_recorder.dropped_spans
  894. # we do this to break the circular reference of transaction -> span
  895. # recorder -> span -> containing transaction (which is where we started)
  896. # before either the spans or the transaction goes out of scope and has
  897. # to be garbage collected
  898. self._span_recorder = None
  899. contexts = {}
  900. contexts.update(self._contexts)
  901. contexts.update({"trace": self.get_trace_context()})
  902. profile_context = self.get_profile_context()
  903. if profile_context is not None:
  904. contexts.update({"profile": profile_context})
  905. event = {
  906. "type": "transaction",
  907. "transaction": self.name,
  908. "transaction_info": {"source": self.source},
  909. "contexts": contexts,
  910. "tags": self._tags,
  911. "timestamp": self.timestamp,
  912. "start_timestamp": self.start_timestamp,
  913. "spans": finished_spans,
  914. } # type: Event
  915. if dropped_spans > 0:
  916. event["_dropped_spans"] = dropped_spans
  917. if self._profile is not None and self._profile.valid():
  918. event["profile"] = self._profile
  919. self._profile = None
  920. event["measurements"] = self._measurements
  921. return scope.capture_event(event)
  922. def set_measurement(self, name, value, unit=""):
  923. # type: (str, float, MeasurementUnit) -> None
  924. """
  925. .. deprecated:: 2.28.0
  926. This function is deprecated and will be removed in the next major release.
  927. """
  928. warnings.warn(
  929. "`set_measurement()` is deprecated and will be removed in the next major version. Please use `set_data()` instead.",
  930. DeprecationWarning,
  931. stacklevel=2,
  932. )
  933. self._measurements[name] = {"value": value, "unit": unit}
  934. def set_context(self, key, value):
  935. # type: (str, dict[str, Any]) -> None
  936. """Sets a context. Transactions can have multiple contexts
  937. and they should follow the format described in the "Contexts Interface"
  938. documentation.
  939. :param key: The name of the context.
  940. :param value: The information about the context.
  941. """
  942. self._contexts[key] = value
  943. def set_http_status(self, http_status):
  944. # type: (int) -> None
  945. """Sets the status of the Transaction according to the given HTTP status.
  946. :param http_status: The HTTP status code."""
  947. super().set_http_status(http_status)
  948. self.set_context("response", {"status_code": http_status})
  949. def to_json(self):
  950. # type: () -> Dict[str, Any]
  951. """Returns a JSON-compatible representation of the transaction."""
  952. rv = super().to_json()
  953. rv["name"] = self.name
  954. rv["source"] = self.source
  955. rv["sampled"] = self.sampled
  956. return rv
  957. def get_trace_context(self):
  958. # type: () -> Any
  959. trace_context = super().get_trace_context()
  960. if self._data:
  961. trace_context["data"] = self._data
  962. return trace_context
  963. def get_baggage(self):
  964. # type: () -> Baggage
  965. """Returns the :py:class:`~sentry_sdk.tracing_utils.Baggage`
  966. associated with the Transaction.
  967. The first time a new baggage with Sentry items is made,
  968. it will be frozen."""
  969. if not self._baggage or self._baggage.mutable:
  970. self._baggage = Baggage.populate_from_transaction(self)
  971. return self._baggage
  972. def _set_initial_sampling_decision(self, sampling_context):
  973. # type: (SamplingContext) -> None
  974. """
  975. Sets the transaction's sampling decision, according to the following
  976. precedence rules:
  977. 1. If a sampling decision is passed to `start_transaction`
  978. (`start_transaction(name: "my transaction", sampled: True)`), that
  979. decision will be used, regardless of anything else
  980. 2. If `traces_sampler` is defined, its decision will be used. It can
  981. choose to keep or ignore any parent sampling decision, or use the
  982. sampling context data to make its own decision or to choose a sample
  983. rate for the transaction.
  984. 3. If `traces_sampler` is not defined, but there's a parent sampling
  985. decision, the parent sampling decision will be used.
  986. 4. If `traces_sampler` is not defined and there's no parent sampling
  987. decision, `traces_sample_rate` will be used.
  988. """
  989. client = sentry_sdk.get_client()
  990. transaction_description = self._get_log_representation()
  991. # nothing to do if tracing is disabled
  992. if not has_tracing_enabled(client.options):
  993. self.sampled = False
  994. return
  995. # if the user has forced a sampling decision by passing a `sampled`
  996. # value when starting the transaction, go with that
  997. if self.sampled is not None:
  998. self.sample_rate = float(self.sampled)
  999. return
  1000. # we would have bailed already if neither `traces_sampler` nor
  1001. # `traces_sample_rate` were defined, so one of these should work; prefer
  1002. # the hook if so
  1003. sample_rate = (
  1004. client.options["traces_sampler"](sampling_context)
  1005. if callable(client.options.get("traces_sampler"))
  1006. # default inheritance behavior
  1007. else (
  1008. sampling_context["parent_sampled"]
  1009. if sampling_context["parent_sampled"] is not None
  1010. else client.options["traces_sample_rate"]
  1011. )
  1012. )
  1013. # Since this is coming from the user (or from a function provided by the
  1014. # user), who knows what we might get. (The only valid values are
  1015. # booleans or numbers between 0 and 1.)
  1016. if not is_valid_sample_rate(sample_rate, source="Tracing"):
  1017. logger.warning(
  1018. "[Tracing] Discarding {transaction_description} because of invalid sample rate.".format(
  1019. transaction_description=transaction_description,
  1020. )
  1021. )
  1022. self.sampled = False
  1023. return
  1024. self.sample_rate = float(sample_rate)
  1025. if client.monitor:
  1026. self.sample_rate /= 2**client.monitor.downsample_factor
  1027. # if the function returned 0 (or false), or if `traces_sample_rate` is
  1028. # 0, it's a sign the transaction should be dropped
  1029. if not self.sample_rate:
  1030. logger.debug(
  1031. "[Tracing] Discarding {transaction_description} because {reason}".format(
  1032. transaction_description=transaction_description,
  1033. reason=(
  1034. "traces_sampler returned 0 or False"
  1035. if callable(client.options.get("traces_sampler"))
  1036. else "traces_sample_rate is set to 0"
  1037. ),
  1038. )
  1039. )
  1040. self.sampled = False
  1041. return
  1042. # Now we roll the dice.
  1043. self.sampled = self._sample_rand < self.sample_rate
  1044. if self.sampled:
  1045. logger.debug(
  1046. "[Tracing] Starting {transaction_description}".format(
  1047. transaction_description=transaction_description,
  1048. )
  1049. )
  1050. else:
  1051. logger.debug(
  1052. "[Tracing] Discarding {transaction_description} because it's not included in the random sample (sampling rate = {sample_rate})".format(
  1053. transaction_description=transaction_description,
  1054. sample_rate=self.sample_rate,
  1055. )
  1056. )
  1057. class NoOpSpan(Span):
  1058. def __repr__(self):
  1059. # type: () -> str
  1060. return "<%s>" % self.__class__.__name__
  1061. @property
  1062. def containing_transaction(self):
  1063. # type: () -> Optional[Transaction]
  1064. return None
  1065. def start_child(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs):
  1066. # type: (str, **Any) -> NoOpSpan
  1067. return NoOpSpan()
  1068. def to_traceparent(self):
  1069. # type: () -> str
  1070. return ""
  1071. def to_baggage(self):
  1072. # type: () -> Optional[Baggage]
  1073. return None
  1074. def get_baggage(self):
  1075. # type: () -> Optional[Baggage]
  1076. return None
  1077. def iter_headers(self):
  1078. # type: () -> Iterator[Tuple[str, str]]
  1079. return iter(())
  1080. def set_tag(self, key, value):
  1081. # type: (str, Any) -> None
  1082. pass
  1083. def set_data(self, key, value):
  1084. # type: (str, Any) -> None
  1085. pass
  1086. def update_data(self, data):
  1087. # type: (Dict[str, Any]) -> None
  1088. pass
  1089. def set_status(self, value):
  1090. # type: (str) -> None
  1091. pass
  1092. def set_http_status(self, http_status):
  1093. # type: (int) -> None
  1094. pass
  1095. def is_success(self):
  1096. # type: () -> bool
  1097. return True
  1098. def to_json(self):
  1099. # type: () -> Dict[str, Any]
  1100. return {}
  1101. def get_trace_context(self):
  1102. # type: () -> Any
  1103. return {}
  1104. def get_profile_context(self):
  1105. # type: () -> Any
  1106. return {}
  1107. def finish(
  1108. self,
  1109. scope=None, # type: Optional[sentry_sdk.Scope]
  1110. end_timestamp=None, # type: Optional[Union[float, datetime]]
  1111. *,
  1112. hub=None, # type: Optional[sentry_sdk.Hub]
  1113. ):
  1114. # type: (...) -> Optional[str]
  1115. """
  1116. The `hub` parameter is deprecated. Please use the `scope` parameter, instead.
  1117. """
  1118. pass
  1119. def set_measurement(self, name, value, unit=""):
  1120. # type: (str, float, MeasurementUnit) -> None
  1121. pass
  1122. def set_context(self, key, value):
  1123. # type: (str, dict[str, Any]) -> None
  1124. pass
  1125. def init_span_recorder(self, maxlen):
  1126. # type: (int) -> None
  1127. pass
  1128. def _set_initial_sampling_decision(self, sampling_context):
  1129. # type: (SamplingContext) -> None
  1130. pass
  1131. if TYPE_CHECKING:
  1132. @overload
  1133. def trace(
  1134. func=None, *, op=None, name=None, attributes=None, template=SPANTEMPLATE.DEFAULT
  1135. ):
  1136. # type: (None, Optional[str], Optional[str], Optional[dict[str, Any]], SPANTEMPLATE) -> Callable[[Callable[P, R]], Callable[P, R]]
  1137. # Handles: @trace() and @trace(op="custom")
  1138. pass
  1139. @overload
  1140. def trace(func):
  1141. # type: (Callable[P, R]) -> Callable[P, R]
  1142. # Handles: @trace
  1143. pass
  1144. def trace(
  1145. func=None, *, op=None, name=None, attributes=None, template=SPANTEMPLATE.DEFAULT
  1146. ):
  1147. # type: (Optional[Callable[P, R]], Optional[str], Optional[str], Optional[dict[str, Any]], SPANTEMPLATE) -> Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]]
  1148. """
  1149. Decorator to start a child span around a function call.
  1150. This decorator automatically creates a new span when the decorated function
  1151. is called, and finishes the span when the function returns or raises an exception.
  1152. :param func: The function to trace. When used as a decorator without parentheses,
  1153. this is the function being decorated. When used with parameters (e.g.,
  1154. ``@trace(op="custom")``, this should be None.
  1155. :type func: Callable or None
  1156. :param op: The operation name for the span. This is a high-level description
  1157. of what the span represents (e.g., "http.client", "db.query").
  1158. You can use predefined constants from :py:class:`sentry_sdk.consts.OP`
  1159. or provide your own string. If not provided, a default operation will
  1160. be assigned based on the template.
  1161. :type op: str or None
  1162. :param name: The human-readable name/description for the span. If not provided,
  1163. defaults to the function name. This provides more specific details about
  1164. what the span represents (e.g., "GET /api/users", "process_user_data").
  1165. :type name: str or None
  1166. :param attributes: A dictionary of key-value pairs to add as attributes to the span.
  1167. Attribute values must be strings, integers, floats, or booleans. These
  1168. attributes provide additional context about the span's execution.
  1169. :type attributes: dict[str, Any] or None
  1170. :param template: The type of span to create. This determines what kind of
  1171. span instrumentation and data collection will be applied. Use predefined
  1172. constants from :py:class:`sentry_sdk.consts.SPANTEMPLATE`.
  1173. The default is `SPANTEMPLATE.DEFAULT` which is the right choice for most
  1174. use cases.
  1175. :type template: :py:class:`sentry_sdk.consts.SPANTEMPLATE`
  1176. :returns: When used as ``@trace``, returns the decorated function. When used as
  1177. ``@trace(...)`` with parameters, returns a decorator function.
  1178. :rtype: Callable or decorator function
  1179. Example::
  1180. import sentry_sdk
  1181. from sentry_sdk.consts import OP, SPANTEMPLATE
  1182. # Simple usage with default values
  1183. @sentry_sdk.trace
  1184. def process_data():
  1185. # Function implementation
  1186. pass
  1187. # With custom parameters
  1188. @sentry_sdk.trace(
  1189. op=OP.DB_QUERY,
  1190. name="Get user data",
  1191. attributes={"postgres": True}
  1192. )
  1193. def make_db_query(sql):
  1194. # Function implementation
  1195. pass
  1196. # With a custom template
  1197. @sentry_sdk.trace(template=SPANTEMPLATE.AI_TOOL)
  1198. def calculate_interest_rate(amount, rate, years):
  1199. # Function implementation
  1200. pass
  1201. """
  1202. from sentry_sdk.tracing_utils import create_span_decorator
  1203. decorator = create_span_decorator(
  1204. op=op,
  1205. name=name,
  1206. attributes=attributes,
  1207. template=template,
  1208. )
  1209. if func:
  1210. return decorator(func)
  1211. else:
  1212. return decorator
  1213. # Circular imports
  1214. from sentry_sdk.tracing_utils import (
  1215. Baggage,
  1216. EnvironHeaders,
  1217. extract_sentrytrace_data,
  1218. _generate_sample_rand,
  1219. has_tracing_enabled,
  1220. maybe_create_breadcrumbs_from_span,
  1221. )