UNPKG

13.6 kBJavaScriptView Raw
1Object.defineProperty(exports, '__esModule', { value: true });
2
3const utils = require('@sentry/utils');
4const session = require('./session.js');
5
6/**
7 * Default value for maximum number of breadcrumbs added to an event.
8 */
9const DEFAULT_MAX_BREADCRUMBS = 100;
10
11/**
12 * Holds additional event information. {@link Scope.applyToEvent} will be
13 * called by the client before an event will be sent.
14 */
15class Scope {
16 /** Flag if notifying is happening. */
17
18 /** Callback for client to receive scope changes. */
19
20 /** Callback list that will be called after {@link applyToEvent}. */
21
22 /** Array of breadcrumbs. */
23
24 /** User */
25
26 /** Tags */
27
28 /** Extra */
29
30 /** Contexts */
31
32 /** Attachments */
33
34 /**
35 * A place to stash data which is needed at some point in the SDK's event processing pipeline but which shouldn't get
36 * sent to Sentry
37 */
38
39 /** Fingerprint */
40
41 /** Severity */
42 // eslint-disable-next-line deprecation/deprecation
43
44 /** Transaction Name */
45
46 /** Span */
47
48 /** Session */
49
50 /** Request Mode Session Status */
51
52 // NOTE: Any field which gets added here should get added not only to the constructor but also to the `clone` method.
53
54 constructor() {
55 this._notifyingListeners = false;
56 this._scopeListeners = [];
57 this._eventProcessors = [];
58 this._breadcrumbs = [];
59 this._attachments = [];
60 this._user = {};
61 this._tags = {};
62 this._extra = {};
63 this._contexts = {};
64 this._sdkProcessingMetadata = {};
65 }
66
67 /**
68 * Inherit values from the parent scope.
69 * @param scope to clone.
70 */
71 static clone(scope) {
72 const newScope = new Scope();
73 if (scope) {
74 newScope._breadcrumbs = [...scope._breadcrumbs];
75 newScope._tags = { ...scope._tags };
76 newScope._extra = { ...scope._extra };
77 newScope._contexts = { ...scope._contexts };
78 newScope._user = scope._user;
79 newScope._level = scope._level;
80 newScope._span = scope._span;
81 newScope._session = scope._session;
82 newScope._transactionName = scope._transactionName;
83 newScope._fingerprint = scope._fingerprint;
84 newScope._eventProcessors = [...scope._eventProcessors];
85 newScope._requestSession = scope._requestSession;
86 newScope._attachments = [...scope._attachments];
87 newScope._sdkProcessingMetadata = { ...scope._sdkProcessingMetadata };
88 }
89 return newScope;
90 }
91
92 /**
93 * Add internal on change listener. Used for sub SDKs that need to store the scope.
94 * @hidden
95 */
96 addScopeListener(callback) {
97 this._scopeListeners.push(callback);
98 }
99
100 /**
101 * @inheritDoc
102 */
103 addEventProcessor(callback) {
104 this._eventProcessors.push(callback);
105 return this;
106 }
107
108 /**
109 * @inheritDoc
110 */
111 setUser(user) {
112 this._user = user || {};
113 if (this._session) {
114 session.updateSession(this._session, { user });
115 }
116 this._notifyScopeListeners();
117 return this;
118 }
119
120 /**
121 * @inheritDoc
122 */
123 getUser() {
124 return this._user;
125 }
126
127 /**
128 * @inheritDoc
129 */
130 getRequestSession() {
131 return this._requestSession;
132 }
133
134 /**
135 * @inheritDoc
136 */
137 setRequestSession(requestSession) {
138 this._requestSession = requestSession;
139 return this;
140 }
141
142 /**
143 * @inheritDoc
144 */
145 setTags(tags) {
146 this._tags = {
147 ...this._tags,
148 ...tags,
149 };
150 this._notifyScopeListeners();
151 return this;
152 }
153
154 /**
155 * @inheritDoc
156 */
157 setTag(key, value) {
158 this._tags = { ...this._tags, [key]: value };
159 this._notifyScopeListeners();
160 return this;
161 }
162
163 /**
164 * @inheritDoc
165 */
166 setExtras(extras) {
167 this._extra = {
168 ...this._extra,
169 ...extras,
170 };
171 this._notifyScopeListeners();
172 return this;
173 }
174
175 /**
176 * @inheritDoc
177 */
178 setExtra(key, extra) {
179 this._extra = { ...this._extra, [key]: extra };
180 this._notifyScopeListeners();
181 return this;
182 }
183
184 /**
185 * @inheritDoc
186 */
187 setFingerprint(fingerprint) {
188 this._fingerprint = fingerprint;
189 this._notifyScopeListeners();
190 return this;
191 }
192
193 /**
194 * @inheritDoc
195 */
196 setLevel(
197 // eslint-disable-next-line deprecation/deprecation
198 level,
199 ) {
200 this._level = level;
201 this._notifyScopeListeners();
202 return this;
203 }
204
205 /**
206 * @inheritDoc
207 */
208 setTransactionName(name) {
209 this._transactionName = name;
210 this._notifyScopeListeners();
211 return this;
212 }
213
214 /**
215 * @inheritDoc
216 */
217 setContext(key, context) {
218 if (context === null) {
219 // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
220 delete this._contexts[key];
221 } else {
222 this._contexts[key] = context;
223 }
224
225 this._notifyScopeListeners();
226 return this;
227 }
228
229 /**
230 * @inheritDoc
231 */
232 setSpan(span) {
233 this._span = span;
234 this._notifyScopeListeners();
235 return this;
236 }
237
238 /**
239 * @inheritDoc
240 */
241 getSpan() {
242 return this._span;
243 }
244
245 /**
246 * @inheritDoc
247 */
248 getTransaction() {
249 // Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
250 // have a pointer to the currently-active transaction.
251 const span = this.getSpan();
252 return span && span.transaction;
253 }
254
255 /**
256 * @inheritDoc
257 */
258 setSession(session) {
259 if (!session) {
260 delete this._session;
261 } else {
262 this._session = session;
263 }
264 this._notifyScopeListeners();
265 return this;
266 }
267
268 /**
269 * @inheritDoc
270 */
271 getSession() {
272 return this._session;
273 }
274
275 /**
276 * @inheritDoc
277 */
278 update(captureContext) {
279 if (!captureContext) {
280 return this;
281 }
282
283 if (typeof captureContext === 'function') {
284 const updatedScope = (captureContext )(this);
285 return updatedScope instanceof Scope ? updatedScope : this;
286 }
287
288 if (captureContext instanceof Scope) {
289 this._tags = { ...this._tags, ...captureContext._tags };
290 this._extra = { ...this._extra, ...captureContext._extra };
291 this._contexts = { ...this._contexts, ...captureContext._contexts };
292 if (captureContext._user && Object.keys(captureContext._user).length) {
293 this._user = captureContext._user;
294 }
295 if (captureContext._level) {
296 this._level = captureContext._level;
297 }
298 if (captureContext._fingerprint) {
299 this._fingerprint = captureContext._fingerprint;
300 }
301 if (captureContext._requestSession) {
302 this._requestSession = captureContext._requestSession;
303 }
304 } else if (utils.isPlainObject(captureContext)) {
305 // eslint-disable-next-line no-param-reassign
306 captureContext = captureContext ;
307 this._tags = { ...this._tags, ...captureContext.tags };
308 this._extra = { ...this._extra, ...captureContext.extra };
309 this._contexts = { ...this._contexts, ...captureContext.contexts };
310 if (captureContext.user) {
311 this._user = captureContext.user;
312 }
313 if (captureContext.level) {
314 this._level = captureContext.level;
315 }
316 if (captureContext.fingerprint) {
317 this._fingerprint = captureContext.fingerprint;
318 }
319 if (captureContext.requestSession) {
320 this._requestSession = captureContext.requestSession;
321 }
322 }
323
324 return this;
325 }
326
327 /**
328 * @inheritDoc
329 */
330 clear() {
331 this._breadcrumbs = [];
332 this._tags = {};
333 this._extra = {};
334 this._user = {};
335 this._contexts = {};
336 this._level = undefined;
337 this._transactionName = undefined;
338 this._fingerprint = undefined;
339 this._requestSession = undefined;
340 this._span = undefined;
341 this._session = undefined;
342 this._notifyScopeListeners();
343 this._attachments = [];
344 return this;
345 }
346
347 /**
348 * @inheritDoc
349 */
350 addBreadcrumb(breadcrumb, maxBreadcrumbs) {
351 const maxCrumbs = typeof maxBreadcrumbs === 'number' ? maxBreadcrumbs : DEFAULT_MAX_BREADCRUMBS;
352
353 // No data has been changed, so don't notify scope listeners
354 if (maxCrumbs <= 0) {
355 return this;
356 }
357
358 const mergedBreadcrumb = {
359 timestamp: utils.dateTimestampInSeconds(),
360 ...breadcrumb,
361 };
362 this._breadcrumbs = [...this._breadcrumbs, mergedBreadcrumb].slice(-maxCrumbs);
363 this._notifyScopeListeners();
364
365 return this;
366 }
367
368 /**
369 * @inheritDoc
370 */
371 getLastBreadcrumb() {
372 return this._breadcrumbs[this._breadcrumbs.length - 1];
373 }
374
375 /**
376 * @inheritDoc
377 */
378 clearBreadcrumbs() {
379 this._breadcrumbs = [];
380 this._notifyScopeListeners();
381 return this;
382 }
383
384 /**
385 * @inheritDoc
386 */
387 addAttachment(attachment) {
388 this._attachments.push(attachment);
389 return this;
390 }
391
392 /**
393 * @inheritDoc
394 */
395 getAttachments() {
396 return this._attachments;
397 }
398
399 /**
400 * @inheritDoc
401 */
402 clearAttachments() {
403 this._attachments = [];
404 return this;
405 }
406
407 /**
408 * Applies data from the scope to the event and runs all event processors on it.
409 *
410 * @param event Event
411 * @param hint Object containing additional information about the original exception, for use by the event processors.
412 * @hidden
413 */
414 applyToEvent(event, hint = {}) {
415 if (this._extra && Object.keys(this._extra).length) {
416 event.extra = { ...this._extra, ...event.extra };
417 }
418 if (this._tags && Object.keys(this._tags).length) {
419 event.tags = { ...this._tags, ...event.tags };
420 }
421 if (this._user && Object.keys(this._user).length) {
422 event.user = { ...this._user, ...event.user };
423 }
424 if (this._contexts && Object.keys(this._contexts).length) {
425 event.contexts = { ...this._contexts, ...event.contexts };
426 }
427 if (this._level) {
428 event.level = this._level;
429 }
430 if (this._transactionName) {
431 event.transaction = this._transactionName;
432 }
433
434 // We want to set the trace context for normal events only if there isn't already
435 // a trace context on the event. There is a product feature in place where we link
436 // errors with transaction and it relies on that.
437 if (this._span) {
438 event.contexts = { trace: this._span.getTraceContext(), ...event.contexts };
439 const transactionName = this._span.transaction && this._span.transaction.name;
440 if (transactionName) {
441 event.tags = { transaction: transactionName, ...event.tags };
442 }
443 }
444
445 this._applyFingerprint(event);
446
447 event.breadcrumbs = [...(event.breadcrumbs || []), ...this._breadcrumbs];
448 event.breadcrumbs = event.breadcrumbs.length > 0 ? event.breadcrumbs : undefined;
449
450 event.sdkProcessingMetadata = { ...event.sdkProcessingMetadata, ...this._sdkProcessingMetadata };
451
452 return this._notifyEventProcessors([...getGlobalEventProcessors(), ...this._eventProcessors], event, hint);
453 }
454
455 /**
456 * Add data which will be accessible during event processing but won't get sent to Sentry
457 */
458 setSDKProcessingMetadata(newData) {
459 this._sdkProcessingMetadata = { ...this._sdkProcessingMetadata, ...newData };
460
461 return this;
462 }
463
464 /**
465 * This will be called after {@link applyToEvent} is finished.
466 */
467 _notifyEventProcessors(
468 processors,
469 event,
470 hint,
471 index = 0,
472 ) {
473 return new utils.SyncPromise((resolve, reject) => {
474 const processor = processors[index];
475 if (event === null || typeof processor !== 'function') {
476 resolve(event);
477 } else {
478 const result = processor({ ...event }, hint) ;
479
480 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
481 processor.id &&
482 result === null &&
483 utils.logger.log(`Event processor "${processor.id}" dropped event`);
484
485 if (utils.isThenable(result)) {
486 void result
487 .then(final => this._notifyEventProcessors(processors, final, hint, index + 1).then(resolve))
488 .then(null, reject);
489 } else {
490 void this._notifyEventProcessors(processors, result, hint, index + 1)
491 .then(resolve)
492 .then(null, reject);
493 }
494 }
495 });
496 }
497
498 /**
499 * This will be called on every set call.
500 */
501 _notifyScopeListeners() {
502 // We need this check for this._notifyingListeners to be able to work on scope during updates
503 // If this check is not here we'll produce endless recursion when something is done with the scope
504 // during the callback.
505 if (!this._notifyingListeners) {
506 this._notifyingListeners = true;
507 this._scopeListeners.forEach(callback => {
508 callback(this);
509 });
510 this._notifyingListeners = false;
511 }
512 }
513
514 /**
515 * Applies fingerprint from the scope to the event if there's one,
516 * uses message if there's one instead or get rid of empty fingerprint
517 */
518 _applyFingerprint(event) {
519 // Make sure it's an array first and we actually have something in place
520 event.fingerprint = event.fingerprint ? utils.arrayify(event.fingerprint) : [];
521
522 // If we have something on the scope, then merge it with event
523 if (this._fingerprint) {
524 event.fingerprint = event.fingerprint.concat(this._fingerprint);
525 }
526
527 // If we have no data at all, remove empty array default
528 if (event.fingerprint && !event.fingerprint.length) {
529 delete event.fingerprint;
530 }
531 }
532}
533
534/**
535 * Returns the global event processors.
536 */
537function getGlobalEventProcessors() {
538 return utils.getGlobalSingleton('globalEventProcessors', () => []);
539}
540
541/**
542 * Add a EventProcessor to be kept globally.
543 * @param callback EventProcessor to add
544 */
545function addGlobalEventProcessor(callback) {
546 getGlobalEventProcessors().push(callback);
547}
548
549exports.Scope = Scope;
550exports.addGlobalEventProcessor = addGlobalEventProcessor;
551//# sourceMappingURL=scope.js.map