@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
566 lines (491 loc) • 13.7 kB
JavaScript
import { isPlainObject, dateTimestampInSeconds, arrayify, uuid4 } from '@sentry/utils';
import { notifyEventProcessors, getGlobalEventProcessors } from './eventProcessors.js';
import { updateSession } from './session.js';
/**
* Default value for maximum number of breadcrumbs added to an event.
*/
const DEFAULT_MAX_BREADCRUMBS = 100;
/**
* Holds additional event information. {@link Scope.applyToEvent} will be
* called by the client before an event will be sent.
*/
class Scope {
/** Flag if notifying is happening. */
/** Callback for client to receive scope changes. */
/** Callback list that will be called after {@link applyToEvent}. */
/** Array of breadcrumbs. */
/** User */
/** Tags */
/** Extra */
/** Contexts */
/** Attachments */
/** Propagation Context for distributed tracing */
/**
* A place to stash data which is needed at some point in the SDK's event processing pipeline but which shouldn't get
* sent to Sentry
*/
/** Fingerprint */
/** Severity */
// eslint-disable-next-line deprecation/deprecation
/** Transaction Name */
/** Span */
/** Session */
/** Request Mode Session Status */
// NOTE: Any field which gets added here should get added not only to the constructor but also to the `clone` method.
constructor() {
this._notifyingListeners = false;
this._scopeListeners = [];
this._eventProcessors = [];
this._breadcrumbs = [];
this._attachments = [];
this._user = {};
this._tags = {};
this._extra = {};
this._contexts = {};
this._sdkProcessingMetadata = {};
this._propagationContext = generatePropagationContext();
}
/**
* Inherit values from the parent scope.
* @param scope to clone.
*/
static clone(scope) {
const newScope = new Scope();
if (scope) {
newScope._breadcrumbs = [...scope._breadcrumbs];
newScope._tags = { ...scope._tags };
newScope._extra = { ...scope._extra };
newScope._contexts = { ...scope._contexts };
newScope._user = scope._user;
newScope._level = scope._level;
newScope._span = scope._span;
newScope._session = scope._session;
newScope._transactionName = scope._transactionName;
newScope._fingerprint = scope._fingerprint;
newScope._eventProcessors = [...scope._eventProcessors];
newScope._requestSession = scope._requestSession;
newScope._attachments = [...scope._attachments];
newScope._sdkProcessingMetadata = { ...scope._sdkProcessingMetadata };
newScope._propagationContext = { ...scope._propagationContext };
}
return newScope;
}
/**
* Add internal on change listener. Used for sub SDKs that need to store the scope.
* @hidden
*/
addScopeListener(callback) {
this._scopeListeners.push(callback);
}
/**
* @inheritDoc
*/
addEventProcessor(callback) {
this._eventProcessors.push(callback);
return this;
}
/**
* @inheritDoc
*/
setUser(user) {
this._user = user || {};
if (this._session) {
updateSession(this._session, { user });
}
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
getUser() {
return this._user;
}
/**
* @inheritDoc
*/
getRequestSession() {
return this._requestSession;
}
/**
* @inheritDoc
*/
setRequestSession(requestSession) {
this._requestSession = requestSession;
return this;
}
/**
* @inheritDoc
*/
setTags(tags) {
this._tags = {
...this._tags,
...tags,
};
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
setTag(key, value) {
this._tags = { ...this._tags, [key]: value };
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
setExtras(extras) {
this._extra = {
...this._extra,
...extras,
};
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
setExtra(key, extra) {
this._extra = { ...this._extra, [key]: extra };
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
setFingerprint(fingerprint) {
this._fingerprint = fingerprint;
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
setLevel(
// eslint-disable-next-line deprecation/deprecation
level,
) {
this._level = level;
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
setTransactionName(name) {
this._transactionName = name;
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
setContext(key, context) {
if (context === null) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this._contexts[key];
} else {
this._contexts[key] = context;
}
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
setSpan(span) {
this._span = span;
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
getSpan() {
return this._span;
}
/**
* @inheritDoc
*/
getTransaction() {
// Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will
// have a pointer to the currently-active transaction.
const span = this.getSpan();
return span && span.transaction;
}
/**
* @inheritDoc
*/
setSession(session) {
if (!session) {
delete this._session;
} else {
this._session = session;
}
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
getSession() {
return this._session;
}
/**
* @inheritDoc
*/
update(captureContext) {
if (!captureContext) {
return this;
}
if (typeof captureContext === 'function') {
const updatedScope = (captureContext )(this);
return updatedScope instanceof Scope ? updatedScope : this;
}
if (captureContext instanceof Scope) {
this._tags = { ...this._tags, ...captureContext._tags };
this._extra = { ...this._extra, ...captureContext._extra };
this._contexts = { ...this._contexts, ...captureContext._contexts };
if (captureContext._user && Object.keys(captureContext._user).length) {
this._user = captureContext._user;
}
if (captureContext._level) {
this._level = captureContext._level;
}
if (captureContext._fingerprint) {
this._fingerprint = captureContext._fingerprint;
}
if (captureContext._requestSession) {
this._requestSession = captureContext._requestSession;
}
if (captureContext._propagationContext) {
this._propagationContext = captureContext._propagationContext;
}
} else if (isPlainObject(captureContext)) {
// eslint-disable-next-line no-param-reassign
captureContext = captureContext ;
this._tags = { ...this._tags, ...captureContext.tags };
this._extra = { ...this._extra, ...captureContext.extra };
this._contexts = { ...this._contexts, ...captureContext.contexts };
if (captureContext.user) {
this._user = captureContext.user;
}
if (captureContext.level) {
this._level = captureContext.level;
}
if (captureContext.fingerprint) {
this._fingerprint = captureContext.fingerprint;
}
if (captureContext.requestSession) {
this._requestSession = captureContext.requestSession;
}
if (captureContext.propagationContext) {
this._propagationContext = captureContext.propagationContext;
}
}
return this;
}
/**
* @inheritDoc
*/
clear() {
this._breadcrumbs = [];
this._tags = {};
this._extra = {};
this._user = {};
this._contexts = {};
this._level = undefined;
this._transactionName = undefined;
this._fingerprint = undefined;
this._requestSession = undefined;
this._span = undefined;
this._session = undefined;
this._notifyScopeListeners();
this._attachments = [];
this._propagationContext = generatePropagationContext();
return this;
}
/**
* @inheritDoc
*/
addBreadcrumb(breadcrumb, maxBreadcrumbs) {
const maxCrumbs = typeof maxBreadcrumbs === 'number' ? maxBreadcrumbs : DEFAULT_MAX_BREADCRUMBS;
// No data has been changed, so don't notify scope listeners
if (maxCrumbs <= 0) {
return this;
}
const mergedBreadcrumb = {
timestamp: dateTimestampInSeconds(),
...breadcrumb,
};
const breadcrumbs = this._breadcrumbs;
breadcrumbs.push(mergedBreadcrumb);
this._breadcrumbs = breadcrumbs.length > maxCrumbs ? breadcrumbs.slice(-maxCrumbs) : breadcrumbs;
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
getLastBreadcrumb() {
return this._breadcrumbs[this._breadcrumbs.length - 1];
}
/**
* @inheritDoc
*/
clearBreadcrumbs() {
this._breadcrumbs = [];
this._notifyScopeListeners();
return this;
}
/**
* @inheritDoc
*/
addAttachment(attachment) {
this._attachments.push(attachment);
return this;
}
/**
* @inheritDoc
*/
getAttachments() {
return this._attachments;
}
/**
* @inheritDoc
*/
clearAttachments() {
this._attachments = [];
return this;
}
/**
* Applies data from the scope to the event and runs all event processors on it.
*
* @param event Event
* @param hint Object containing additional information about the original exception, for use by the event processors.
* @hidden
*/
applyToEvent(
event,
hint = {},
additionalEventProcessors,
) {
if (this._extra && Object.keys(this._extra).length) {
event.extra = { ...this._extra, ...event.extra };
}
if (this._tags && Object.keys(this._tags).length) {
event.tags = { ...this._tags, ...event.tags };
}
if (this._user && Object.keys(this._user).length) {
event.user = { ...this._user, ...event.user };
}
if (this._contexts && Object.keys(this._contexts).length) {
event.contexts = { ...this._contexts, ...event.contexts };
}
if (this._level) {
event.level = this._level;
}
if (this._transactionName) {
event.transaction = this._transactionName;
}
// We want to set the trace context for normal events only if there isn't already
// a trace context on the event. There is a product feature in place where we link
// errors with transaction and it relies on that.
if (this._span) {
event.contexts = { trace: this._span.getTraceContext(), ...event.contexts };
const transaction = this._span.transaction;
if (transaction) {
event.sdkProcessingMetadata = {
dynamicSamplingContext: transaction.getDynamicSamplingContext(),
...event.sdkProcessingMetadata,
};
const transactionName = transaction.name;
if (transactionName) {
event.tags = { transaction: transactionName, ...event.tags };
}
}
}
this._applyFingerprint(event);
const scopeBreadcrumbs = this._getBreadcrumbs();
const breadcrumbs = [...(event.breadcrumbs || []), ...scopeBreadcrumbs];
event.breadcrumbs = breadcrumbs.length > 0 ? breadcrumbs : undefined;
event.sdkProcessingMetadata = {
...event.sdkProcessingMetadata,
...this._sdkProcessingMetadata,
propagationContext: this._propagationContext,
};
// TODO (v8): Update this order to be: Global > Client > Scope
return notifyEventProcessors(
[...(additionalEventProcessors || []), ...getGlobalEventProcessors(), ...this._eventProcessors],
event,
hint,
);
}
/**
* Add data which will be accessible during event processing but won't get sent to Sentry
*/
setSDKProcessingMetadata(newData) {
this._sdkProcessingMetadata = { ...this._sdkProcessingMetadata, ...newData };
return this;
}
/**
* @inheritDoc
*/
setPropagationContext(context) {
this._propagationContext = context;
return this;
}
/**
* @inheritDoc
*/
getPropagationContext() {
return this._propagationContext;
}
/**
* Get the breadcrumbs for this scope.
*/
_getBreadcrumbs() {
return this._breadcrumbs;
}
/**
* This will be called on every set call.
*/
_notifyScopeListeners() {
// We need this check for this._notifyingListeners to be able to work on scope during updates
// If this check is not here we'll produce endless recursion when something is done with the scope
// during the callback.
if (!this._notifyingListeners) {
this._notifyingListeners = true;
this._scopeListeners.forEach(callback => {
callback(this);
});
this._notifyingListeners = false;
}
}
/**
* Applies fingerprint from the scope to the event if there's one,
* uses message if there's one instead or get rid of empty fingerprint
*/
_applyFingerprint(event) {
// Make sure it's an array first and we actually have something in place
event.fingerprint = event.fingerprint ? arrayify(event.fingerprint) : [];
// If we have something on the scope, then merge it with event
if (this._fingerprint) {
event.fingerprint = event.fingerprint.concat(this._fingerprint);
}
// If we have no data at all, remove empty array default
if (event.fingerprint && !event.fingerprint.length) {
delete event.fingerprint;
}
}
}
function generatePropagationContext() {
return {
traceId: uuid4(),
spanId: uuid4().substring(16),
};
}
export { Scope };
//# sourceMappingURL=scope.js.map