From 61e9a77a6f3aa56abda172da86993494b5ca8e25 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 7 Feb 2024 17:08:46 +0100 Subject: [PATCH 01/10] Revert "Revert "feat(core): Add metric summaries to spans (#10432)" (#10495)" This reverts commit 60a7d65605d7db4de854e4071896abf168e6bdc5. # Conflicts: # packages/types/src/event.ts --- .../tracing/metric-summaries/scenario.js | 48 ++++++++++ .../suites/tracing/metric-summaries/test.ts | 69 +++++++++++++++ packages/core/src/metrics/aggregator.ts | 11 ++- .../core/src/metrics/browser-aggregator.ts | 27 +++--- packages/core/src/metrics/metric-summary.ts | 87 +++++++++++++++++++ packages/core/src/tracing/span.ts | 2 + packages/core/src/tracing/transaction.ts | 2 + packages/types/src/event.ts | 3 +- packages/types/src/index.ts | 7 +- packages/types/src/span.ts | 9 ++ 10 files changed, 250 insertions(+), 15 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js create mode 100644 dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts create mode 100644 packages/core/src/metrics/metric-summary.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js new file mode 100644 index 000000000000..8a7dbabe0dec --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js @@ -0,0 +1,48 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, + _experiments: { + metricsAggregator: true, + }, +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +Sentry.startSpan( + { + name: 'Test Transaction', + op: 'transaction', + }, + () => { + Sentry.metrics.increment('root-counter'); + Sentry.metrics.increment('root-counter'); + + Sentry.startSpan( + { + name: 'Some other span', + op: 'transaction', + }, + () => { + Sentry.metrics.increment('root-counter'); + Sentry.metrics.increment('root-counter'); + Sentry.metrics.increment('root-counter', 2); + + Sentry.metrics.set('root-set', 'some-value'); + Sentry.metrics.set('root-set', 'another-value'); + Sentry.metrics.set('root-set', 'another-value'); + + Sentry.metrics.gauge('root-gauge', 42); + Sentry.metrics.gauge('root-gauge', 20); + + Sentry.metrics.distribution('root-distribution', 42); + Sentry.metrics.distribution('root-distribution', 20); + }, + ); + }, +); diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts new file mode 100644 index 000000000000..98ed58a75c57 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts @@ -0,0 +1,69 @@ +import { createRunner } from '../../../utils/runner'; + +const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + _metrics_summary: { + 'c:root-counter@none': { + min: 1, + max: 1, + count: 2, + sum: 2, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, + }, + }, + spans: expect.arrayContaining([ + expect.objectContaining({ + description: 'Some other span', + op: 'transaction', + _metrics_summary: { + 'c:root-counter@none': { + min: 1, + max: 2, + count: 3, + sum: 4, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, + }, + 's:root-set@none': { + min: 0, + max: 1, + count: 3, + sum: 2, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, + }, + 'g:root-gauge@none': { + min: 20, + max: 42, + count: 2, + sum: 62, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, + }, + 'd:root-distribution@none': { + min: 20, + max: 42, + count: 2, + sum: 62, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, + }, + }, + }), + ]), +}; + +test('Should add metric summaries to spans', done => { + createRunner(__dirname, 'scenario.js').expect({ transaction: EXPECTED_TRANSACTION }).start(done); +}); diff --git a/packages/core/src/metrics/aggregator.ts b/packages/core/src/metrics/aggregator.ts index 6a49fda5918b..2b331082ab3e 100644 --- a/packages/core/src/metrics/aggregator.ts +++ b/packages/core/src/metrics/aggregator.ts @@ -6,8 +6,9 @@ import type { Primitive, } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; -import { DEFAULT_FLUSH_INTERVAL, MAX_WEIGHT, NAME_AND_TAG_KEY_NORMALIZATION_REGEX } from './constants'; +import { DEFAULT_FLUSH_INTERVAL, MAX_WEIGHT, NAME_AND_TAG_KEY_NORMALIZATION_REGEX, SET_METRIC_TYPE } from './constants'; import { METRIC_MAP } from './instance'; +import { updateMetricSummaryOnActiveSpan } from './metric-summary'; import type { MetricBucket, MetricType } from './types'; import { getBucketKey, sanitizeTags } from './utils'; @@ -62,7 +63,11 @@ export class MetricsAggregator implements MetricsAggregatorBase { const tags = sanitizeTags(unsanitizedTags); const bucketKey = getBucketKey(metricType, name, unit, tags); + let bucketItem = this._buckets.get(bucketKey); + // If this is a set metric, we need to calculate the delta from the previous weight. + const previousWeight = bucketItem && metricType === SET_METRIC_TYPE ? bucketItem.metric.weight : 0; + if (bucketItem) { bucketItem.metric.add(value); // TODO(abhi): Do we need this check? @@ -82,6 +87,10 @@ export class MetricsAggregator implements MetricsAggregatorBase { this._buckets.set(bucketKey, bucketItem); } + // If value is a string, it's a set metric so calculate the delta from the previous weight. + const val = typeof value === 'string' ? bucketItem.metric.weight - previousWeight : value; + updateMetricSummaryOnActiveSpan(metricType, name, val, unit, unsanitizedTags, bucketKey); + // We need to keep track of the total weight of the buckets so that we can // flush them when we exceed the max weight. this._bucketsTotalWeight += bucketItem.metric.weight; diff --git a/packages/core/src/metrics/browser-aggregator.ts b/packages/core/src/metrics/browser-aggregator.ts index 5b5c81353024..40cfa1d404ab 100644 --- a/packages/core/src/metrics/browser-aggregator.ts +++ b/packages/core/src/metrics/browser-aggregator.ts @@ -1,14 +1,8 @@ -import type { - Client, - ClientOptions, - MeasurementUnit, - MetricBucketItem, - MetricsAggregator, - Primitive, -} from '@sentry/types'; +import type { Client, ClientOptions, MeasurementUnit, MetricsAggregator, Primitive } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; -import { DEFAULT_BROWSER_FLUSH_INTERVAL, NAME_AND_TAG_KEY_NORMALIZATION_REGEX } from './constants'; +import { DEFAULT_BROWSER_FLUSH_INTERVAL, NAME_AND_TAG_KEY_NORMALIZATION_REGEX, SET_METRIC_TYPE } from './constants'; import { METRIC_MAP } from './instance'; +import { updateMetricSummaryOnActiveSpan } from './metric-summary'; import type { MetricBucket, MetricType } from './types'; import { getBucketKey, sanitizeTags } from './utils'; @@ -46,7 +40,11 @@ export class BrowserMetricsAggregator implements MetricsAggregator { const tags = sanitizeTags(unsanitizedTags); const bucketKey = getBucketKey(metricType, name, unit, tags); - const bucketItem: MetricBucketItem | undefined = this._buckets.get(bucketKey); + + let bucketItem = this._buckets.get(bucketKey); + // If this is a set metric, we need to calculate the delta from the previous weight. + const previousWeight = bucketItem && metricType === SET_METRIC_TYPE ? bucketItem.metric.weight : 0; + if (bucketItem) { bucketItem.metric.add(value); // TODO(abhi): Do we need this check? @@ -54,7 +52,7 @@ export class BrowserMetricsAggregator implements MetricsAggregator { bucketItem.timestamp = timestamp; } } else { - this._buckets.set(bucketKey, { + bucketItem = { // @ts-expect-error we don't need to narrow down the type of value here, saves bundle size. metric: new METRIC_MAP[metricType](value), timestamp, @@ -62,8 +60,13 @@ export class BrowserMetricsAggregator implements MetricsAggregator { name, unit, tags, - }); + }; + this._buckets.set(bucketKey, bucketItem); } + + // If value is a string, it's a set metric so calculate the delta from the previous weight. + const val = typeof value === 'string' ? bucketItem.metric.weight - previousWeight : value; + updateMetricSummaryOnActiveSpan(metricType, name, val, unit, unsanitizedTags, bucketKey); } /** diff --git a/packages/core/src/metrics/metric-summary.ts b/packages/core/src/metrics/metric-summary.ts new file mode 100644 index 000000000000..dff610574b2c --- /dev/null +++ b/packages/core/src/metrics/metric-summary.ts @@ -0,0 +1,87 @@ +import type { MeasurementUnit, Span } from '@sentry/types'; +import type { MetricSummary } from '@sentry/types'; +import type { Primitive } from '@sentry/types'; +import { dropUndefinedKeys } from '@sentry/utils'; +import { getActiveSpan } from '../tracing'; +import type { MetricType } from './types'; + +/** + * key: bucketKey + * value: [exportKey, MetricSummary] + */ +type MetricSummaryStorage = Map; + +let SPAN_METRIC_SUMMARY: WeakMap | undefined; + +function getMetricStorageForSpan(span: Span): MetricSummaryStorage | undefined { + return SPAN_METRIC_SUMMARY ? SPAN_METRIC_SUMMARY.get(span) : undefined; +} + +/** + * Fetches the metric summary if it exists for the passed span + */ +export function getMetricSummaryJsonForSpan(span: Span): Record | undefined { + const storage = getMetricStorageForSpan(span); + + if (!storage) { + return undefined; + } + const output: Record = {}; + + for (const [, [exportKey, summary]] of storage) { + output[exportKey] = dropUndefinedKeys(summary); + } + + return output; +} + +/** + * Updates the metric summary on the currently active span + */ +export function updateMetricSummaryOnActiveSpan( + metricType: MetricType, + sanitizedName: string, + value: number, + unit: MeasurementUnit, + tags: Record, + bucketKey: string, +): void { + const span = getActiveSpan(); + if (span) { + const storage = getMetricStorageForSpan(span) || new Map(); + + const exportKey = `${metricType}:${sanitizedName}@${unit}`; + const bucketItem = storage.get(bucketKey); + + if (bucketItem) { + const [, summary] = bucketItem; + storage.set(bucketKey, [ + exportKey, + { + min: Math.min(summary.min, value), + max: Math.max(summary.max, value), + count: (summary.count += 1), + sum: (summary.sum += value), + tags: summary.tags, + }, + ]); + } else { + storage.set(bucketKey, [ + exportKey, + { + min: value, + max: value, + count: 1, + sum: value, + tags, + }, + ]); + } + + if (!SPAN_METRIC_SUMMARY) { + SPAN_METRIC_SUMMARY = new WeakMap(); + } + + SPAN_METRIC_SUMMARY.set(span, storage); + } +} diff --git a/packages/core/src/tracing/span.ts b/packages/core/src/tracing/span.ts index 165677455d7f..1e12628ae2a1 100644 --- a/packages/core/src/tracing/span.ts +++ b/packages/core/src/tracing/span.ts @@ -16,6 +16,7 @@ import type { import { dropUndefinedKeys, logger, timestampInSeconds, uuid4 } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; +import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes'; import { getRootSpan } from '../utils/getRootSpan'; import { @@ -624,6 +625,7 @@ export class Span implements SpanInterface { timestamp: this._endTime, trace_id: this._traceId, origin: this._attributes[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] as SpanOrigin | undefined, + _metrics_summary: getMetricSummaryJsonForSpan(this), }); } diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index 026723929471..709aa628f42e 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -15,6 +15,7 @@ import { dropUndefinedKeys, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import type { Hub } from '../hub'; import { getCurrentHub } from '../hub'; +import { getMetricSummaryJsonForSpan } from '../metrics/metric-summary'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes'; import { spanTimeInputToSeconds, spanToJSON, spanToTraceContext } from '../utils/spanUtils'; import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; @@ -331,6 +332,7 @@ export class Transaction extends SpanClass implements TransactionInterface { capturedSpanIsolationScope, dynamicSamplingContext: getDynamicSamplingContextFromSpan(this), }, + _metrics_summary: getMetricSummaryJsonForSpan(this), ...(source && { transaction_info: { source, diff --git a/packages/types/src/event.ts b/packages/types/src/event.ts index cfddd6bef471..40ca2e88be65 100644 --- a/packages/types/src/event.ts +++ b/packages/types/src/event.ts @@ -11,7 +11,7 @@ import type { Request } from './request'; import type { CaptureContext } from './scope'; import type { SdkInfo } from './sdkinfo'; import type { SeverityLevel } from './severity'; -import type { Span, SpanJSON } from './span'; +import type { MetricSummary, Span, SpanJSON } from './span'; import type { Thread } from './thread'; import type { TransactionSource } from './transaction'; import type { User } from './user'; @@ -72,6 +72,7 @@ export interface ErrorEvent extends Event { } export interface TransactionEvent extends Event { type: 'transaction'; + _metrics_summary?: Record; } /** JSDoc */ diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 0504803c49a1..abdf076cf033 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -98,6 +98,7 @@ export type { SpanJSON, SpanContextData, TraceFlag, + MetricSummary, } from './span'; export type { StackFrame } from './stackframe'; export type { Stacktrace, StackParser, StackLineParser, StackLineParserFn } from './stacktrace'; @@ -149,5 +150,9 @@ export type { export type { BrowserClientReplayOptions, BrowserClientProfilingOptions } from './browseroptions'; export type { CheckIn, MonitorConfig, FinishedCheckIn, InProgressCheckIn, SerializedCheckIn } from './checkin'; -export type { MetricsAggregator, MetricBucketItem, MetricInstance } from './metrics'; +export type { + MetricsAggregator, + MetricBucketItem, + MetricInstance, +} from './metrics'; export type { ParameterizedString } from './parameterize'; diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 73c2fbdaaaa8..0743497f1411 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -31,6 +31,14 @@ export type SpanAttributes = Partial<{ }> & Record; +export type MetricSummary = { + min: number; + max: number; + count: number; + sum: number; + tags?: Record | undefined; +}; + /** This type is aligned with the OpenTelemetry TimeInput type. */ export type SpanTimeInput = HrTime | number | Date; @@ -47,6 +55,7 @@ export interface SpanJSON { timestamp?: number; trace_id: string; origin?: SpanOrigin; + _metrics_summary?: Record; } // These are aligned with OpenTelemetry trace flags From c694cf3410535049dc6ebee02613e3e3e6588d04 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Wed, 7 Feb 2024 17:29:02 +0100 Subject: [PATCH 02/10] Update types and tests --- .../suites/tracing/metric-summaries/test.ts | 102 ++++++++++-------- packages/types/src/event.ts | 2 +- packages/types/src/span.ts | 2 +- 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts index 98ed58a75c57..99d9b762b537 100644 --- a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts @@ -3,62 +3,72 @@ import { createRunner } from '../../../utils/runner'; const EXPECTED_TRANSACTION = { transaction: 'Test Transaction', _metrics_summary: { - 'c:root-counter@none': { - min: 1, - max: 1, - count: 2, - sum: 2, - tags: { - release: '1.0', - transaction: 'Test Transaction', + 'c:root-counter@none': [ + { + min: 1, + max: 1, + count: 2, + sum: 2, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, }, - }, + ], }, spans: expect.arrayContaining([ expect.objectContaining({ description: 'Some other span', op: 'transaction', _metrics_summary: { - 'c:root-counter@none': { - min: 1, - max: 2, - count: 3, - sum: 4, - tags: { - release: '1.0', - transaction: 'Test Transaction', - }, - }, - 's:root-set@none': { - min: 0, - max: 1, - count: 3, - sum: 2, - tags: { - release: '1.0', - transaction: 'Test Transaction', + 'c:root-counter@none': [ + { + min: 1, + max: 2, + count: 3, + sum: 4, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, }, - }, - 'g:root-gauge@none': { - min: 20, - max: 42, - count: 2, - sum: 62, - tags: { - release: '1.0', - transaction: 'Test Transaction', + ], + 's:root-set@none': [ + { + min: 0, + max: 1, + count: 3, + sum: 2, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, + } + ], + 'g:root-gauge@none': [ + { + min: 20, + max: 42, + count: 2, + sum: 62, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, }, - }, - 'd:root-distribution@none': { - min: 20, - max: 42, - count: 2, - sum: 62, - tags: { - release: '1.0', - transaction: 'Test Transaction', + ] + 'd:root-distribution@none': [ + { + min: 20, + max: 42, + count: 2, + sum: 62, + tags: { + release: '1.0', + transaction: 'Test Transaction', + }, }, - }, + ], }, }), ]), diff --git a/packages/types/src/event.ts b/packages/types/src/event.ts index 40ca2e88be65..b64a9e4b1ef4 100644 --- a/packages/types/src/event.ts +++ b/packages/types/src/event.ts @@ -72,7 +72,7 @@ export interface ErrorEvent extends Event { } export interface TransactionEvent extends Event { type: 'transaction'; - _metrics_summary?: Record; + _metrics_summary?: Record>; } /** JSDoc */ diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 0743497f1411..c254bee08aad 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -55,7 +55,7 @@ export interface SpanJSON { timestamp?: number; trace_id: string; origin?: SpanOrigin; - _metrics_summary?: Record; + _metrics_summary?: Record>; } // These are aligned with OpenTelemetry trace flags From c88ba57da51f62abdf47e3f7de9af5f244c92f63 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 8 Feb 2024 13:14:11 +0100 Subject: [PATCH 03/10] Push new summaries instead of overwriting them --- packages/core/src/metrics/metric-summary.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/metrics/metric-summary.ts b/packages/core/src/metrics/metric-summary.ts index dff610574b2c..947152d4d5d4 100644 --- a/packages/core/src/metrics/metric-summary.ts +++ b/packages/core/src/metrics/metric-summary.ts @@ -20,16 +20,16 @@ function getMetricStorageForSpan(span: Span): MetricSummaryStorage | undefined { /** * Fetches the metric summary if it exists for the passed span */ -export function getMetricSummaryJsonForSpan(span: Span): Record | undefined { +export function getMetricSummaryJsonForSpan(span: Span): Record> | undefined { const storage = getMetricStorageForSpan(span); if (!storage) { return undefined; } - const output: Record = {}; + const output: Record> = {}; for (const [, [exportKey, summary]] of storage) { - output[exportKey] = dropUndefinedKeys(summary); + output[exportKey].push(dropUndefinedKeys(summary)); } return output; From 639406149aa443d4b6afa35229271a675c585cae Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 8 Feb 2024 13:36:48 +0100 Subject: [PATCH 04/10] Do as Lukas told you --- packages/core/src/metrics/metric-summary.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/metrics/metric-summary.ts b/packages/core/src/metrics/metric-summary.ts index 947152d4d5d4..1230b3f27217 100644 --- a/packages/core/src/metrics/metric-summary.ts +++ b/packages/core/src/metrics/metric-summary.ts @@ -29,7 +29,7 @@ export function getMetricSummaryJsonForSpan(span: Span): Record> = {}; for (const [, [exportKey, summary]] of storage) { - output[exportKey].push(dropUndefinedKeys(summary)); + output[exportKey] = [...(output[exportKey] || []), dropUndefinedKeys(summary)]; } return output; From f231c8a20c1a6493f48557965a19b884724eb3b2 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 8 Feb 2024 13:42:35 +0100 Subject: [PATCH 05/10] CS --- .../suites/tracing/metric-summaries/test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts index 99d9b762b537..9a5f9c2826f3 100644 --- a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts @@ -43,7 +43,7 @@ const EXPECTED_TRANSACTION = { release: '1.0', transaction: 'Test Transaction', }, - } + }, ], 'g:root-gauge@none': [ { @@ -56,7 +56,7 @@ const EXPECTED_TRANSACTION = { transaction: 'Test Transaction', }, }, - ] + ], 'd:root-distribution@none': [ { min: 20, From dff3a8fe8c450379fcadc25fb6bd7d0997ff14e4 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 8 Feb 2024 14:04:54 +0100 Subject: [PATCH 06/10] Allow whitespace in tag values and remove tag value replacement character --- packages/core/src/metrics/constants.ts | 2 +- packages/core/src/metrics/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/metrics/constants.ts b/packages/core/src/metrics/constants.ts index e89e0fd1562b..a5f3a87f57d5 100644 --- a/packages/core/src/metrics/constants.ts +++ b/packages/core/src/metrics/constants.ts @@ -21,7 +21,7 @@ export const NAME_AND_TAG_KEY_NORMALIZATION_REGEX = /[^a-zA-Z0-9_/.-]+/g; * * See: https://develop.sentry.dev/sdk/metrics/#normalization */ -export const TAG_VALUE_NORMALIZATION_REGEX = /[^\w\d_:/@.{}[\]$-]+/g; +export const TAG_VALUE_NORMALIZATION_REGEX = /[^\w\d\s_:/@.{}[\]$-]+/g; /** * This does not match spec in https://develop.sentry.dev/sdk/metrics diff --git a/packages/core/src/metrics/utils.ts b/packages/core/src/metrics/utils.ts index a6674bcf30e1..7b1cf96a8462 100644 --- a/packages/core/src/metrics/utils.ts +++ b/packages/core/src/metrics/utils.ts @@ -62,7 +62,7 @@ export function sanitizeTags(unsanitizedTags: Record): Record for (const key in unsanitizedTags) { if (Object.prototype.hasOwnProperty.call(unsanitizedTags, key)) { const sanitizedKey = key.replace(NAME_AND_TAG_KEY_NORMALIZATION_REGEX, '_'); - tags[sanitizedKey] = String(unsanitizedTags[key]).replace(TAG_VALUE_NORMALIZATION_REGEX, '_'); + tags[sanitizedKey] = String(unsanitizedTags[key]).replace(TAG_VALUE_NORMALIZATION_REGEX, ''); } } return tags; From 858b9bd0792c8d92951d33609a1a8df96f28aaa2 Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 8 Feb 2024 14:49:00 +0100 Subject: [PATCH 07/10] Change some test --- .../suites/tracing/metric-summaries/scenario.js | 12 ++++++++++-- .../suites/tracing/metric-summaries/test.ts | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js index 8a7dbabe0dec..2b4e26970c0f 100644 --- a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js @@ -20,8 +20,16 @@ Sentry.startSpan( op: 'transaction', }, () => { - Sentry.metrics.increment('root-counter'); - Sentry.metrics.increment('root-counter'); + Sentry.metrics.increment('root-counter', 1, { + tags: { + email: 'jon.doe@example.com', + } + }); + Sentry.metrics.increment('root-counter', 1, { + tags: { + email: 'jane.doe@example.com', + } + }); Sentry.startSpan( { diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts index 9a5f9c2826f3..94f5fdc30c70 100644 --- a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/test.ts @@ -7,11 +7,23 @@ const EXPECTED_TRANSACTION = { { min: 1, max: 1, - count: 2, - sum: 2, + count: 1, + sum: 1, tags: { release: '1.0', transaction: 'Test Transaction', + email: 'jon.doe@example.com', + }, + }, + { + min: 1, + max: 1, + count: 1, + sum: 1, + tags: { + release: '1.0', + transaction: 'Test Transaction', + email: 'jane.doe@example.com', }, }, ], From 9fe0359c5394b02c9b8ddd3c2a093e3cf570348e Mon Sep 17 00:00:00 2001 From: Michael Hoffmann Date: Thu, 8 Feb 2024 15:15:10 +0100 Subject: [PATCH 08/10] CS --- .../suites/tracing/metric-summaries/scenario.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js index 2b4e26970c0f..ef68afb06576 100644 --- a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js @@ -23,12 +23,12 @@ Sentry.startSpan( Sentry.metrics.increment('root-counter', 1, { tags: { email: 'jon.doe@example.com', - } + }, }); Sentry.metrics.increment('root-counter', 1, { tags: { email: 'jane.doe@example.com', - } + }, }); Sentry.startSpan( From 34c62198b73da08cf92513daab53b528889256be Mon Sep 17 00:00:00 2001 From: Michi Hoffmann Date: Thu, 8 Feb 2024 16:10:31 +0100 Subject: [PATCH 09/10] Update packages/core/src/metrics/metric-summary.ts Co-authored-by: Abhijeet Prasad --- packages/core/src/metrics/metric-summary.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/metrics/metric-summary.ts b/packages/core/src/metrics/metric-summary.ts index 1230b3f27217..ede2330bffcf 100644 --- a/packages/core/src/metrics/metric-summary.ts +++ b/packages/core/src/metrics/metric-summary.ts @@ -29,7 +29,11 @@ export function getMetricSummaryJsonForSpan(span: Span): Record> = {}; for (const [, [exportKey, summary]] of storage) { - output[exportKey] = [...(output[exportKey] || []), dropUndefinedKeys(summary)]; + if (!output[exportKey]) { + output[exportKey] = []; + } + + output[exportKey].push(dropUndefinedKeys(summary)); } return output; From 102a0a837147178f9e09a9f2a47db7b9187cbc75 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Sat, 10 Feb 2024 12:41:55 -0400 Subject: [PATCH 10/10] feat(core): Decouple metrics aggregation from client --- .../tracing/metric-summaries/scenario.js | 3 - packages/astro/src/index.types.ts | 2 + packages/browser/src/exports.ts | 2 +- packages/browser/src/metrics.ts | 51 +++++++++++++++++ packages/bun/src/index.ts | 2 +- packages/core/src/baseclient.ts | 22 ++++--- packages/core/src/index.ts | 3 + packages/core/src/metrics/exports-node.ts | 52 +++++++++++++++++ packages/core/src/metrics/exports.ts | 57 +++++++++++++------ packages/core/src/metrics/integration.ts | 13 +++-- packages/core/src/server-runtime-client.ts | 5 -- packages/deno/src/index.ts | 2 +- packages/nextjs/src/index.types.ts | 2 + packages/node/src/index.ts | 2 +- packages/remix/src/index.types.ts | 2 + packages/sveltekit/src/index.types.ts | 2 + packages/types/src/client.ts | 20 +++++++ 17 files changed, 201 insertions(+), 41 deletions(-) create mode 100644 packages/browser/src/metrics.ts create mode 100644 packages/core/src/metrics/exports-node.ts diff --git a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js index ef68afb06576..e2921db180af 100644 --- a/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js +++ b/dev-packages/node-integration-tests/suites/tracing/metric-summaries/scenario.js @@ -6,9 +6,6 @@ Sentry.init({ release: '1.0', tracesSampleRate: 1.0, transport: loggingTransport, - _experiments: { - metricsAggregator: true, - }, }); // Stop the process from exiting before the transaction is sent diff --git a/packages/astro/src/index.types.ts b/packages/astro/src/index.types.ts index 026321e8ab3d..93ec819d4e8f 100644 --- a/packages/astro/src/index.types.ts +++ b/packages/astro/src/index.types.ts @@ -26,6 +26,8 @@ export declare const defaultStackParser: StackParser; export declare function close(timeout?: number | undefined): PromiseLike; export declare function flush(timeout?: number | undefined): PromiseLike; +export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; + /** * @deprecated This function will be removed in the next major version of the Sentry SDK. */ diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index f0f717f084cc..df0d9fc5d6c7 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -68,12 +68,12 @@ export { FunctionToString, // eslint-disable-next-line deprecation/deprecation InboundFilters, - metrics, functionToStringIntegration, inboundFiltersIntegration, parameterize, } from '@sentry/core'; +export * from './metrics'; export { WINDOW } from './helpers'; export { BrowserClient } from './client'; export { makeFetchTransport, makeXHRTransport } from './transports'; diff --git a/packages/browser/src/metrics.ts b/packages/browser/src/metrics.ts new file mode 100644 index 000000000000..a6c9e2f4e1cb --- /dev/null +++ b/packages/browser/src/metrics.ts @@ -0,0 +1,51 @@ +import type { MetricData } from '@sentry/core'; +import { BrowserMetricsAggregator, metrics as metricsCore } from '@sentry/core'; + +/** + * Adds a value to a counter metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function increment(name: string, value: number = 1, data?: MetricData): void { + metricsCore.increment(BrowserMetricsAggregator, name, value, data); +} + +/** + * Adds a value to a distribution metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function distribution(name: string, value: number, data?: MetricData): void { + metricsCore.distribution(BrowserMetricsAggregator, name, value, data); +} + +/** + * Adds a value to a set metric. Value must be a string or integer. + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function set(name: string, value: number | string, data?: MetricData): void { + metricsCore.set(BrowserMetricsAggregator, name, value, data); +} + +/** + * Adds a value to a gauge metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function gauge(name: string, value: number, data?: MetricData): void { + metricsCore.gauge(BrowserMetricsAggregator, name, value, data); +} + +export const metrics = { + increment, + distribution, + set, + gauge, + /** @deprecated An integration is no longer required to use the metrics feature */ + // eslint-disable-next-line deprecation/deprecation + MetricsAggregator: metricsCore.MetricsAggregator, + /** @deprecated An integration is no longer required to use the metrics feature */ + // eslint-disable-next-line deprecation/deprecation + metricsAggregatorIntegration: metricsCore.metricsAggregatorIntegration, +}; diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index e8a4738b9bba..8b08f5f8e86b 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -78,7 +78,7 @@ export { startInactiveSpan, startSpanManual, continueTrace, - metrics, + metricsNode as metrics, functionToStringIntegration, inboundFiltersIntegration, linkedErrorsIntegration, diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index b54e1887fd2b..ff56cf7ca9e6 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -95,9 +95,9 @@ const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been ca */ export abstract class BaseClient implements Client { /** - * A reference to a metrics aggregator + * TODO (v8): Remove * - * @experimental Note this is alpha API. It may experience breaking changes in the future. + * @deprecated The metricsAggregator is no longer referenced on the client. */ public metricsAggregator?: MetricsAggregator; @@ -281,9 +281,7 @@ export abstract class BaseClient implements Client { public flush(timeout?: number): PromiseLike { const transport = this._transport; if (transport) { - if (this.metricsAggregator) { - this.metricsAggregator.flush(); - } + this.emit('flush'); return this._isClientDoneProcessing(timeout).then(clientFinished => { return transport.flush(timeout).then(transportFlushed => clientFinished && transportFlushed); }); @@ -298,9 +296,7 @@ export abstract class BaseClient implements Client { public close(timeout?: number): PromiseLike { return this.flush(timeout).then(result => { this.getOptions().enabled = false; - if (this.metricsAggregator) { - this.metricsAggregator.close(); - } + this.emit('close'); return result; }); } @@ -496,6 +492,10 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public on(hook: 'startNavigationSpan', callback: (options: StartSpanOptions) => void): void; + public on(hook: 'flush', callback: () => void): void; + + public on(hook: 'close', callback: () => void): void; + /** @inheritdoc */ public on(hook: string, callback: unknown): void { if (!this._hooks[hook]) { @@ -542,6 +542,12 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public emit(hook: 'startNavigationSpan', options: StartSpanOptions): void; + /** @inheritdoc */ + public emit(hook: 'flush'): void; + + /** @inheritdoc */ + public emit(hook: 'close'): void; + /** @inheritdoc */ public emit(hook: string, ...rest: unknown[]): void { if (this._hooks[hook]) { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 849e34f6c92b..60aa5bab5124 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -106,6 +106,9 @@ export { linkedErrorsIntegration } from './integrations/linkederrors'; export { moduleMetadataIntegration } from './integrations/metadata'; export { requestDataIntegration } from './integrations/requestdata'; export { metrics } from './metrics/exports'; +export type { MetricData } from './metrics/exports'; +export { metricsNode } from './metrics/exports-node'; +export { BrowserMetricsAggregator } from './metrics/browser-aggregator'; /** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */ const Integrations = INTEGRATIONS; diff --git a/packages/core/src/metrics/exports-node.ts b/packages/core/src/metrics/exports-node.ts new file mode 100644 index 000000000000..53cc6ee6dccf --- /dev/null +++ b/packages/core/src/metrics/exports-node.ts @@ -0,0 +1,52 @@ +import { MetricsAggregator } from './aggregator'; +import type { MetricData } from './exports'; +import { metrics as metricsCore } from './exports'; + +/** + * Adds a value to a counter metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function increment(name: string, value: number = 1, data?: MetricData): void { + metricsCore.increment(MetricsAggregator, name, value, data); +} + +/** + * Adds a value to a distribution metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function distribution(name: string, value: number, data?: MetricData): void { + metricsCore.distribution(MetricsAggregator, name, value, data); +} + +/** + * Adds a value to a set metric. Value must be a string or integer. + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function set(name: string, value: number | string, data?: MetricData): void { + metricsCore.set(MetricsAggregator, name, value, data); +} + +/** + * Adds a value to a gauge metric + * + * @experimental This API is experimental and might have breaking changes in the future. + */ +function gauge(name: string, value: number, data?: MetricData): void { + metricsCore.gauge(MetricsAggregator, name, value, data); +} + +export const metricsNode = { + increment, + distribution, + set, + gauge, + /** @deprecated An integration is no longer required to use the metrics feature */ + // eslint-disable-next-line deprecation/deprecation + MetricsAggregator: metricsCore.MetricsAggregator, + /** @deprecated An integration is no longer required to use the metrics feature */ + // eslint-disable-next-line deprecation/deprecation + metricsAggregatorIntegration: metricsCore.metricsAggregatorIntegration, +}; diff --git a/packages/core/src/metrics/exports.ts b/packages/core/src/metrics/exports.ts index 20d63a2ca119..02af9dbf6d0d 100644 --- a/packages/core/src/metrics/exports.ts +++ b/packages/core/src/metrics/exports.ts @@ -1,4 +1,9 @@ -import type { ClientOptions, MeasurementUnit, Primitive } from '@sentry/types'; +import type { + ClientOptions, + MeasurementUnit, + MetricsAggregator as MetricsAggregatorInterface, + Primitive, +} from '@sentry/types'; import { logger } from '@sentry/utils'; import type { BaseClient } from '../baseclient'; import { DEBUG_BUILD } from '../debug-build'; @@ -8,26 +13,44 @@ import { COUNTER_METRIC_TYPE, DISTRIBUTION_METRIC_TYPE, GAUGE_METRIC_TYPE, SET_M import { MetricsAggregator, metricsAggregatorIntegration } from './integration'; import type { MetricType } from './types'; -interface MetricData { +export interface MetricData { unit?: MeasurementUnit; tags?: Record; timestamp?: number; } +type MetricsAggregatorConstructor = { + new (client: BaseClient): MetricsAggregatorInterface; +}; + +/** + * Global metrics aggregator instance. + * + * This is initialized on the first call to any `Sentry.metric.*` method. + */ +let globalMetricsAggregator: MetricsAggregatorInterface | undefined; + function addToMetricsAggregator( + Aggregator: MetricsAggregatorConstructor, metricType: MetricType, name: string, value: number | string, data: MetricData | undefined = {}, ): void { const client = getClient>(); - const scope = getCurrentScope(); + if (!client) { + return; + } + + if (!globalMetricsAggregator) { + const aggregator = (globalMetricsAggregator = new Aggregator(client)); + + client.on('flush', () => aggregator.flush()); + client.on('close', () => aggregator.close()); + } + if (client) { - if (!client.metricsAggregator) { - DEBUG_BUILD && - logger.warn('No metrics aggregator enabled. Please add the MetricsAggregator integration to use metrics APIs'); - return; - } + const scope = getCurrentScope(); const { unit, tags, timestamp } = data; const { release, environment } = client.getOptions(); // eslint-disable-next-line deprecation/deprecation @@ -44,7 +67,7 @@ function addToMetricsAggregator( } DEBUG_BUILD && logger.log(`Adding value of ${value} to ${metricType} metric ${name}`); - client.metricsAggregator.add(metricType, name, value, unit, { ...metricTags, ...tags }, timestamp); + globalMetricsAggregator.add(metricType, name, value, unit, { ...metricTags, ...tags }, timestamp); } } @@ -53,8 +76,8 @@ function addToMetricsAggregator( * * @experimental This API is experimental and might have breaking changes in the future. */ -export function increment(name: string, value: number = 1, data?: MetricData): void { - addToMetricsAggregator(COUNTER_METRIC_TYPE, name, value, data); +function increment(aggregator: MetricsAggregatorConstructor, name: string, value: number = 1, data?: MetricData): void { + addToMetricsAggregator(aggregator, COUNTER_METRIC_TYPE, name, value, data); } /** @@ -62,8 +85,8 @@ export function increment(name: string, value: number = 1, data?: MetricData): v * * @experimental This API is experimental and might have breaking changes in the future. */ -export function distribution(name: string, value: number, data?: MetricData): void { - addToMetricsAggregator(DISTRIBUTION_METRIC_TYPE, name, value, data); +function distribution(aggregator: MetricsAggregatorConstructor, name: string, value: number, data?: MetricData): void { + addToMetricsAggregator(aggregator, DISTRIBUTION_METRIC_TYPE, name, value, data); } /** @@ -71,8 +94,8 @@ export function distribution(name: string, value: number, data?: MetricData): vo * * @experimental This API is experimental and might have breaking changes in the future. */ -export function set(name: string, value: number | string, data?: MetricData): void { - addToMetricsAggregator(SET_METRIC_TYPE, name, value, data); +function set(aggregator: MetricsAggregatorConstructor, name: string, value: number | string, data?: MetricData): void { + addToMetricsAggregator(aggregator, SET_METRIC_TYPE, name, value, data); } /** @@ -80,8 +103,8 @@ export function set(name: string, value: number | string, data?: MetricData): vo * * @experimental This API is experimental and might have breaking changes in the future. */ -export function gauge(name: string, value: number, data?: MetricData): void { - addToMetricsAggregator(GAUGE_METRIC_TYPE, name, value, data); +function gauge(aggregator: MetricsAggregatorConstructor, name: string, value: number, data?: MetricData): void { + addToMetricsAggregator(aggregator, GAUGE_METRIC_TYPE, name, value, data); } export const metrics = { diff --git a/packages/core/src/metrics/integration.ts b/packages/core/src/metrics/integration.ts index af797bd8adf4..4a91c1fa2a58 100644 --- a/packages/core/src/metrics/integration.ts +++ b/packages/core/src/metrics/integration.ts @@ -1,7 +1,8 @@ import type { Client, ClientOptions, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; import type { BaseClient } from '../baseclient'; import { convertIntegrationFnToClass, defineIntegration } from '../integration'; -import { BrowserMetricsAggregator } from './browser-aggregator'; + +// TODO (v8): Remove this entire file const INTEGRATION_NAME = 'MetricsAggregator'; @@ -10,22 +11,26 @@ const _metricsAggregatorIntegration = (() => { name: INTEGRATION_NAME, // TODO v8: Remove this setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function - setup(client: BaseClient) { - client.metricsAggregator = new BrowserMetricsAggregator(client); + setup(_client: BaseClient) { + // }, }; }) satisfies IntegrationFn; +/** + * @deprecated An integration is no longer required to use the metrics feature + */ export const metricsAggregatorIntegration = defineIntegration(_metricsAggregatorIntegration); /** * Enables Sentry metrics monitoring. * * @experimental This API is experimental and might having breaking changes in the future. - * @deprecated Use `metricsAggegratorIntegration()` instead. + * @deprecated An integration is no longer required to use the metrics feature */ // eslint-disable-next-line deprecation/deprecation export const MetricsAggregator = convertIntegrationFnToClass( INTEGRATION_NAME, + // eslint-disable-next-line deprecation/deprecation metricsAggregatorIntegration, ) as IntegrationClass void }>; diff --git a/packages/core/src/server-runtime-client.ts b/packages/core/src/server-runtime-client.ts index e4dfb135abdf..93b0a7ea3bed 100644 --- a/packages/core/src/server-runtime-client.ts +++ b/packages/core/src/server-runtime-client.ts @@ -17,7 +17,6 @@ import { BaseClient } from './baseclient'; import { createCheckInEnvelope } from './checkin'; import { DEBUG_BUILD } from './debug-build'; import { getClient } from './exports'; -import { MetricsAggregator } from './metrics/aggregator'; import type { Scope } from './scope'; import { SessionFlusher } from './sessionflusher'; import { @@ -51,10 +50,6 @@ export class ServerRuntimeClient< addTracingExtensions(); super(options); - - if (options._experiments && options._experiments['metricsAggregator']) { - this.metricsAggregator = new MetricsAggregator(this); - } } /** diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 0d0e357737ac..44cd84ffc7d9 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -76,7 +76,7 @@ export { startSpan, startInactiveSpan, startSpanManual, - metrics, + metricsNode as metrics, inboundFiltersIntegration, linkedErrorsIntegration, functionToStringIntegration, diff --git a/packages/nextjs/src/index.types.ts b/packages/nextjs/src/index.types.ts index 2328208e28c5..0e9969e49027 100644 --- a/packages/nextjs/src/index.types.ts +++ b/packages/nextjs/src/index.types.ts @@ -44,6 +44,8 @@ export declare const withErrorBoundary: typeof clientSdk.withErrorBoundary; export declare const Span: typeof edgeSdk.Span; export declare const Transaction: typeof edgeSdk.Transaction; +export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; + export { withSentryConfig } from './config'; /** diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 3e91aae28d14..d4194dd0087f 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -80,11 +80,11 @@ export { startSpanManual, continueTrace, parameterize, - metrics, functionToStringIntegration, inboundFiltersIntegration, linkedErrorsIntegration, requestDataIntegration, + metricsNode as metrics, } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing'; diff --git a/packages/remix/src/index.types.ts b/packages/remix/src/index.types.ts index 0abe77c7a20d..3a8d523aa90e 100644 --- a/packages/remix/src/index.types.ts +++ b/packages/remix/src/index.types.ts @@ -30,6 +30,8 @@ declare const runtime: 'client' | 'server'; export const close = runtime === 'client' ? clientSdk.close : serverSdk.close; export const flush = runtime === 'client' ? clientSdk.flush : serverSdk.flush; +export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; + /** * @deprecated This function will be removed in the next major version of the Sentry SDK. */ diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts index 6a5d3e3883e9..bd50e9d8f81b 100644 --- a/packages/sveltekit/src/index.types.ts +++ b/packages/sveltekit/src/index.types.ts @@ -49,6 +49,8 @@ export declare const defaultStackParser: StackParser; export declare function close(timeout?: number | undefined): PromiseLike; export declare function flush(timeout?: number | undefined): PromiseLike; +export declare const metrics: typeof clientSdk.metrics & typeof serverSdk.metrics; + /** * @deprecated This function will be removed in the next major version of the Sentry SDK. */ diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 6c4409185e2d..3538fb7c41f3 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -272,6 +272,16 @@ export interface Client { */ on?(hook: 'startNavigationSpan', callback: (options: StartSpanOptions) => void): void; + /** + * A hook that is called when the client is flushing + */ + on?(hook: 'flush', callback: () => void): void; + + /** + * A hook that is called when the client is closing + */ + on?(hook: 'close', callback: () => void): void; + /** * Fire a hook event for transaction start. * Expects to be given a transaction as the second argument. @@ -343,5 +353,15 @@ export interface Client { */ emit?(hook: 'startNavigationSpan', options: StartSpanOptions): void; + /** + * Emit a hook event for client flush + */ + emit?(hook: 'flush'): void; + + /** + * Emit a hook event for client close + */ + emit?(hook: 'close'): void; + /* eslint-enable @typescript-eslint/unified-signatures */ }