UNPKG

@sentry/core

Version:
586 lines (496 loc) 15.3 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const session = require('./session.js'); const merge = require('./utils/merge.js'); const spanOnScope = require('./utils/spanOnScope.js'); const is = require('./utils-hoist/is.js'); const logger = require('./utils-hoist/logger.js'); const misc = require('./utils-hoist/misc.js'); const propagationContext = require('./utils-hoist/propagationContext.js'); const string = require('./utils-hoist/string.js'); const time = require('./utils-hoist/time.js'); /** * Default value for maximum number of breadcrumbs added to an event. */ const DEFAULT_MAX_BREADCRUMBS = 100; /** * A context to be used for capturing an event. * This can either be a Scope, or a partial ScopeContext, * or a callback that receives the current scope and returns a new scope to use. */ /** * Holds additional event information. */ class Scope { /** Flag if notifying is happening. */ /** Callback for client to receive scope changes. */ /** Callback list that will be called during event processing. */ /** 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 */ /** * Transaction Name * * IMPORTANT: The transaction name on the scope has nothing to do with root spans/transaction objects. * It's purpose is to assign a transaction to the scope that's added to non-transaction events. */ /** Session */ /** The client on this scope */ /** Contains the last event id of a captured event. */ // 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 = { traceId: propagationContext.generateTraceId(), sampleRand: Math.random(), }; } /** * Clone all data from this scope into a new scope. */ clone() { const newScope = new Scope(); newScope._breadcrumbs = [...this._breadcrumbs]; newScope._tags = { ...this._tags }; newScope._extra = { ...this._extra }; newScope._contexts = { ...this._contexts }; if (this._contexts.flags) { // We need to copy the `values` array so insertions on a cloned scope // won't affect the original array. newScope._contexts.flags = { values: [...this._contexts.flags.values], }; } newScope._user = this._user; newScope._level = this._level; newScope._session = this._session; newScope._transactionName = this._transactionName; newScope._fingerprint = this._fingerprint; newScope._eventProcessors = [...this._eventProcessors]; newScope._attachments = [...this._attachments]; newScope._sdkProcessingMetadata = { ...this._sdkProcessingMetadata }; newScope._propagationContext = { ...this._propagationContext }; newScope._client = this._client; newScope._lastEventId = this._lastEventId; spanOnScope._setSpanForScope(newScope, spanOnScope._getSpanForScope(this)); return newScope; } /** * Update the client assigned to this scope. * Note that not every scope will have a client assigned - isolation scopes & the global scope will generally not have a client, * as well as manually created scopes. */ setClient(client) { this._client = client; } /** * Set the ID of the last captured error event. * This is generally only captured on the isolation scope. */ setLastEventId(lastEventId) { this._lastEventId = lastEventId; } /** * Get the client assigned to this scope. */ getClient() { return this._client ; } /** * Get the ID of the last captured error event. * This is generally only available on the isolation scope. */ lastEventId() { return this._lastEventId; } /** * @inheritDoc */ addScopeListener(callback) { this._scopeListeners.push(callback); } /** * Add an event processor that will be called before an event is sent. */ addEventProcessor(callback) { this._eventProcessors.push(callback); return this; } /** * Set the user for this scope. * Set to `null` to unset the user. */ 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, username: undefined, }; if (this._session) { session.updateSession(this._session, { user }); } this._notifyScopeListeners(); return this; } /** * Get the user from this scope. */ getUser() { return this._user; } /** * Set an object that will be merged into existing tags on the scope, * and will be sent as tags data with the event. */ setTags(tags) { this._tags = { ...this._tags, ...tags, }; this._notifyScopeListeners(); return this; } /** * Set a single tag that will be sent as tags data with the event. */ setTag(key, value) { this._tags = { ...this._tags, [key]: value }; this._notifyScopeListeners(); return this; } /** * Set an object that will be merged into existing extra on the scope, * and will be sent as extra data with the event. */ setExtras(extras) { this._extra = { ...this._extra, ...extras, }; this._notifyScopeListeners(); return this; } /** * Set a single key:value extra entry that will be sent as extra data with the event. */ setExtra(key, extra) { this._extra = { ...this._extra, [key]: extra }; this._notifyScopeListeners(); return this; } /** * Sets the fingerprint on the scope to send with the events. * @param {string[]} fingerprint Fingerprint to group events in Sentry. */ setFingerprint(fingerprint) { this._fingerprint = fingerprint; this._notifyScopeListeners(); return this; } /** * Sets the level on the scope for future events. */ setLevel(level) { this._level = level; this._notifyScopeListeners(); return this; } /** * Sets the transaction name on the scope so that the name of e.g. taken server route or * the page location is attached to future events. * * IMPORTANT: Calling this function does NOT change the name of the currently active * root span. If you want to change the name of the active root span, use * `Sentry.updateSpanName(rootSpan, 'new name')` instead. * * By default, the SDK updates the scope's transaction name automatically on sensible * occasions, such as a page navigation or when handling a new request on the server. */ setTransactionName(name) { this._transactionName = name; this._notifyScopeListeners(); return this; } /** * Sets context data with the given name. * Data passed as context will be normalized. You can also pass `null` to unset the context. * Note that context data will not be merged - calling `setContext` will overwrite an existing context with the same key. */ 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; } /** * Set the session for the scope. */ setSession(session) { if (!session) { delete this._session; } else { this._session = session; } this._notifyScopeListeners(); return this; } /** * Get the session from the scope. */ getSession() { return this._session; } /** * Updates the scope with provided data. Can work in three variations: * - plain object containing updatable attributes * - Scope instance that'll extract the attributes from * - callback function that'll receive the current scope as an argument and allow for modifications */ update(captureContext) { if (!captureContext) { return this; } const scopeToMerge = typeof captureContext === 'function' ? captureContext(this) : captureContext; const scopeInstance = scopeToMerge instanceof Scope ? scopeToMerge.getScopeData() : is.isPlainObject(scopeToMerge) ? (captureContext ) : undefined; const { tags, extra, user, contexts, level, fingerprint = [], propagationContext } = scopeInstance || {}; this._tags = { ...this._tags, ...tags }; this._extra = { ...this._extra, ...extra }; this._contexts = { ...this._contexts, ...contexts }; if (user && Object.keys(user).length) { this._user = user; } if (level) { this._level = level; } if (fingerprint.length) { this._fingerprint = fingerprint; } if (propagationContext) { this._propagationContext = propagationContext; } return this; } /** * Clears the current scope and resets its properties. * Note: The client will not be cleared. */ clear() { // client is not cleared here on purpose! this._breadcrumbs = []; this._tags = {}; this._extra = {}; this._user = {}; this._contexts = {}; this._level = undefined; this._transactionName = undefined; this._fingerprint = undefined; this._session = undefined; spanOnScope._setSpanForScope(this, undefined); this._attachments = []; this.setPropagationContext({ traceId: propagationContext.generateTraceId(), sampleRand: Math.random() }); this._notifyScopeListeners(); return this; } /** * Adds a breadcrumb to the scope. * By default, the last 100 breadcrumbs are kept. */ 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: time.dateTimestampInSeconds(), ...breadcrumb, // Breadcrumb messages can theoretically be infinitely large and they're held in memory so we truncate them not to leak (too much) memory message: breadcrumb.message ? string.truncate(breadcrumb.message, 2048) : breadcrumb.message, }; this._breadcrumbs.push(mergedBreadcrumb); if (this._breadcrumbs.length > maxCrumbs) { this._breadcrumbs = this._breadcrumbs.slice(-maxCrumbs); this._client?.recordDroppedEvent('buffer_overflow', 'log_item'); } this._notifyScopeListeners(); return this; } /** * Get the last breadcrumb of the scope. */ getLastBreadcrumb() { return this._breadcrumbs[this._breadcrumbs.length - 1]; } /** * Clear all breadcrumbs from the scope. */ clearBreadcrumbs() { this._breadcrumbs = []; this._notifyScopeListeners(); return this; } /** * Add an attachment to the scope. */ addAttachment(attachment) { this._attachments.push(attachment); return this; } /** * Clear all attachments from the scope. */ clearAttachments() { this._attachments = []; return this; } /** * Get the data of this scope, which should be applied to an event during processing. */ getScopeData() { return { breadcrumbs: this._breadcrumbs, attachments: this._attachments, contexts: this._contexts, tags: this._tags, extra: this._extra, user: this._user, level: this._level, fingerprint: this._fingerprint || [], eventProcessors: this._eventProcessors, propagationContext: this._propagationContext, sdkProcessingMetadata: this._sdkProcessingMetadata, transactionName: this._transactionName, span: spanOnScope._getSpanForScope(this), }; } /** * Add data which will be accessible during event processing but won't get sent to Sentry. */ setSDKProcessingMetadata(newData) { this._sdkProcessingMetadata = merge.merge(this._sdkProcessingMetadata, newData, 2); return this; } /** * Add propagation context to the scope, used for distributed tracing */ setPropagationContext(context) { this._propagationContext = context; return this; } /** * Get propagation context from the scope, used for distributed tracing */ getPropagationContext() { return this._propagationContext; } /** * Capture an exception for this scope. * * @returns {string} The id of the captured Sentry event. */ captureException(exception, hint) { const eventId = hint?.event_id || misc.uuid4(); if (!this._client) { logger.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. * * @returns {string} The id of the captured message. */ captureMessage(message, level, hint) { const eventId = hint?.event_id || misc.uuid4(); if (!this._client) { logger.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; } /** * Capture a Sentry event for this scope. * * @returns {string} The id of the captured event. */ captureEvent(event, hint) { const eventId = hint?.event_id || misc.uuid4(); if (!this._client) { logger.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; } } } exports.Scope = Scope; //# sourceMappingURL=scope.js.map