transaction.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import { dropUndefinedKeys, logger } from '@sentry/utils';
  2. import { DEBUG_BUILD } from '../debug-build.js';
  3. import { getCurrentHub } from '../hub.js';
  4. import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary.js';
  5. import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '../semanticAttributes.js';
  6. import { spanTimeInputToSeconds, spanToJSON, spanToTraceContext } from '../utils/spanUtils.js';
  7. import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext.js';
  8. import { Span, SpanRecorder } from './span.js';
  9. import { getCapturedScopesOnSpan } from './trace.js';
  10. /** JSDoc */
  11. class Transaction extends Span {
  12. /**
  13. * The reference to the current hub.
  14. */
  15. // DO NOT yet remove this property, it is used in a hack for v7 backwards compatibility.
  16. /**
  17. * This constructor should never be called manually. Those instrumenting tracing should use
  18. * `Sentry.startTransaction()`, and internal methods should use `hub.startTransaction()`.
  19. * @internal
  20. * @hideconstructor
  21. * @hidden
  22. *
  23. * @deprecated Transactions will be removed in v8. Use spans instead.
  24. */
  25. constructor(transactionContext, hub) {
  26. super(transactionContext);
  27. this._measurements = {};
  28. this._contexts = {};
  29. // eslint-disable-next-line deprecation/deprecation
  30. this._hub = hub || getCurrentHub();
  31. this._name = transactionContext.name || '';
  32. this._metadata = {
  33. // eslint-disable-next-line deprecation/deprecation
  34. ...transactionContext.metadata,
  35. };
  36. this._trimEnd = transactionContext.trimEnd;
  37. // this is because transactions are also spans, and spans have a transaction pointer
  38. // TODO (v8): Replace this with another way to set the root span
  39. // eslint-disable-next-line deprecation/deprecation
  40. this.transaction = this;
  41. // If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
  42. // there is incoming Dynamic Sampling Context. (Either through an incoming request, a baggage meta-tag, or other means)
  43. const incomingDynamicSamplingContext = this._metadata.dynamicSamplingContext;
  44. if (incomingDynamicSamplingContext) {
  45. // We shallow copy this in case anything writes to the original reference of the passed in `dynamicSamplingContext`
  46. this._frozenDynamicSamplingContext = { ...incomingDynamicSamplingContext };
  47. }
  48. }
  49. // This sadly conflicts with the getter/setter ordering :(
  50. /* eslint-disable @typescript-eslint/member-ordering */
  51. /**
  52. * Getter for `name` property.
  53. * @deprecated Use `spanToJSON(span).description` instead.
  54. */
  55. get name() {
  56. return this._name;
  57. }
  58. /**
  59. * Setter for `name` property, which also sets `source` as custom.
  60. * @deprecated Use `updateName()` and `setMetadata()` instead.
  61. */
  62. set name(newName) {
  63. // eslint-disable-next-line deprecation/deprecation
  64. this.setName(newName);
  65. }
  66. /**
  67. * Get the metadata for this transaction.
  68. * @deprecated Use `spanGetMetadata(transaction)` instead.
  69. */
  70. get metadata() {
  71. // We merge attributes in for backwards compatibility
  72. return {
  73. // Defaults
  74. // eslint-disable-next-line deprecation/deprecation
  75. source: 'custom',
  76. spanMetadata: {},
  77. // Legacy metadata
  78. ...this._metadata,
  79. // From attributes
  80. ...(this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] && {
  81. source: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] ,
  82. }),
  83. ...(this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] && {
  84. sampleRate: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE] ,
  85. }),
  86. };
  87. }
  88. /**
  89. * Update the metadata for this transaction.
  90. * @deprecated Use `spanGetMetadata(transaction)` instead.
  91. */
  92. set metadata(metadata) {
  93. this._metadata = metadata;
  94. }
  95. /* eslint-enable @typescript-eslint/member-ordering */
  96. /**
  97. * Setter for `name` property, which also sets `source` on the metadata.
  98. *
  99. * @deprecated Use `.updateName()` and `.setAttribute()` instead.
  100. */
  101. setName(name, source = 'custom') {
  102. this._name = name;
  103. this.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
  104. }
  105. /** @inheritdoc */
  106. updateName(name) {
  107. this._name = name;
  108. return this;
  109. }
  110. /**
  111. * Attaches SpanRecorder to the span itself
  112. * @param maxlen maximum number of spans that can be recorded
  113. */
  114. initSpanRecorder(maxlen = 1000) {
  115. // eslint-disable-next-line deprecation/deprecation
  116. if (!this.spanRecorder) {
  117. // eslint-disable-next-line deprecation/deprecation
  118. this.spanRecorder = new SpanRecorder(maxlen);
  119. }
  120. // eslint-disable-next-line deprecation/deprecation
  121. this.spanRecorder.add(this);
  122. }
  123. /**
  124. * Set the context of a transaction event.
  125. * @deprecated Use either `.setAttribute()`, or set the context on the scope before creating the transaction.
  126. */
  127. setContext(key, context) {
  128. if (context === null) {
  129. // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
  130. delete this._contexts[key];
  131. } else {
  132. this._contexts[key] = context;
  133. }
  134. }
  135. /**
  136. * @inheritDoc
  137. *
  138. * @deprecated Use top-level `setMeasurement()` instead.
  139. */
  140. setMeasurement(name, value, unit = '') {
  141. this._measurements[name] = { value, unit };
  142. }
  143. /**
  144. * Store metadata on this transaction.
  145. * @deprecated Use attributes or store data on the scope instead.
  146. */
  147. setMetadata(newMetadata) {
  148. this._metadata = { ...this._metadata, ...newMetadata };
  149. }
  150. /**
  151. * @inheritDoc
  152. */
  153. end(endTimestamp) {
  154. const timestampInS = spanTimeInputToSeconds(endTimestamp);
  155. const transaction = this._finishTransaction(timestampInS);
  156. if (!transaction) {
  157. return undefined;
  158. }
  159. // eslint-disable-next-line deprecation/deprecation
  160. return this._hub.captureEvent(transaction);
  161. }
  162. /**
  163. * @inheritDoc
  164. */
  165. toContext() {
  166. // eslint-disable-next-line deprecation/deprecation
  167. const spanContext = super.toContext();
  168. return dropUndefinedKeys({
  169. ...spanContext,
  170. name: this._name,
  171. trimEnd: this._trimEnd,
  172. });
  173. }
  174. /**
  175. * @inheritDoc
  176. */
  177. updateWithContext(transactionContext) {
  178. // eslint-disable-next-line deprecation/deprecation
  179. super.updateWithContext(transactionContext);
  180. this._name = transactionContext.name || '';
  181. this._trimEnd = transactionContext.trimEnd;
  182. return this;
  183. }
  184. /**
  185. * @inheritdoc
  186. *
  187. * @experimental
  188. *
  189. * @deprecated Use top-level `getDynamicSamplingContextFromSpan` instead.
  190. */
  191. getDynamicSamplingContext() {
  192. return getDynamicSamplingContextFromSpan(this);
  193. }
  194. /**
  195. * Override the current hub with a new one.
  196. * Used if you want another hub to finish the transaction.
  197. *
  198. * @internal
  199. */
  200. setHub(hub) {
  201. this._hub = hub;
  202. }
  203. /**
  204. * Finish the transaction & prepare the event to send to Sentry.
  205. */
  206. _finishTransaction(endTimestamp) {
  207. // This transaction is already finished, so we should not flush it again.
  208. if (this._endTime !== undefined) {
  209. return undefined;
  210. }
  211. if (!this._name) {
  212. DEBUG_BUILD && logger.warn('Transaction has no name, falling back to `<unlabeled transaction>`.');
  213. this._name = '<unlabeled transaction>';
  214. }
  215. // just sets the end timestamp
  216. super.end(endTimestamp);
  217. // eslint-disable-next-line deprecation/deprecation
  218. const client = this._hub.getClient();
  219. if (client && client.emit) {
  220. client.emit('finishTransaction', this);
  221. }
  222. if (this._sampled !== true) {
  223. // At this point if `sampled !== true` we want to discard the transaction.
  224. DEBUG_BUILD && logger.log('[Tracing] Discarding transaction because its trace was not chosen to be sampled.');
  225. if (client) {
  226. client.recordDroppedEvent('sample_rate', 'transaction');
  227. }
  228. return undefined;
  229. }
  230. // eslint-disable-next-line deprecation/deprecation
  231. const finishedSpans = this.spanRecorder
  232. ? // eslint-disable-next-line deprecation/deprecation
  233. this.spanRecorder.spans.filter(span => span !== this && spanToJSON(span).timestamp)
  234. : [];
  235. if (this._trimEnd && finishedSpans.length > 0) {
  236. const endTimes = finishedSpans.map(span => spanToJSON(span).timestamp).filter(Boolean) ;
  237. this._endTime = endTimes.reduce((prev, current) => {
  238. return prev > current ? prev : current;
  239. });
  240. }
  241. const { scope: capturedSpanScope, isolationScope: capturedSpanIsolationScope } = getCapturedScopesOnSpan(this);
  242. // eslint-disable-next-line deprecation/deprecation
  243. const { metadata } = this;
  244. // eslint-disable-next-line deprecation/deprecation
  245. const { source } = metadata;
  246. const transaction = {
  247. contexts: {
  248. ...this._contexts,
  249. // We don't want to override trace context
  250. trace: spanToTraceContext(this),
  251. },
  252. // TODO: Pass spans serialized via `spanToJSON()` here instead in v8.
  253. spans: finishedSpans,
  254. start_timestamp: this._startTime,
  255. // eslint-disable-next-line deprecation/deprecation
  256. tags: this.tags,
  257. timestamp: this._endTime,
  258. transaction: this._name,
  259. type: 'transaction',
  260. sdkProcessingMetadata: {
  261. ...metadata,
  262. capturedSpanScope,
  263. capturedSpanIsolationScope,
  264. dynamicSamplingContext: getDynamicSamplingContextFromSpan(this),
  265. },
  266. _metrics_summary: getMetricSummaryJsonForSpan(this),
  267. ...(source && {
  268. transaction_info: {
  269. source,
  270. },
  271. }),
  272. };
  273. const hasMeasurements = Object.keys(this._measurements).length > 0;
  274. if (hasMeasurements) {
  275. DEBUG_BUILD &&
  276. logger.log(
  277. '[Measurements] Adding measurements to transaction',
  278. JSON.stringify(this._measurements, undefined, 2),
  279. );
  280. transaction.measurements = this._measurements;
  281. }
  282. // eslint-disable-next-line deprecation/deprecation
  283. DEBUG_BUILD && logger.log(`[Tracing] Finishing ${this.op} transaction: ${this._name}.`);
  284. return transaction;
  285. }
  286. }
  287. export { Transaction };
  288. //# sourceMappingURL=transaction.js.map