scope.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. import { isPlainObject, dateTimestampInSeconds, uuid4, logger } from '@sentry/utils';
  2. import { getGlobalEventProcessors, notifyEventProcessors } from './eventProcessors.js';
  3. import { updateSession } from './session.js';
  4. import { applyScopeDataToEvent } from './utils/applyScopeDataToEvent.js';
  5. /**
  6. * Default value for maximum number of breadcrumbs added to an event.
  7. */
  8. const DEFAULT_MAX_BREADCRUMBS = 100;
  9. /**
  10. * The global scope is kept in this module.
  11. * When accessing this via `getGlobalScope()` we'll make sure to set one if none is currently present.
  12. */
  13. let globalScope;
  14. /**
  15. * Holds additional event information. {@link Scope.applyToEvent} will be
  16. * called by the client before an event will be sent.
  17. */
  18. class Scope {
  19. /** Flag if notifying is happening. */
  20. /** Callback for client to receive scope changes. */
  21. /** Callback list that will be called after {@link applyToEvent}. */
  22. /** Array of breadcrumbs. */
  23. /** User */
  24. /** Tags */
  25. /** Extra */
  26. /** Contexts */
  27. /** Attachments */
  28. /** Propagation Context for distributed tracing */
  29. /**
  30. * A place to stash data which is needed at some point in the SDK's event processing pipeline but which shouldn't get
  31. * sent to Sentry
  32. */
  33. /** Fingerprint */
  34. /** Severity */
  35. // eslint-disable-next-line deprecation/deprecation
  36. /**
  37. * Transaction Name
  38. */
  39. /** Span */
  40. /** Session */
  41. /** Request Mode Session Status */
  42. /** The client on this scope */
  43. // NOTE: Any field which gets added here should get added not only to the constructor but also to the `clone` method.
  44. constructor() {
  45. this._notifyingListeners = false;
  46. this._scopeListeners = [];
  47. this._eventProcessors = [];
  48. this._breadcrumbs = [];
  49. this._attachments = [];
  50. this._user = {};
  51. this._tags = {};
  52. this._extra = {};
  53. this._contexts = {};
  54. this._sdkProcessingMetadata = {};
  55. this._propagationContext = generatePropagationContext();
  56. }
  57. /**
  58. * Inherit values from the parent scope.
  59. * @deprecated Use `scope.clone()` and `new Scope()` instead.
  60. */
  61. static clone(scope) {
  62. return scope ? scope.clone() : new Scope();
  63. }
  64. /**
  65. * Clone this scope instance.
  66. */
  67. clone() {
  68. const newScope = new Scope();
  69. newScope._breadcrumbs = [...this._breadcrumbs];
  70. newScope._tags = { ...this._tags };
  71. newScope._extra = { ...this._extra };
  72. newScope._contexts = { ...this._contexts };
  73. newScope._user = this._user;
  74. newScope._level = this._level;
  75. newScope._span = this._span;
  76. newScope._session = this._session;
  77. newScope._transactionName = this._transactionName;
  78. newScope._fingerprint = this._fingerprint;
  79. newScope._eventProcessors = [...this._eventProcessors];
  80. newScope._requestSession = this._requestSession;
  81. newScope._attachments = [...this._attachments];
  82. newScope._sdkProcessingMetadata = { ...this._sdkProcessingMetadata };
  83. newScope._propagationContext = { ...this._propagationContext };
  84. newScope._client = this._client;
  85. return newScope;
  86. }
  87. /** Update the client on the scope. */
  88. setClient(client) {
  89. this._client = client;
  90. }
  91. /**
  92. * Get the client assigned to this scope.
  93. *
  94. * It is generally recommended to use the global function `Sentry.getClient()` instead, unless you know what you are doing.
  95. */
  96. getClient() {
  97. return this._client;
  98. }
  99. /**
  100. * Add internal on change listener. Used for sub SDKs that need to store the scope.
  101. * @hidden
  102. */
  103. addScopeListener(callback) {
  104. this._scopeListeners.push(callback);
  105. }
  106. /**
  107. * @inheritDoc
  108. */
  109. addEventProcessor(callback) {
  110. this._eventProcessors.push(callback);
  111. return this;
  112. }
  113. /**
  114. * @inheritDoc
  115. */
  116. setUser(user) {
  117. // If null is passed we want to unset everything, but still define keys,
  118. // so that later down in the pipeline any existing values are cleared.
  119. this._user = user || {
  120. email: undefined,
  121. id: undefined,
  122. ip_address: undefined,
  123. segment: undefined,
  124. username: undefined,
  125. };
  126. if (this._session) {
  127. updateSession(this._session, { user });
  128. }
  129. this._notifyScopeListeners();
  130. return this;
  131. }
  132. /**
  133. * @inheritDoc
  134. */
  135. getUser() {
  136. return this._user;
  137. }
  138. /**
  139. * @inheritDoc
  140. */
  141. getRequestSession() {
  142. return this._requestSession;
  143. }
  144. /**
  145. * @inheritDoc
  146. */
  147. setRequestSession(requestSession) {
  148. this._requestSession = requestSession;
  149. return this;
  150. }
  151. /**
  152. * @inheritDoc
  153. */
  154. setTags(tags) {
  155. this._tags = {
  156. ...this._tags,
  157. ...tags,
  158. };
  159. this._notifyScopeListeners();
  160. return this;
  161. }
  162. /**
  163. * @inheritDoc
  164. */
  165. setTag(key, value) {
  166. this._tags = { ...this._tags, [key]: value };
  167. this._notifyScopeListeners();
  168. return this;
  169. }
  170. /**
  171. * @inheritDoc
  172. */
  173. setExtras(extras) {
  174. this._extra = {
  175. ...this._extra,
  176. ...extras,
  177. };
  178. this._notifyScopeListeners();
  179. return this;
  180. }
  181. /**
  182. * @inheritDoc
  183. */
  184. setExtra(key, extra) {
  185. this._extra = { ...this._extra, [key]: extra };
  186. this._notifyScopeListeners();
  187. return this;
  188. }
  189. /**
  190. * @inheritDoc
  191. */
  192. setFingerprint(fingerprint) {
  193. this._fingerprint = fingerprint;
  194. this._notifyScopeListeners();
  195. return this;
  196. }
  197. /**
  198. * @inheritDoc
  199. */
  200. setLevel(
  201. // eslint-disable-next-line deprecation/deprecation
  202. level,
  203. ) {
  204. this._level = level;
  205. this._notifyScopeListeners();
  206. return this;
  207. }
  208. /**
  209. * Sets the transaction name on the scope for future events.
  210. * @deprecated Use extra or tags instead.
  211. */
  212. setTransactionName(name) {
  213. this._transactionName = name;
  214. this._notifyScopeListeners();
  215. return this;
  216. }
  217. /**
  218. * @inheritDoc
  219. */
  220. setContext(key, context) {
  221. if (context === null) {
  222. // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
  223. delete this._contexts[key];
  224. } else {
  225. this._contexts[key] = context;
  226. }
  227. this._notifyScopeListeners();
  228. return this;
  229. }
  230. /**
  231. * Sets the Span on the scope.
  232. * @param span Span
  233. * @deprecated Instead of setting a span on a scope, use `startSpan()`/`startSpanManual()` instead.
  234. */
  235. setSpan(span) {
  236. this._span = span;
  237. this._notifyScopeListeners();
  238. return this;
  239. }
  240. /**
  241. * Returns the `Span` if there is one.
  242. * @deprecated Use `getActiveSpan()` instead.
  243. */
  244. getSpan() {
  245. return this._span;
  246. }
  247. /**
  248. * Returns the `Transaction` attached to the scope (if there is one).
  249. * @deprecated You should not rely on the transaction, but just use `startSpan()` APIs instead.
  250. */
  251. getTransaction() {
  252. // Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
  253. // have a pointer to the currently-active transaction.
  254. const span = this._span;
  255. // Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
  256. // Also, this method will be removed anyway.
  257. // eslint-disable-next-line deprecation/deprecation
  258. return span && span.transaction;
  259. }
  260. /**
  261. * @inheritDoc
  262. */
  263. setSession(session) {
  264. if (!session) {
  265. delete this._session;
  266. } else {
  267. this._session = session;
  268. }
  269. this._notifyScopeListeners();
  270. return this;
  271. }
  272. /**
  273. * @inheritDoc
  274. */
  275. getSession() {
  276. return this._session;
  277. }
  278. /**
  279. * @inheritDoc
  280. */
  281. update(captureContext) {
  282. if (!captureContext) {
  283. return this;
  284. }
  285. const scopeToMerge = typeof captureContext === 'function' ? captureContext(this) : captureContext;
  286. if (scopeToMerge instanceof Scope) {
  287. const scopeData = scopeToMerge.getScopeData();
  288. this._tags = { ...this._tags, ...scopeData.tags };
  289. this._extra = { ...this._extra, ...scopeData.extra };
  290. this._contexts = { ...this._contexts, ...scopeData.contexts };
  291. if (scopeData.user && Object.keys(scopeData.user).length) {
  292. this._user = scopeData.user;
  293. }
  294. if (scopeData.level) {
  295. this._level = scopeData.level;
  296. }
  297. if (scopeData.fingerprint.length) {
  298. this._fingerprint = scopeData.fingerprint;
  299. }
  300. if (scopeToMerge.getRequestSession()) {
  301. this._requestSession = scopeToMerge.getRequestSession();
  302. }
  303. if (scopeData.propagationContext) {
  304. this._propagationContext = scopeData.propagationContext;
  305. }
  306. } else if (isPlainObject(scopeToMerge)) {
  307. const scopeContext = captureContext ;
  308. this._tags = { ...this._tags, ...scopeContext.tags };
  309. this._extra = { ...this._extra, ...scopeContext.extra };
  310. this._contexts = { ...this._contexts, ...scopeContext.contexts };
  311. if (scopeContext.user) {
  312. this._user = scopeContext.user;
  313. }
  314. if (scopeContext.level) {
  315. this._level = scopeContext.level;
  316. }
  317. if (scopeContext.fingerprint) {
  318. this._fingerprint = scopeContext.fingerprint;
  319. }
  320. if (scopeContext.requestSession) {
  321. this._requestSession = scopeContext.requestSession;
  322. }
  323. if (scopeContext.propagationContext) {
  324. this._propagationContext = scopeContext.propagationContext;
  325. }
  326. }
  327. return this;
  328. }
  329. /**
  330. * @inheritDoc
  331. */
  332. clear() {
  333. this._breadcrumbs = [];
  334. this._tags = {};
  335. this._extra = {};
  336. this._user = {};
  337. this._contexts = {};
  338. this._level = undefined;
  339. this._transactionName = undefined;
  340. this._fingerprint = undefined;
  341. this._requestSession = undefined;
  342. this._span = undefined;
  343. this._session = undefined;
  344. this._notifyScopeListeners();
  345. this._attachments = [];
  346. this._propagationContext = generatePropagationContext();
  347. return this;
  348. }
  349. /**
  350. * @inheritDoc
  351. */
  352. addBreadcrumb(breadcrumb, maxBreadcrumbs) {
  353. const maxCrumbs = typeof maxBreadcrumbs === 'number' ? maxBreadcrumbs : DEFAULT_MAX_BREADCRUMBS;
  354. // No data has been changed, so don't notify scope listeners
  355. if (maxCrumbs <= 0) {
  356. return this;
  357. }
  358. const mergedBreadcrumb = {
  359. timestamp: dateTimestampInSeconds(),
  360. ...breadcrumb,
  361. };
  362. const breadcrumbs = this._breadcrumbs;
  363. breadcrumbs.push(mergedBreadcrumb);
  364. this._breadcrumbs = breadcrumbs.length > maxCrumbs ? breadcrumbs.slice(-maxCrumbs) : breadcrumbs;
  365. this._notifyScopeListeners();
  366. return this;
  367. }
  368. /**
  369. * @inheritDoc
  370. */
  371. getLastBreadcrumb() {
  372. return this._breadcrumbs[this._breadcrumbs.length - 1];
  373. }
  374. /**
  375. * @inheritDoc
  376. */
  377. clearBreadcrumbs() {
  378. this._breadcrumbs = [];
  379. this._notifyScopeListeners();
  380. return this;
  381. }
  382. /**
  383. * @inheritDoc
  384. */
  385. addAttachment(attachment) {
  386. this._attachments.push(attachment);
  387. return this;
  388. }
  389. /**
  390. * @inheritDoc
  391. * @deprecated Use `getScopeData()` instead.
  392. */
  393. getAttachments() {
  394. const data = this.getScopeData();
  395. return data.attachments;
  396. }
  397. /**
  398. * @inheritDoc
  399. */
  400. clearAttachments() {
  401. this._attachments = [];
  402. return this;
  403. }
  404. /** @inheritDoc */
  405. getScopeData() {
  406. const {
  407. _breadcrumbs,
  408. _attachments,
  409. _contexts,
  410. _tags,
  411. _extra,
  412. _user,
  413. _level,
  414. _fingerprint,
  415. _eventProcessors,
  416. _propagationContext,
  417. _sdkProcessingMetadata,
  418. _transactionName,
  419. _span,
  420. } = this;
  421. return {
  422. breadcrumbs: _breadcrumbs,
  423. attachments: _attachments,
  424. contexts: _contexts,
  425. tags: _tags,
  426. extra: _extra,
  427. user: _user,
  428. level: _level,
  429. fingerprint: _fingerprint || [],
  430. eventProcessors: _eventProcessors,
  431. propagationContext: _propagationContext,
  432. sdkProcessingMetadata: _sdkProcessingMetadata,
  433. transactionName: _transactionName,
  434. span: _span,
  435. };
  436. }
  437. /**
  438. * Applies data from the scope to the event and runs all event processors on it.
  439. *
  440. * @param event Event
  441. * @param hint Object containing additional information about the original exception, for use by the event processors.
  442. * @hidden
  443. * @deprecated Use `applyScopeDataToEvent()` directly
  444. */
  445. applyToEvent(
  446. event,
  447. hint = {},
  448. additionalEventProcessors = [],
  449. ) {
  450. applyScopeDataToEvent(event, this.getScopeData());
  451. // TODO (v8): Update this order to be: Global > Client > Scope
  452. const eventProcessors = [
  453. ...additionalEventProcessors,
  454. // eslint-disable-next-line deprecation/deprecation
  455. ...getGlobalEventProcessors(),
  456. ...this._eventProcessors,
  457. ];
  458. return notifyEventProcessors(eventProcessors, event, hint);
  459. }
  460. /**
  461. * Add data which will be accessible during event processing but won't get sent to Sentry
  462. */
  463. setSDKProcessingMetadata(newData) {
  464. this._sdkProcessingMetadata = { ...this._sdkProcessingMetadata, ...newData };
  465. return this;
  466. }
  467. /**
  468. * @inheritDoc
  469. */
  470. setPropagationContext(context) {
  471. this._propagationContext = context;
  472. return this;
  473. }
  474. /**
  475. * @inheritDoc
  476. */
  477. getPropagationContext() {
  478. return this._propagationContext;
  479. }
  480. /**
  481. * Capture an exception for this scope.
  482. *
  483. * @param exception The exception to capture.
  484. * @param hint Optinal additional data to attach to the Sentry event.
  485. * @returns the id of the captured Sentry event.
  486. */
  487. captureException(exception, hint) {
  488. const eventId = hint && hint.event_id ? hint.event_id : uuid4();
  489. if (!this._client) {
  490. logger.warn('No client configured on scope - will not capture exception!');
  491. return eventId;
  492. }
  493. const syntheticException = new Error('Sentry syntheticException');
  494. this._client.captureException(
  495. exception,
  496. {
  497. originalException: exception,
  498. syntheticException,
  499. ...hint,
  500. event_id: eventId,
  501. },
  502. this,
  503. );
  504. return eventId;
  505. }
  506. /**
  507. * Capture a message for this scope.
  508. *
  509. * @param message The message to capture.
  510. * @param level An optional severity level to report the message with.
  511. * @param hint Optional additional data to attach to the Sentry event.
  512. * @returns the id of the captured message.
  513. */
  514. captureMessage(message, level, hint) {
  515. const eventId = hint && hint.event_id ? hint.event_id : uuid4();
  516. if (!this._client) {
  517. logger.warn('No client configured on scope - will not capture message!');
  518. return eventId;
  519. }
  520. const syntheticException = new Error(message);
  521. this._client.captureMessage(
  522. message,
  523. level,
  524. {
  525. originalException: message,
  526. syntheticException,
  527. ...hint,
  528. event_id: eventId,
  529. },
  530. this,
  531. );
  532. return eventId;
  533. }
  534. /**
  535. * Captures a manually created event for this scope and sends it to Sentry.
  536. *
  537. * @param exception The event to capture.
  538. * @param hint Optional additional data to attach to the Sentry event.
  539. * @returns the id of the captured event.
  540. */
  541. captureEvent(event, hint) {
  542. const eventId = hint && hint.event_id ? hint.event_id : uuid4();
  543. if (!this._client) {
  544. logger.warn('No client configured on scope - will not capture event!');
  545. return eventId;
  546. }
  547. this._client.captureEvent(event, { ...hint, event_id: eventId }, this);
  548. return eventId;
  549. }
  550. /**
  551. * This will be called on every set call.
  552. */
  553. _notifyScopeListeners() {
  554. // We need this check for this._notifyingListeners to be able to work on scope during updates
  555. // If this check is not here we'll produce endless recursion when something is done with the scope
  556. // during the callback.
  557. if (!this._notifyingListeners) {
  558. this._notifyingListeners = true;
  559. this._scopeListeners.forEach(callback => {
  560. callback(this);
  561. });
  562. this._notifyingListeners = false;
  563. }
  564. }
  565. }
  566. /**
  567. * Get the global scope.
  568. * This scope is applied to _all_ events.
  569. */
  570. function getGlobalScope() {
  571. if (!globalScope) {
  572. globalScope = new Scope();
  573. }
  574. return globalScope;
  575. }
  576. /**
  577. * This is mainly needed for tests.
  578. * DO NOT USE this, as this is an internal API and subject to change.
  579. * @hidden
  580. */
  581. function setGlobalScope(scope) {
  582. globalScope = scope;
  583. }
  584. function generatePropagationContext() {
  585. return {
  586. traceId: uuid4(),
  587. spanId: uuid4().substring(16),
  588. };
  589. }
  590. export { Scope, getGlobalScope, setGlobalScope };
  591. //# sourceMappingURL=scope.js.map