scope.js 16 KB

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