span.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. import { uuid4, timestampInSeconds, logger, dropUndefinedKeys } from '@sentry/utils';
  2. import { DEBUG_BUILD } from '../debug-build.js';
  3. import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary.js';
  4. import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '../semanticAttributes.js';
  5. import { getRootSpan } from '../utils/getRootSpan.js';
  6. import { TRACE_FLAG_SAMPLED, TRACE_FLAG_NONE, spanToJSON, spanTimeInputToSeconds, spanToTraceHeader, spanToTraceContext } from '../utils/spanUtils.js';
  7. import { setHttpStatus } from './spanstatus.js';
  8. /**
  9. * Keeps track of finished spans for a given transaction
  10. * @internal
  11. * @hideconstructor
  12. * @hidden
  13. */
  14. class SpanRecorder {
  15. constructor(maxlen = 1000) {
  16. this._maxlen = maxlen;
  17. this.spans = [];
  18. }
  19. /**
  20. * This is just so that we don't run out of memory while recording a lot
  21. * of spans. At some point we just stop and flush out the start of the
  22. * trace tree (i.e.the first n spans with the smallest
  23. * start_timestamp).
  24. */
  25. add(span) {
  26. if (this.spans.length > this._maxlen) {
  27. // eslint-disable-next-line deprecation/deprecation
  28. span.spanRecorder = undefined;
  29. } else {
  30. this.spans.push(span);
  31. }
  32. }
  33. }
  34. /**
  35. * Span contains all data about a span
  36. */
  37. class Span {
  38. /**
  39. * Tags for the span.
  40. * @deprecated Use `spanToJSON(span).atttributes` instead.
  41. */
  42. /**
  43. * Data for the span.
  44. * @deprecated Use `spanToJSON(span).atttributes` instead.
  45. */
  46. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  47. /**
  48. * List of spans that were finalized
  49. *
  50. * @deprecated This property will no longer be public. Span recording will be handled internally.
  51. */
  52. /**
  53. * @inheritDoc
  54. * @deprecated Use top level `Sentry.getRootSpan()` instead
  55. */
  56. /**
  57. * The instrumenter that created this span.
  58. *
  59. * TODO (v8): This can probably be replaced by an `instanceOf` check of the span class.
  60. * the instrumenter can only be sentry or otel so we can check the span instance
  61. * to verify which one it is and remove this field entirely.
  62. *
  63. * @deprecated This field will be removed.
  64. */
  65. /** Epoch timestamp in seconds when the span started. */
  66. /** Epoch timestamp in seconds when the span ended. */
  67. /** Internal keeper of the status */
  68. /**
  69. * You should never call the constructor manually, always use `Sentry.startTransaction()`
  70. * or call `startChild()` on an existing span.
  71. * @internal
  72. * @hideconstructor
  73. * @hidden
  74. */
  75. constructor(spanContext = {}) {
  76. this._traceId = spanContext.traceId || uuid4();
  77. this._spanId = spanContext.spanId || uuid4().substring(16);
  78. this._startTime = spanContext.startTimestamp || timestampInSeconds();
  79. // eslint-disable-next-line deprecation/deprecation
  80. this.tags = spanContext.tags ? { ...spanContext.tags } : {};
  81. // eslint-disable-next-line deprecation/deprecation
  82. this.data = spanContext.data ? { ...spanContext.data } : {};
  83. // eslint-disable-next-line deprecation/deprecation
  84. this.instrumenter = spanContext.instrumenter || 'sentry';
  85. this._attributes = {};
  86. this.setAttributes({
  87. [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: spanContext.origin || 'manual',
  88. [SEMANTIC_ATTRIBUTE_SENTRY_OP]: spanContext.op,
  89. ...spanContext.attributes,
  90. });
  91. // eslint-disable-next-line deprecation/deprecation
  92. this._name = spanContext.name || spanContext.description;
  93. if (spanContext.parentSpanId) {
  94. this._parentSpanId = spanContext.parentSpanId;
  95. }
  96. // We want to include booleans as well here
  97. if ('sampled' in spanContext) {
  98. this._sampled = spanContext.sampled;
  99. }
  100. if (spanContext.status) {
  101. this._status = spanContext.status;
  102. }
  103. if (spanContext.endTimestamp) {
  104. this._endTime = spanContext.endTimestamp;
  105. }
  106. }
  107. // This rule conflicts with another eslint rule :(
  108. /* eslint-disable @typescript-eslint/member-ordering */
  109. /**
  110. * An alias for `description` of the Span.
  111. * @deprecated Use `spanToJSON(span).description` instead.
  112. */
  113. get name() {
  114. return this._name || '';
  115. }
  116. /**
  117. * Update the name of the span.
  118. * @deprecated Use `spanToJSON(span).description` instead.
  119. */
  120. set name(name) {
  121. this.updateName(name);
  122. }
  123. /**
  124. * Get the description of the Span.
  125. * @deprecated Use `spanToJSON(span).description` instead.
  126. */
  127. get description() {
  128. return this._name;
  129. }
  130. /**
  131. * Get the description of the Span.
  132. * @deprecated Use `spanToJSON(span).description` instead.
  133. */
  134. set description(description) {
  135. this._name = description;
  136. }
  137. /**
  138. * The ID of the trace.
  139. * @deprecated Use `spanContext().traceId` instead.
  140. */
  141. get traceId() {
  142. return this._traceId;
  143. }
  144. /**
  145. * The ID of the trace.
  146. * @deprecated You cannot update the traceId of a span after span creation.
  147. */
  148. set traceId(traceId) {
  149. this._traceId = traceId;
  150. }
  151. /**
  152. * The ID of the span.
  153. * @deprecated Use `spanContext().spanId` instead.
  154. */
  155. get spanId() {
  156. return this._spanId;
  157. }
  158. /**
  159. * The ID of the span.
  160. * @deprecated You cannot update the spanId of a span after span creation.
  161. */
  162. set spanId(spanId) {
  163. this._spanId = spanId;
  164. }
  165. /**
  166. * @inheritDoc
  167. *
  168. * @deprecated Use `startSpan` functions instead.
  169. */
  170. set parentSpanId(string) {
  171. this._parentSpanId = string;
  172. }
  173. /**
  174. * @inheritDoc
  175. *
  176. * @deprecated Use `spanToJSON(span).parent_span_id` instead.
  177. */
  178. get parentSpanId() {
  179. return this._parentSpanId;
  180. }
  181. /**
  182. * Was this span chosen to be sent as part of the sample?
  183. * @deprecated Use `isRecording()` instead.
  184. */
  185. get sampled() {
  186. return this._sampled;
  187. }
  188. /**
  189. * Was this span chosen to be sent as part of the sample?
  190. * @deprecated You cannot update the sampling decision of a span after span creation.
  191. */
  192. set sampled(sampled) {
  193. this._sampled = sampled;
  194. }
  195. /**
  196. * Attributes for the span.
  197. * @deprecated Use `spanToJSON(span).atttributes` instead.
  198. */
  199. get attributes() {
  200. return this._attributes;
  201. }
  202. /**
  203. * Attributes for the span.
  204. * @deprecated Use `setAttributes()` instead.
  205. */
  206. set attributes(attributes) {
  207. this._attributes = attributes;
  208. }
  209. /**
  210. * Timestamp in seconds (epoch time) indicating when the span started.
  211. * @deprecated Use `spanToJSON()` instead.
  212. */
  213. get startTimestamp() {
  214. return this._startTime;
  215. }
  216. /**
  217. * Timestamp in seconds (epoch time) indicating when the span started.
  218. * @deprecated In v8, you will not be able to update the span start time after creation.
  219. */
  220. set startTimestamp(startTime) {
  221. this._startTime = startTime;
  222. }
  223. /**
  224. * Timestamp in seconds when the span ended.
  225. * @deprecated Use `spanToJSON()` instead.
  226. */
  227. get endTimestamp() {
  228. return this._endTime;
  229. }
  230. /**
  231. * Timestamp in seconds when the span ended.
  232. * @deprecated Set the end time via `span.end()` instead.
  233. */
  234. set endTimestamp(endTime) {
  235. this._endTime = endTime;
  236. }
  237. /**
  238. * The status of the span.
  239. *
  240. * @deprecated Use `spanToJSON().status` instead to get the status.
  241. */
  242. get status() {
  243. return this._status;
  244. }
  245. /**
  246. * The status of the span.
  247. *
  248. * @deprecated Use `.setStatus()` instead to set or update the status.
  249. */
  250. set status(status) {
  251. this._status = status;
  252. }
  253. /**
  254. * Operation of the span
  255. *
  256. * @deprecated Use `spanToJSON().op` to read the op instead.
  257. */
  258. get op() {
  259. return this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] ;
  260. }
  261. /**
  262. * Operation of the span
  263. *
  264. * @deprecated Use `startSpan()` functions to set or `span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'op')
  265. * to update the span instead.
  266. */
  267. set op(op) {
  268. this.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, op);
  269. }
  270. /**
  271. * The origin of the span, giving context about what created the span.
  272. *
  273. * @deprecated Use `spanToJSON().origin` to read the origin instead.
  274. */
  275. get origin() {
  276. return this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] ;
  277. }
  278. /**
  279. * The origin of the span, giving context about what created the span.
  280. *
  281. * @deprecated Use `startSpan()` functions to set the origin instead.
  282. */
  283. set origin(origin) {
  284. this.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, origin);
  285. }
  286. /* eslint-enable @typescript-eslint/member-ordering */
  287. /** @inheritdoc */
  288. spanContext() {
  289. const { _spanId: spanId, _traceId: traceId, _sampled: sampled } = this;
  290. return {
  291. spanId,
  292. traceId,
  293. traceFlags: sampled ? TRACE_FLAG_SAMPLED : TRACE_FLAG_NONE,
  294. };
  295. }
  296. /**
  297. * Creates a new `Span` while setting the current `Span.id` as `parentSpanId`.
  298. * Also the `sampled` decision will be inherited.
  299. *
  300. * @deprecated Use `startSpan()`, `startSpanManual()` or `startInactiveSpan()` instead.
  301. */
  302. startChild(
  303. spanContext,
  304. ) {
  305. const childSpan = new Span({
  306. ...spanContext,
  307. parentSpanId: this._spanId,
  308. sampled: this._sampled,
  309. traceId: this._traceId,
  310. });
  311. // eslint-disable-next-line deprecation/deprecation
  312. childSpan.spanRecorder = this.spanRecorder;
  313. // eslint-disable-next-line deprecation/deprecation
  314. if (childSpan.spanRecorder) {
  315. // eslint-disable-next-line deprecation/deprecation
  316. childSpan.spanRecorder.add(childSpan);
  317. }
  318. const rootSpan = getRootSpan(this);
  319. // TODO: still set span.transaction here until we have a more permanent solution
  320. // Probably similarly to the weakmap we hold in node-experimental
  321. // eslint-disable-next-line deprecation/deprecation
  322. childSpan.transaction = rootSpan ;
  323. if (DEBUG_BUILD && rootSpan) {
  324. const opStr = (spanContext && spanContext.op) || '< unknown op >';
  325. const nameStr = spanToJSON(childSpan).description || '< unknown name >';
  326. const idStr = rootSpan.spanContext().spanId;
  327. const logMessage = `[Tracing] Starting '${opStr}' span on transaction '${nameStr}' (${idStr}).`;
  328. logger.log(logMessage);
  329. this._logMessage = logMessage;
  330. }
  331. return childSpan;
  332. }
  333. /**
  334. * Sets the tag attribute on the current span.
  335. *
  336. * Can also be used to unset a tag, by passing `undefined`.
  337. *
  338. * @param key Tag key
  339. * @param value Tag value
  340. * @deprecated Use `setAttribute()` instead.
  341. */
  342. setTag(key, value) {
  343. // eslint-disable-next-line deprecation/deprecation
  344. this.tags = { ...this.tags, [key]: value };
  345. return this;
  346. }
  347. /**
  348. * Sets the data attribute on the current span
  349. * @param key Data key
  350. * @param value Data value
  351. * @deprecated Use `setAttribute()` instead.
  352. */
  353. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  354. setData(key, value) {
  355. // eslint-disable-next-line deprecation/deprecation
  356. this.data = { ...this.data, [key]: value };
  357. return this;
  358. }
  359. /** @inheritdoc */
  360. setAttribute(key, value) {
  361. if (value === undefined) {
  362. // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
  363. delete this._attributes[key];
  364. } else {
  365. this._attributes[key] = value;
  366. }
  367. }
  368. /** @inheritdoc */
  369. setAttributes(attributes) {
  370. Object.keys(attributes).forEach(key => this.setAttribute(key, attributes[key]));
  371. }
  372. /**
  373. * @inheritDoc
  374. */
  375. setStatus(value) {
  376. this._status = value;
  377. return this;
  378. }
  379. /**
  380. * @inheritDoc
  381. * @deprecated Use top-level `setHttpStatus()` instead.
  382. */
  383. setHttpStatus(httpStatus) {
  384. setHttpStatus(this, httpStatus);
  385. return this;
  386. }
  387. /**
  388. * @inheritdoc
  389. *
  390. * @deprecated Use `.updateName()` instead.
  391. */
  392. setName(name) {
  393. this.updateName(name);
  394. }
  395. /**
  396. * @inheritDoc
  397. */
  398. updateName(name) {
  399. this._name = name;
  400. return this;
  401. }
  402. /**
  403. * @inheritDoc
  404. *
  405. * @deprecated Use `spanToJSON(span).status === 'ok'` instead.
  406. */
  407. isSuccess() {
  408. return this._status === 'ok';
  409. }
  410. /**
  411. * @inheritDoc
  412. *
  413. * @deprecated Use `.end()` instead.
  414. */
  415. finish(endTimestamp) {
  416. return this.end(endTimestamp);
  417. }
  418. /** @inheritdoc */
  419. end(endTimestamp) {
  420. // If already ended, skip
  421. if (this._endTime) {
  422. return;
  423. }
  424. const rootSpan = getRootSpan(this);
  425. if (
  426. DEBUG_BUILD &&
  427. // Don't call this for transactions
  428. rootSpan &&
  429. rootSpan.spanContext().spanId !== this._spanId
  430. ) {
  431. const logMessage = this._logMessage;
  432. if (logMessage) {
  433. logger.log((logMessage ).replace('Starting', 'Finishing'));
  434. }
  435. }
  436. this._endTime = spanTimeInputToSeconds(endTimestamp);
  437. }
  438. /**
  439. * @inheritDoc
  440. *
  441. * @deprecated Use `spanToTraceHeader()` instead.
  442. */
  443. toTraceparent() {
  444. return spanToTraceHeader(this);
  445. }
  446. /**
  447. * @inheritDoc
  448. *
  449. * @deprecated Use `spanToJSON()` or access the fields directly instead.
  450. */
  451. toContext() {
  452. return dropUndefinedKeys({
  453. data: this._getData(),
  454. description: this._name,
  455. endTimestamp: this._endTime,
  456. // eslint-disable-next-line deprecation/deprecation
  457. op: this.op,
  458. parentSpanId: this._parentSpanId,
  459. sampled: this._sampled,
  460. spanId: this._spanId,
  461. startTimestamp: this._startTime,
  462. status: this._status,
  463. // eslint-disable-next-line deprecation/deprecation
  464. tags: this.tags,
  465. traceId: this._traceId,
  466. });
  467. }
  468. /**
  469. * @inheritDoc
  470. *
  471. * @deprecated Update the fields directly instead.
  472. */
  473. updateWithContext(spanContext) {
  474. // eslint-disable-next-line deprecation/deprecation
  475. this.data = spanContext.data || {};
  476. // eslint-disable-next-line deprecation/deprecation
  477. this._name = spanContext.name || spanContext.description;
  478. this._endTime = spanContext.endTimestamp;
  479. // eslint-disable-next-line deprecation/deprecation
  480. this.op = spanContext.op;
  481. this._parentSpanId = spanContext.parentSpanId;
  482. this._sampled = spanContext.sampled;
  483. this._spanId = spanContext.spanId || this._spanId;
  484. this._startTime = spanContext.startTimestamp || this._startTime;
  485. this._status = spanContext.status;
  486. // eslint-disable-next-line deprecation/deprecation
  487. this.tags = spanContext.tags || {};
  488. this._traceId = spanContext.traceId || this._traceId;
  489. return this;
  490. }
  491. /**
  492. * @inheritDoc
  493. *
  494. * @deprecated Use `spanToTraceContext()` util function instead.
  495. */
  496. getTraceContext() {
  497. return spanToTraceContext(this);
  498. }
  499. /**
  500. * Get JSON representation of this span.
  501. *
  502. * @hidden
  503. * @internal This method is purely for internal purposes and should not be used outside
  504. * of SDK code. If you need to get a JSON representation of a span,
  505. * use `spanToJSON(span)` instead.
  506. */
  507. getSpanJSON() {
  508. return dropUndefinedKeys({
  509. data: this._getData(),
  510. description: this._name,
  511. op: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] ,
  512. parent_span_id: this._parentSpanId,
  513. span_id: this._spanId,
  514. start_timestamp: this._startTime,
  515. status: this._status,
  516. // eslint-disable-next-line deprecation/deprecation
  517. tags: Object.keys(this.tags).length > 0 ? this.tags : undefined,
  518. timestamp: this._endTime,
  519. trace_id: this._traceId,
  520. origin: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] ,
  521. _metrics_summary: getMetricSummaryJsonForSpan(this),
  522. });
  523. }
  524. /** @inheritdoc */
  525. isRecording() {
  526. return !this._endTime && !!this._sampled;
  527. }
  528. /**
  529. * Convert the object to JSON.
  530. * @deprecated Use `spanToJSON(span)` instead.
  531. */
  532. toJSON() {
  533. return this.getSpanJSON();
  534. }
  535. /**
  536. * Get the merged data for this span.
  537. * For now, this combines `data` and `attributes` together,
  538. * until eventually we can ingest `attributes` directly.
  539. */
  540. _getData()
  541. {
  542. // eslint-disable-next-line deprecation/deprecation
  543. const { data, _attributes: attributes } = this;
  544. const hasData = Object.keys(data).length > 0;
  545. const hasAttributes = Object.keys(attributes).length > 0;
  546. if (!hasData && !hasAttributes) {
  547. return undefined;
  548. }
  549. if (hasData && hasAttributes) {
  550. return {
  551. ...data,
  552. ...attributes,
  553. };
  554. }
  555. return hasData ? data : attributes;
  556. }
  557. }
  558. export { Span, SpanRecorder };
  559. //# sourceMappingURL=span.js.map