bifocal/node_modules/@sentry/core/esm/scope.js

687 lines
16 KiB
JavaScript

import { isPlainObject, dateTimestampInSeconds, uuid4, logger } from '@sentry/utils';
import { getGlobalEventProcessors, notifyEventProcessors } from './eventProcessors.js';
import { updateSession } from './session.js';
import { applyScopeDataToEvent } from './utils/applyScopeDataToEvent.js';
/**
* Default value for maximum number of breadcrumbs added to an event.
*/
const DEFAULT_MAX_BREADCRUMBS = 100;
/**
* The global scope is kept in this module.
* When accessing this via `getGlobalScope()` we'll make sure to set one if none is currently present.
*/
let globalScope;
/**
* 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 */
/** The client on this scope */
// 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.
* @deprecated Use `scope.clone()` and `new Scope()` instead.
*/
static clone(scope) {
return scope ? scope.clone() : new Scope();
}
/**
* Clone this scope instance.
*/
clone() {
const newScope = new Scope();
newScope._breadcrumbs = [...this._breadcrumbs];
newScope._tags = { ...this._tags };
newScope._extra = { ...this._extra };
newScope._contexts = { ...this._contexts };
newScope._user = this._user;
newScope._level = this._level;
newScope._span = this._span;
newScope._session = this._session;
newScope._transactionName = this._transactionName;
newScope._fingerprint = this._fingerprint;
newScope._eventProcessors = [...this._eventProcessors];
newScope._requestSession = this._requestSession;
newScope._attachments = [...this._attachments];
newScope._sdkProcessingMetadata = { ...this._sdkProcessingMetadata };
newScope._propagationContext = { ...this._propagationContext };
newScope._client = this._client;
return newScope;
}
/** Update the client on the scope. */
setClient(client) {
this._client = client;
}
/**
* Get the client assigned to this scope.
*
* It is generally recommended to use the global function `Sentry.getClient()` instead, unless you know what you are doing.
*/
getClient() {
return this._client;
}
/**
* 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) {
// If null is passed we want to unset everything, but still define keys,
// so that later down in the pipeline any existing values are cleared.
this._user = user || {
email: undefined,
id: undefined,
ip_address: undefined,
segment: undefined,
username: undefined,
};
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;
}
/**
* Sets the transaction name on the scope for future events.
*/
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;
}
/**
* Sets the Span on the scope.
* @param span Span
* @deprecated Instead of setting a span on a scope, use `startSpan()`/`startSpanManual()` instead.
*/
setSpan(span) {
this._span = span;
this._notifyScopeListeners();
return this;
}
/**
* Returns the `Span` if there is one.
* @deprecated Use `getActiveSpan()` instead.
*/
getSpan() {
return this._span;
}
/**
* Returns the `Transaction` attached to the scope (if there is one).
* @deprecated You should not rely on the transaction, but just use `startSpan()` APIs instead.
*/
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._span;
// Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction
// Also, this method will be removed anyway.
// eslint-disable-next-line deprecation/deprecation
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;
}
const scopeToMerge = typeof captureContext === 'function' ? captureContext(this) : captureContext;
if (scopeToMerge instanceof Scope) {
const scopeData = scopeToMerge.getScopeData();
this._tags = { ...this._tags, ...scopeData.tags };
this._extra = { ...this._extra, ...scopeData.extra };
this._contexts = { ...this._contexts, ...scopeData.contexts };
if (scopeData.user && Object.keys(scopeData.user).length) {
this._user = scopeData.user;
}
if (scopeData.level) {
this._level = scopeData.level;
}
if (scopeData.fingerprint.length) {
this._fingerprint = scopeData.fingerprint;
}
if (scopeToMerge.getRequestSession()) {
this._requestSession = scopeToMerge.getRequestSession();
}
if (scopeData.propagationContext) {
this._propagationContext = scopeData.propagationContext;
}
} else if (isPlainObject(scopeToMerge)) {
const scopeContext = captureContext ;
this._tags = { ...this._tags, ...scopeContext.tags };
this._extra = { ...this._extra, ...scopeContext.extra };
this._contexts = { ...this._contexts, ...scopeContext.contexts };
if (scopeContext.user) {
this._user = scopeContext.user;
}
if (scopeContext.level) {
this._level = scopeContext.level;
}
if (scopeContext.fingerprint) {
this._fingerprint = scopeContext.fingerprint;
}
if (scopeContext.requestSession) {
this._requestSession = scopeContext.requestSession;
}
if (scopeContext.propagationContext) {
this._propagationContext = scopeContext.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
* @deprecated Use `getScopeData()` instead.
*/
getAttachments() {
const data = this.getScopeData();
return data.attachments;
}
/**
* @inheritDoc
*/
clearAttachments() {
this._attachments = [];
return this;
}
/** @inheritDoc */
getScopeData() {
const {
_breadcrumbs,
_attachments,
_contexts,
_tags,
_extra,
_user,
_level,
_fingerprint,
_eventProcessors,
_propagationContext,
_sdkProcessingMetadata,
_transactionName,
_span,
} = this;
return {
breadcrumbs: _breadcrumbs,
attachments: _attachments,
contexts: _contexts,
tags: _tags,
extra: _extra,
user: _user,
level: _level,
fingerprint: _fingerprint || [],
eventProcessors: _eventProcessors,
propagationContext: _propagationContext,
sdkProcessingMetadata: _sdkProcessingMetadata,
transactionName: _transactionName,
span: _span,
};
}
/**
* 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
* @deprecated Use `applyScopeDataToEvent()` directly
*/
applyToEvent(
event,
hint = {},
additionalEventProcessors = [],
) {
applyScopeDataToEvent(event, this.getScopeData());
// TODO (v8): Update this order to be: Global > Client > Scope
const eventProcessors = [
...additionalEventProcessors,
// eslint-disable-next-line deprecation/deprecation
...getGlobalEventProcessors(),
...this._eventProcessors,
];
return notifyEventProcessors(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;
}
/**
* Capture an exception for this scope.
*
* @param exception The exception to capture.
* @param hint Optinal additional data to attach to the Sentry event.
* @returns the id of the captured Sentry event.
*/
captureException(exception, hint) {
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
if (!this._client) {
logger.warn('No client configured on scope - will not capture exception!');
return eventId;
}
const syntheticException = new Error('Sentry syntheticException');
this._client.captureException(
exception,
{
originalException: exception,
syntheticException,
...hint,
event_id: eventId,
},
this,
);
return eventId;
}
/**
* Capture a message for this scope.
*
* @param message The message to capture.
* @param level An optional severity level to report the message with.
* @param hint Optional additional data to attach to the Sentry event.
* @returns the id of the captured message.
*/
captureMessage(message, level, hint) {
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
if (!this._client) {
logger.warn('No client configured on scope - will not capture message!');
return eventId;
}
const syntheticException = new Error(message);
this._client.captureMessage(
message,
level,
{
originalException: message,
syntheticException,
...hint,
event_id: eventId,
},
this,
);
return eventId;
}
/**
* Captures a manually created event for this scope and sends it to Sentry.
*
* @param exception The event to capture.
* @param hint Optional additional data to attach to the Sentry event.
* @returns the id of the captured event.
*/
captureEvent(event, hint) {
const eventId = hint && hint.event_id ? hint.event_id : uuid4();
if (!this._client) {
logger.warn('No client configured on scope - will not capture event!');
return eventId;
}
this._client.captureEvent(event, { ...hint, event_id: eventId }, this);
return eventId;
}
/**
* 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;
}
}
}
/**
* Get the global scope.
* This scope is applied to _all_ events.
*/
function getGlobalScope() {
if (!globalScope) {
globalScope = new Scope();
}
return globalScope;
}
/**
* This is mainly needed for tests.
* DO NOT USE this, as this is an internal API and subject to change.
* @hidden
*/
function setGlobalScope(scope) {
globalScope = scope;
}
function generatePropagationContext() {
return {
traceId: uuid4(),
spanId: uuid4().substring(16),
};
}
export { Scope, getGlobalScope, setGlobalScope };
//# sourceMappingURL=scope.js.map