trace.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. import { tracingContextFromHeaders, logger, dropUndefinedKeys, addNonEnumerableProperty } from '@sentry/utils';
  2. import { DEBUG_BUILD } from '../debug-build.js';
  3. import { getCurrentScope, withScope } from '../exports.js';
  4. import { getCurrentHub, runWithAsyncContext, getIsolationScope } from '../hub.js';
  5. import { handleCallbackErrors } from '../utils/handleCallbackErrors.js';
  6. import { hasTracingEnabled } from '../utils/hasTracingEnabled.js';
  7. import { spanToJSON, spanTimeInputToSeconds } from '../utils/spanUtils.js';
  8. /**
  9. * Wraps a function with a transaction/span and finishes the span after the function is done.
  10. *
  11. * Note that if you have not enabled tracing extensions via `addTracingExtensions`
  12. * or you didn't set `tracesSampleRate`, this function will not generate spans
  13. * and the `span` returned from the callback will be undefined.
  14. *
  15. * This function is meant to be used internally and may break at any time. Use at your own risk.
  16. *
  17. * @internal
  18. * @private
  19. *
  20. * @deprecated Use `startSpan` instead.
  21. */
  22. function trace(
  23. context,
  24. callback,
  25. // eslint-disable-next-line @typescript-eslint/no-empty-function
  26. onError = () => {},
  27. // eslint-disable-next-line @typescript-eslint/no-empty-function
  28. afterFinish = () => {},
  29. ) {
  30. // eslint-disable-next-line deprecation/deprecation
  31. const hub = getCurrentHub();
  32. const scope = getCurrentScope();
  33. // eslint-disable-next-line deprecation/deprecation
  34. const parentSpan = scope.getSpan();
  35. const ctx = normalizeContext(context);
  36. const activeSpan = createChildSpanOrTransaction(hub, parentSpan, ctx);
  37. // eslint-disable-next-line deprecation/deprecation
  38. scope.setSpan(activeSpan);
  39. return handleCallbackErrors(
  40. () => callback(activeSpan),
  41. error => {
  42. activeSpan && activeSpan.setStatus('internal_error');
  43. onError(error, activeSpan);
  44. },
  45. () => {
  46. activeSpan && activeSpan.end();
  47. // eslint-disable-next-line deprecation/deprecation
  48. scope.setSpan(parentSpan);
  49. afterFinish();
  50. },
  51. );
  52. }
  53. /**
  54. * Wraps a function with a transaction/span and finishes the span after the function is done.
  55. * The created span is the active span and will be used as parent by other spans created inside the function
  56. * and can be accessed via `Sentry.getSpan()`, as long as the function is executed while the scope is active.
  57. *
  58. * If you want to create a span that is not set as active, use {@link startInactiveSpan}.
  59. *
  60. * Note that if you have not enabled tracing extensions via `addTracingExtensions`
  61. * or you didn't set `tracesSampleRate`, this function will not generate spans
  62. * and the `span` returned from the callback will be undefined.
  63. */
  64. function startSpan(context, callback) {
  65. const ctx = normalizeContext(context);
  66. return runWithAsyncContext(() => {
  67. return withScope(context.scope, scope => {
  68. // eslint-disable-next-line deprecation/deprecation
  69. const hub = getCurrentHub();
  70. // eslint-disable-next-line deprecation/deprecation
  71. const parentSpan = scope.getSpan();
  72. const shouldSkipSpan = context.onlyIfParent && !parentSpan;
  73. const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction(hub, parentSpan, ctx);
  74. // eslint-disable-next-line deprecation/deprecation
  75. scope.setSpan(activeSpan);
  76. return handleCallbackErrors(
  77. () => callback(activeSpan),
  78. () => {
  79. // Only update the span status if it hasn't been changed yet
  80. if (activeSpan) {
  81. const { status } = spanToJSON(activeSpan);
  82. if (!status || status === 'ok') {
  83. activeSpan.setStatus('internal_error');
  84. }
  85. }
  86. },
  87. () => activeSpan && activeSpan.end(),
  88. );
  89. });
  90. });
  91. }
  92. /**
  93. * @deprecated Use {@link startSpan} instead.
  94. */
  95. const startActiveSpan = startSpan;
  96. /**
  97. * Similar to `Sentry.startSpan`. Wraps a function with a transaction/span, but does not finish the span
  98. * after the function is done automatically. You'll have to call `span.end()` manually.
  99. *
  100. * The created span is the active span and will be used as parent by other spans created inside the function
  101. * and can be accessed via `Sentry.getActiveSpan()`, as long as the function is executed while the scope is active.
  102. *
  103. * Note that if you have not enabled tracing extensions via `addTracingExtensions`
  104. * or you didn't set `tracesSampleRate`, this function will not generate spans
  105. * and the `span` returned from the callback will be undefined.
  106. */
  107. function startSpanManual(
  108. context,
  109. callback,
  110. ) {
  111. const ctx = normalizeContext(context);
  112. return runWithAsyncContext(() => {
  113. return withScope(context.scope, scope => {
  114. // eslint-disable-next-line deprecation/deprecation
  115. const hub = getCurrentHub();
  116. // eslint-disable-next-line deprecation/deprecation
  117. const parentSpan = scope.getSpan();
  118. const shouldSkipSpan = context.onlyIfParent && !parentSpan;
  119. const activeSpan = shouldSkipSpan ? undefined : createChildSpanOrTransaction(hub, parentSpan, ctx);
  120. // eslint-disable-next-line deprecation/deprecation
  121. scope.setSpan(activeSpan);
  122. function finishAndSetSpan() {
  123. activeSpan && activeSpan.end();
  124. }
  125. return handleCallbackErrors(
  126. () => callback(activeSpan, finishAndSetSpan),
  127. () => {
  128. // Only update the span status if it hasn't been changed yet, and the span is not yet finished
  129. if (activeSpan && activeSpan.isRecording()) {
  130. const { status } = spanToJSON(activeSpan);
  131. if (!status || status === 'ok') {
  132. activeSpan.setStatus('internal_error');
  133. }
  134. }
  135. },
  136. );
  137. });
  138. });
  139. }
  140. /**
  141. * Creates a span. This span is not set as active, so will not get automatic instrumentation spans
  142. * as children or be able to be accessed via `Sentry.getSpan()`.
  143. *
  144. * If you want to create a span that is set as active, use {@link startSpan}.
  145. *
  146. * Note that if you have not enabled tracing extensions via `addTracingExtensions`
  147. * or you didn't set `tracesSampleRate` or `tracesSampler`, this function will not generate spans
  148. * and the `span` returned from the callback will be undefined.
  149. */
  150. function startInactiveSpan(context) {
  151. if (!hasTracingEnabled()) {
  152. return undefined;
  153. }
  154. const ctx = normalizeContext(context);
  155. // eslint-disable-next-line deprecation/deprecation
  156. const hub = getCurrentHub();
  157. const parentSpan = context.scope
  158. ? // eslint-disable-next-line deprecation/deprecation
  159. context.scope.getSpan()
  160. : getActiveSpan();
  161. const shouldSkipSpan = context.onlyIfParent && !parentSpan;
  162. if (shouldSkipSpan) {
  163. return undefined;
  164. }
  165. const isolationScope = getIsolationScope();
  166. const scope = getCurrentScope();
  167. let span;
  168. if (parentSpan) {
  169. // eslint-disable-next-line deprecation/deprecation
  170. span = parentSpan.startChild(ctx);
  171. } else {
  172. const { traceId, dsc, parentSpanId, sampled } = {
  173. ...isolationScope.getPropagationContext(),
  174. ...scope.getPropagationContext(),
  175. };
  176. // eslint-disable-next-line deprecation/deprecation
  177. span = hub.startTransaction({
  178. traceId,
  179. parentSpanId,
  180. parentSampled: sampled,
  181. ...ctx,
  182. metadata: {
  183. dynamicSamplingContext: dsc,
  184. // eslint-disable-next-line deprecation/deprecation
  185. ...ctx.metadata,
  186. },
  187. });
  188. }
  189. setCapturedScopesOnSpan(span, scope, isolationScope);
  190. return span;
  191. }
  192. /**
  193. * Returns the currently active span.
  194. */
  195. function getActiveSpan() {
  196. // eslint-disable-next-line deprecation/deprecation
  197. return getCurrentScope().getSpan();
  198. }
  199. const continueTrace = (
  200. {
  201. sentryTrace,
  202. baggage,
  203. }
  204. ,
  205. callback,
  206. ) => {
  207. // TODO(v8): Change this function so it doesn't do anything besides setting the propagation context on the current scope:
  208. /*
  209. return withScope((scope) => {
  210. const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
  211. scope.setPropagationContext(propagationContext);
  212. return callback();
  213. })
  214. */
  215. const currentScope = getCurrentScope();
  216. // eslint-disable-next-line deprecation/deprecation
  217. const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
  218. sentryTrace,
  219. baggage,
  220. );
  221. currentScope.setPropagationContext(propagationContext);
  222. if (DEBUG_BUILD && traceparentData) {
  223. logger.log(`[Tracing] Continuing trace ${traceparentData.traceId}.`);
  224. }
  225. const transactionContext = {
  226. ...traceparentData,
  227. metadata: dropUndefinedKeys({
  228. dynamicSamplingContext,
  229. }),
  230. };
  231. if (!callback) {
  232. return transactionContext;
  233. }
  234. return runWithAsyncContext(() => {
  235. return callback(transactionContext);
  236. });
  237. };
  238. function createChildSpanOrTransaction(
  239. hub,
  240. parentSpan,
  241. ctx,
  242. ) {
  243. if (!hasTracingEnabled()) {
  244. return undefined;
  245. }
  246. const isolationScope = getIsolationScope();
  247. const scope = getCurrentScope();
  248. let span;
  249. if (parentSpan) {
  250. // eslint-disable-next-line deprecation/deprecation
  251. span = parentSpan.startChild(ctx);
  252. } else {
  253. const { traceId, dsc, parentSpanId, sampled } = {
  254. ...isolationScope.getPropagationContext(),
  255. ...scope.getPropagationContext(),
  256. };
  257. // eslint-disable-next-line deprecation/deprecation
  258. span = hub.startTransaction({
  259. traceId,
  260. parentSpanId,
  261. parentSampled: sampled,
  262. ...ctx,
  263. metadata: {
  264. dynamicSamplingContext: dsc,
  265. // eslint-disable-next-line deprecation/deprecation
  266. ...ctx.metadata,
  267. },
  268. });
  269. }
  270. setCapturedScopesOnSpan(span, scope, isolationScope);
  271. return span;
  272. }
  273. /**
  274. * This converts StartSpanOptions to TransactionContext.
  275. * For the most part (for now) we accept the same options,
  276. * but some of them need to be transformed.
  277. *
  278. * Eventually the StartSpanOptions will be more aligned with OpenTelemetry.
  279. */
  280. function normalizeContext(context) {
  281. if (context.startTime) {
  282. const ctx = { ...context };
  283. ctx.startTimestamp = spanTimeInputToSeconds(context.startTime);
  284. delete ctx.startTime;
  285. return ctx;
  286. }
  287. return context;
  288. }
  289. const SCOPE_ON_START_SPAN_FIELD = '_sentryScope';
  290. const ISOLATION_SCOPE_ON_START_SPAN_FIELD = '_sentryIsolationScope';
  291. function setCapturedScopesOnSpan(span, scope, isolationScope) {
  292. if (span) {
  293. addNonEnumerableProperty(span, ISOLATION_SCOPE_ON_START_SPAN_FIELD, isolationScope);
  294. addNonEnumerableProperty(span, SCOPE_ON_START_SPAN_FIELD, scope);
  295. }
  296. }
  297. /**
  298. * Grabs the scope and isolation scope off a span that were active when the span was started.
  299. */
  300. function getCapturedScopesOnSpan(span) {
  301. return {
  302. scope: (span )[SCOPE_ON_START_SPAN_FIELD],
  303. isolationScope: (span )[ISOLATION_SCOPE_ON_START_SPAN_FIELD],
  304. };
  305. }
  306. export { continueTrace, getActiveSpan, getCapturedScopesOnSpan, startActiveSpan, startInactiveSpan, startSpan, startSpanManual, trace };
  307. //# sourceMappingURL=trace.js.map