UNPKG

@sentry/core

Version:
346 lines (299 loc) 10.8 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const constants = require('../constants.js'); const currentScopes = require('../currentScopes.js'); const eventProcessors = require('../eventProcessors.js'); const scope = require('../scope.js'); const debugIds = require('../utils-hoist/debug-ids.js'); const misc = require('../utils-hoist/misc.js'); const normalize = require('../utils-hoist/normalize.js'); const string = require('../utils-hoist/string.js'); const time = require('../utils-hoist/time.js'); const applyScopeDataToEvent = require('./applyScopeDataToEvent.js'); /** * This type makes sure that we get either a CaptureContext, OR an EventHint. * It does not allow mixing them, which could lead to unexpected outcomes, e.g. this is disallowed: * { user: { id: '123' }, mechanism: { handled: false } } */ /** * Adds common information to events. * * The information includes release and environment from `options`, * breadcrumbs and context (extra, tags and user) from the scope. * * Information that is already present in the event is never overwritten. For * nested objects, such as the context, keys are merged. * * @param event The original event. * @param hint May contain additional information about the original exception. * @param scope A scope containing event metadata. * @returns A new event with more information. * @hidden */ function prepareEvent( options, event, hint, scope, client, isolationScope, ) { const { normalizeDepth = 3, normalizeMaxBreadth = 1000 } = options; const prepared = { ...event, event_id: event.event_id || hint.event_id || misc.uuid4(), timestamp: event.timestamp || time.dateTimestampInSeconds(), }; const integrations = hint.integrations || options.integrations.map(i => i.name); applyClientOptions(prepared, options); applyIntegrationsMetadata(prepared, integrations); if (client) { client.emit('applyFrameMetadata', event); } // Only put debug IDs onto frames for error events. if (event.type === undefined) { applyDebugIds(prepared, options.stackParser); } // If we have scope given to us, use it as the base for further modifications. // This allows us to prevent unnecessary copying of data if `captureContext` is not provided. const finalScope = getFinalScope(scope, hint.captureContext); if (hint.mechanism) { misc.addExceptionMechanism(prepared, hint.mechanism); } const clientEventProcessors = client ? client.getEventProcessors() : []; // This should be the last thing called, since we want that // {@link Scope.addEventProcessor} gets the finished prepared event. // Merge scope data together const data = currentScopes.getGlobalScope().getScopeData(); if (isolationScope) { const isolationData = isolationScope.getScopeData(); applyScopeDataToEvent.mergeScopeData(data, isolationData); } if (finalScope) { const finalScopeData = finalScope.getScopeData(); applyScopeDataToEvent.mergeScopeData(data, finalScopeData); } const attachments = [...(hint.attachments || []), ...data.attachments]; if (attachments.length) { hint.attachments = attachments; } applyScopeDataToEvent.applyScopeDataToEvent(prepared, data); const eventProcessors$1 = [ ...clientEventProcessors, // Run scope event processors _after_ all other processors ...data.eventProcessors, ]; const result = eventProcessors.notifyEventProcessors(eventProcessors$1, prepared, hint); return result.then(evt => { if (evt) { // We apply the debug_meta field only after all event processors have ran, so that if any event processors modified // file names (e.g.the RewriteFrames integration) the filename -> debug ID relationship isn't destroyed. // This should not cause any PII issues, since we're only moving data that is already on the event and not adding // any new data applyDebugMeta(evt); } if (typeof normalizeDepth === 'number' && normalizeDepth > 0) { return normalizeEvent(evt, normalizeDepth, normalizeMaxBreadth); } return evt; }); } /** * Enhances event using the client configuration. * It takes care of all "static" values like environment, release and `dist`, * as well as truncating overly long values. * * Only exported for tests. * * @param event event instance to be enhanced */ function applyClientOptions(event, options) { const { environment, release, dist, maxValueLength = 250 } = options; // empty strings do not make sense for environment, release, and dist // so we handle them the same as if they were not provided event.environment = event.environment || environment || constants.DEFAULT_ENVIRONMENT; if (!event.release && release) { event.release = release; } if (!event.dist && dist) { event.dist = dist; } const request = event.request; if (request?.url) { request.url = string.truncate(request.url, maxValueLength); } } /** * Puts debug IDs into the stack frames of an error event. */ function applyDebugIds(event, stackParser) { // Build a map of filename -> debug_id const filenameDebugIdMap = debugIds.getFilenameToDebugIdMap(stackParser); event.exception?.values?.forEach(exception => { exception.stacktrace?.frames?.forEach(frame => { if (frame.filename) { frame.debug_id = filenameDebugIdMap[frame.filename]; } }); }); } /** * Moves debug IDs from the stack frames of an error event into the debug_meta field. */ function applyDebugMeta(event) { // Extract debug IDs and filenames from the stack frames on the event. const filenameDebugIdMap = {}; event.exception?.values?.forEach(exception => { exception.stacktrace?.frames?.forEach(frame => { if (frame.debug_id) { if (frame.abs_path) { filenameDebugIdMap[frame.abs_path] = frame.debug_id; } else if (frame.filename) { filenameDebugIdMap[frame.filename] = frame.debug_id; } delete frame.debug_id; } }); }); if (Object.keys(filenameDebugIdMap).length === 0) { return; } // Fill debug_meta information event.debug_meta = event.debug_meta || {}; event.debug_meta.images = event.debug_meta.images || []; const images = event.debug_meta.images; Object.entries(filenameDebugIdMap).forEach(([filename, debug_id]) => { images.push({ type: 'sourcemap', code_file: filename, debug_id, }); }); } /** * This function adds all used integrations to the SDK info in the event. * @param event The event that will be filled with all integrations. */ function applyIntegrationsMetadata(event, integrationNames) { if (integrationNames.length > 0) { event.sdk = event.sdk || {}; event.sdk.integrations = [...(event.sdk.integrations || []), ...integrationNames]; } } /** * Applies `normalize` function on necessary `Event` attributes to make them safe for serialization. * Normalized keys: * - `breadcrumbs.data` * - `user` * - `contexts` * - `extra` * @param event Event * @returns Normalized event */ function normalizeEvent(event, depth, maxBreadth) { if (!event) { return null; } const normalized = { ...event, ...(event.breadcrumbs && { breadcrumbs: event.breadcrumbs.map(b => ({ ...b, ...(b.data && { data: normalize.normalize(b.data, depth, maxBreadth), }), })), }), ...(event.user && { user: normalize.normalize(event.user, depth, maxBreadth), }), ...(event.contexts && { contexts: normalize.normalize(event.contexts, depth, maxBreadth), }), ...(event.extra && { extra: normalize.normalize(event.extra, depth, maxBreadth), }), }; // event.contexts.trace stores information about a Transaction. Similarly, // event.spans[] stores information about child Spans. Given that a // Transaction is conceptually a Span, normalization should apply to both // Transactions and Spans consistently. // For now the decision is to skip normalization of Transactions and Spans, // so this block overwrites the normalized event to add back the original // Transaction information prior to normalization. if (event.contexts?.trace && normalized.contexts) { normalized.contexts.trace = event.contexts.trace; // event.contexts.trace.data may contain circular/dangerous data so we need to normalize it if (event.contexts.trace.data) { normalized.contexts.trace.data = normalize.normalize(event.contexts.trace.data, depth, maxBreadth); } } // event.spans[].data may contain circular/dangerous data so we need to normalize it if (event.spans) { normalized.spans = event.spans.map(span => { return { ...span, ...(span.data && { data: normalize.normalize(span.data, depth, maxBreadth), }), }; }); } // event.contexts.flags (FeatureFlagContext) stores context for our feature // flag integrations. It has a greater nesting depth than our other typed // Contexts, so we re-normalize with a fixed depth of 3 here. We do not want // to skip this in case of conflicting, user-provided context. if (event.contexts?.flags && normalized.contexts) { normalized.contexts.flags = normalize.normalize(event.contexts.flags, 3, maxBreadth); } return normalized; } function getFinalScope(scope$1, captureContext) { if (!captureContext) { return scope$1; } const finalScope = scope$1 ? scope$1.clone() : new scope.Scope(); finalScope.update(captureContext); return finalScope; } /** * Parse either an `EventHint` directly, or convert a `CaptureContext` to an `EventHint`. * This is used to allow to update method signatures that used to accept a `CaptureContext` but should now accept an `EventHint`. */ function parseEventHintOrCaptureContext( hint, ) { if (!hint) { return undefined; } // If you pass a Scope or `() => Scope` as CaptureContext, we just return this as captureContext if (hintIsScopeOrFunction(hint)) { return { captureContext: hint }; } if (hintIsScopeContext(hint)) { return { captureContext: hint, }; } return hint; } function hintIsScopeOrFunction(hint) { return hint instanceof scope.Scope || typeof hint === 'function'; } const captureContextKeys = [ 'user', 'level', 'extra', 'contexts', 'tags', 'fingerprint', 'propagationContext', ] ; function hintIsScopeContext(hint) { return Object.keys(hint).some(key => captureContextKeys.includes(key )); } exports.applyClientOptions = applyClientOptions; exports.applyDebugIds = applyDebugIds; exports.applyDebugMeta = applyDebugMeta; exports.parseEventHintOrCaptureContext = parseEventHintOrCaptureContext; exports.prepareEvent = prepareEvent; //# sourceMappingURL=prepareEvent.js.map