UNPKG

@sentry/core

Version:
300 lines (258 loc) 10 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const carrier = require('../carrier.js'); const currentScopes = require('../currentScopes.js'); const debugBuild = require('../debug-build.js'); const applyScopeDataToEvent = require('../utils/applyScopeDataToEvent.js'); const debugLogger = require('../utils/debug-logger.js'); const spanOnScope = require('../utils/spanOnScope.js'); const time = require('../utils/time.js'); const traceInfo = require('../utils/trace-info.js'); const envelope = require('./envelope.js'); const MAX_METRIC_BUFFER_SIZE = 1000; /** * Converts a metric attribute to a serialized metric attribute. * * @param value - The value of the metric attribute. * @returns The serialized metric attribute. */ function metricAttributeToSerializedMetricAttribute(value) { switch (typeof value) { case 'number': if (Number.isInteger(value)) { return { value, type: 'integer', }; } return { value, type: 'double', }; case 'boolean': return { value, type: 'boolean', }; case 'string': return { value, type: 'string', }; default: { let stringValue = ''; try { stringValue = JSON.stringify(value) ?? ''; } catch { // Do nothing } return { value: stringValue, type: 'string', }; } } } /** * Sets a metric attribute if the value exists and the attribute key is not already present. * * @param metricAttributes - The metric attributes object to modify. * @param key - The attribute key to set. * @param value - The value to set (only sets if truthy and key not present). * @param setEvenIfPresent - Whether to set the attribute if it is present. Defaults to true. */ function setMetricAttribute( metricAttributes, key, value, setEvenIfPresent = true, ) { if (value && (setEvenIfPresent || !(key in metricAttributes))) { metricAttributes[key] = value; } } /** * Captures a serialized metric event and adds it to the metric buffer for the given client. * * @param client - A client. Uses the current client if not provided. * @param serializedMetric - The serialized metric event to capture. * * @experimental This method will experience breaking changes. This is not yet part of * the stable Sentry SDK API and can be changed or removed without warning. */ function _INTERNAL_captureSerializedMetric(client, serializedMetric) { const bufferMap = _getBufferMap(); const metricBuffer = _INTERNAL_getMetricBuffer(client); if (metricBuffer === undefined) { bufferMap.set(client, [serializedMetric]); } else { if (metricBuffer.length >= MAX_METRIC_BUFFER_SIZE) { _INTERNAL_flushMetricsBuffer(client, metricBuffer); bufferMap.set(client, [serializedMetric]); } else { bufferMap.set(client, [...metricBuffer, serializedMetric]); } } } /** * Options for capturing a metric internally. */ /** * Enriches metric with all contextual attributes (user, SDK metadata, replay, etc.) */ function _enrichMetricAttributes(beforeMetric, client, currentScope) { const { release, environment } = client.getOptions(); const processedMetricAttributes = { ...beforeMetric.attributes, }; // Add user attributes const { user: { id, email, username }, } = getMergedScopeData(currentScope); setMetricAttribute(processedMetricAttributes, 'user.id', id, false); setMetricAttribute(processedMetricAttributes, 'user.email', email, false); setMetricAttribute(processedMetricAttributes, 'user.name', username, false); // Add Sentry metadata setMetricAttribute(processedMetricAttributes, 'sentry.release', release); setMetricAttribute(processedMetricAttributes, 'sentry.environment', environment); // Add SDK metadata const { name, version } = client.getSdkMetadata()?.sdk ?? {}; setMetricAttribute(processedMetricAttributes, 'sentry.sdk.name', name); setMetricAttribute(processedMetricAttributes, 'sentry.sdk.version', version); // Add replay metadata const replay = client.getIntegrationByName ('Replay'); const replayId = replay?.getReplayId(true); setMetricAttribute(processedMetricAttributes, 'sentry.replay_id', replayId); if (replayId && replay?.getRecordingMode() === 'buffer') { setMetricAttribute(processedMetricAttributes, 'sentry._internal.replay_is_buffering', true); } return { ...beforeMetric, attributes: processedMetricAttributes, }; } /** * Creates a serialized metric ready to be sent to Sentry. */ function _buildSerializedMetric(metric, client, currentScope) { // Serialize attributes const serializedAttributes = {}; for (const key in metric.attributes) { if (metric.attributes[key] !== undefined) { serializedAttributes[key] = metricAttributeToSerializedMetricAttribute(metric.attributes[key]); } } // Get trace context const [, traceContext] = traceInfo._getTraceInfoFromScope(client, currentScope); const span = spanOnScope._getSpanForScope(currentScope); const traceId = span ? span.spanContext().traceId : traceContext?.trace_id; const spanId = span ? span.spanContext().spanId : undefined; return { timestamp: time.timestampInSeconds(), trace_id: traceId ?? '', span_id: spanId, name: metric.name, type: metric.type, unit: metric.unit, value: metric.value, attributes: serializedAttributes, }; } /** * Captures a metric event and sends it to Sentry. * * @param metric - The metric event to capture. * @param options - Options for capturing the metric. * * @experimental This method will experience breaking changes. This is not yet part of * the stable Sentry SDK API and can be changed or removed without warning. */ function _INTERNAL_captureMetric(beforeMetric, options) { const currentScope = options?.scope ?? currentScopes.getCurrentScope(); const captureSerializedMetric = options?.captureSerializedMetric ?? _INTERNAL_captureSerializedMetric; const client = currentScope?.getClient() ?? currentScopes.getClient(); if (!client) { debugBuild.DEBUG_BUILD && debugLogger.debug.warn('No client available to capture metric.'); return; } const { _experiments, enableMetrics, beforeSendMetric } = client.getOptions(); // todo(v11): Remove the experimental flag // eslint-disable-next-line deprecation/deprecation const metricsEnabled = enableMetrics ?? _experiments?.enableMetrics ?? true; if (!metricsEnabled) { debugBuild.DEBUG_BUILD && debugLogger.debug.warn('metrics option not enabled, metric will not be captured.'); return; } // Enrich metric with contextual attributes const enrichedMetric = _enrichMetricAttributes(beforeMetric, client, currentScope); client.emit('processMetric', enrichedMetric); // todo(v11): Remove the experimental `beforeSendMetric` // eslint-disable-next-line deprecation/deprecation const beforeSendCallback = beforeSendMetric || _experiments?.beforeSendMetric; const processedMetric = beforeSendCallback ? beforeSendCallback(enrichedMetric) : enrichedMetric; if (!processedMetric) { debugBuild.DEBUG_BUILD && debugLogger.debug.log('`beforeSendMetric` returned `null`, will not send metric.'); return; } const serializedMetric = _buildSerializedMetric(processedMetric, client, currentScope); debugBuild.DEBUG_BUILD && debugLogger.debug.log('[Metric]', serializedMetric); captureSerializedMetric(client, serializedMetric); client.emit('afterCaptureMetric', processedMetric); } /** * Flushes the metrics buffer to Sentry. * * @param client - A client. * @param maybeMetricBuffer - A metric buffer. Uses the metric buffer for the given client if not provided. * * @experimental This method will experience breaking changes. This is not yet part of * the stable Sentry SDK API and can be changed or removed without warning. */ function _INTERNAL_flushMetricsBuffer(client, maybeMetricBuffer) { const metricBuffer = maybeMetricBuffer ?? _INTERNAL_getMetricBuffer(client) ?? []; if (metricBuffer.length === 0) { return; } const clientOptions = client.getOptions(); const envelope$1 = envelope.createMetricEnvelope(metricBuffer, clientOptions._metadata, clientOptions.tunnel, client.getDsn()); // Clear the metric buffer after envelopes have been constructed. _getBufferMap().set(client, []); client.emit('flushMetrics'); // sendEnvelope should not throw // eslint-disable-next-line @typescript-eslint/no-floating-promises client.sendEnvelope(envelope$1); } /** * Returns the metric buffer for a given client. * * Exported for testing purposes. * * @param client - The client to get the metric buffer for. * @returns The metric buffer for the given client. */ function _INTERNAL_getMetricBuffer(client) { return _getBufferMap().get(client); } /** * Get the scope data for the current scope after merging with the * global scope and isolation scope. * * @param currentScope - The current scope. * @returns The scope data. */ function getMergedScopeData(currentScope) { const scopeData = currentScopes.getGlobalScope().getScopeData(); applyScopeDataToEvent.mergeScopeData(scopeData, currentScopes.getIsolationScope().getScopeData()); applyScopeDataToEvent.mergeScopeData(scopeData, currentScope.getScopeData()); return scopeData; } function _getBufferMap() { // The reference to the Client <> MetricBuffer map is stored on the carrier to ensure it's always the same return carrier.getGlobalSingleton('clientToMetricBufferMap', () => new WeakMap()); } exports._INTERNAL_captureMetric = _INTERNAL_captureMetric; exports._INTERNAL_captureSerializedMetric = _INTERNAL_captureSerializedMetric; exports._INTERNAL_flushMetricsBuffer = _INTERNAL_flushMetricsBuffer; exports._INTERNAL_getMetricBuffer = _INTERNAL_getMetricBuffer; exports.metricAttributeToSerializedMetricAttribute = metricAttributeToSerializedMetricAttribute; //# sourceMappingURL=internal.js.map