-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(browser): Add browser metrics sdk (#9794)
This PR introduces functionality for a browser metrics SDK in an alpha state. Via the newly introduced APIs, you can now flush metrics directly to Sentry in the browser. To enable capturing metrics, you first need to add the `Metrics` integration. This is done for treeshaking purposes. ```js Sentry.init({ dsn: '__DSN__', integrations: [ new Sentry.metrics.MetricsAggregator(), ], }); ``` Then you'll be able to add `counters`, `sets`, `distributions`, and `gauges` under the `Sentry.metrics` namespace. ```js // Add 4 to a counter named `hits` Sentry.metrics.increment('hits', 4); // Add 2 to gauge named `parallel_requests`, tagged with `happy: "no"` Sentry.metrics.gauge('parallel_requests', 2, { tags: { happy: 'no' } }); // Add 4.6 to a distribution named `response_time` with unit seconds Sentry.metrics.distribution('response_time', 4.6, { unit: 'seconds' }); // Add 2 to a set named `valuable.ids` Sentry.metrics.set('valuable.ids', 2); ``` Under the hood, adding the `Metrics` integration adds a `SimpleMetricsAggregator`. This is a bundle-sized efficient metrics aggregator that aggregates metrics and flushes them out every 5 seconds. This does not use any weight based logic - this will be implemented in the `MetricsAggregator` that is used for server runtimes (node, deno, vercel-edge). To make metrics conditional, it is defined on the client as `public metricsAggregator: MetricsAggregator | undefined`. Next step is to add some integration tests for this functionality, and then add server-runtime logic for it. Many of the decisions taken for this aggregator + flushing implementation was to try to be as bundle size efficient as possible, happy to answer any specific questions.
- Loading branch information
1 parent
8800d5b
commit 6c000b6
Showing
18 changed files
with
636 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
export const COUNTER_METRIC_TYPE = 'c' as const; | ||
export const GAUGE_METRIC_TYPE = 'g' as const; | ||
export const SET_METRIC_TYPE = 's' as const; | ||
export const DISTRIBUTION_METRIC_TYPE = 'd' as const; | ||
|
||
/** | ||
* Normalization regex for metric names and metric tag names. | ||
* | ||
* This enforces that names and tag keys only contain alphanumeric characters, | ||
* underscores, forward slashes, periods, and dashes. | ||
* | ||
* See: https://develop.sentry.dev/sdk/metrics/#normalization | ||
*/ | ||
export const NAME_AND_TAG_KEY_NORMALIZATION_REGEX = /[^a-zA-Z0-9_/.-]+/g; | ||
|
||
/** | ||
* Normalization regex for metric tag values. | ||
* | ||
* This enforces that values only contain words, digits, or the following | ||
* special characters: _:/@.{}[\]$- | ||
* | ||
* See: https://develop.sentry.dev/sdk/metrics/#normalization | ||
*/ | ||
export const TAG_VALUE_NORMALIZATION_REGEX = /[^\w\d_:/@.{}[\]$-]+/g; | ||
|
||
/** | ||
* This does not match spec in https://develop.sentry.dev/sdk/metrics | ||
* but was chosen to optimize for the most common case in browser environments. | ||
*/ | ||
export const DEFAULT_FLUSH_INTERVAL = 5000; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import type { DsnComponents, MetricBucketItem, SdkMetadata, StatsdEnvelope, StatsdItem } from '@sentry/types'; | ||
import { createEnvelope, dsnToString } from '@sentry/utils'; | ||
import { serializeMetricBuckets } from './utils'; | ||
|
||
/** | ||
* Create envelope from a metric aggregate. | ||
*/ | ||
export function createMetricEnvelope( | ||
metricBucketItems: Array<MetricBucketItem>, | ||
dsn?: DsnComponents, | ||
metadata?: SdkMetadata, | ||
tunnel?: string, | ||
): StatsdEnvelope { | ||
const headers: StatsdEnvelope[0] = { | ||
sent_at: new Date().toISOString(), | ||
}; | ||
|
||
if (metadata && metadata.sdk) { | ||
headers.sdk = { | ||
name: metadata.sdk.name, | ||
version: metadata.sdk.version, | ||
}; | ||
} | ||
|
||
if (!!tunnel && dsn) { | ||
headers.dsn = dsnToString(dsn); | ||
} | ||
|
||
const item = createMetricEnvelopeItem(metricBucketItems); | ||
return createEnvelope<StatsdEnvelope>(headers, [item]); | ||
} | ||
|
||
function createMetricEnvelopeItem(metricBucketItems: Array<MetricBucketItem>): StatsdItem { | ||
const payload = serializeMetricBuckets(metricBucketItems); | ||
const metricHeaders: StatsdItem[0] = { | ||
type: 'statsd', | ||
length: payload.length, | ||
}; | ||
return [metricHeaders, payload]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import type { ClientOptions, MeasurementUnit, Primitive } from '@sentry/types'; | ||
import { logger } from '@sentry/utils'; | ||
import type { BaseClient } from '../baseclient'; | ||
import { DEBUG_BUILD } from '../debug-build'; | ||
import { getCurrentHub } from '../hub'; | ||
import { COUNTER_METRIC_TYPE, DISTRIBUTION_METRIC_TYPE, GAUGE_METRIC_TYPE, SET_METRIC_TYPE } from './constants'; | ||
import { MetricsAggregator } from './integration'; | ||
import type { MetricType } from './types'; | ||
|
||
interface MetricData { | ||
unit?: MeasurementUnit; | ||
tags?: Record<string, Primitive>; | ||
timestamp?: number; | ||
} | ||
|
||
function addToMetricsAggregator( | ||
metricType: MetricType, | ||
name: string, | ||
value: number | string, | ||
data: MetricData = {}, | ||
): void { | ||
const hub = getCurrentHub(); | ||
const client = hub.getClient() as BaseClient<ClientOptions>; | ||
const scope = hub.getScope(); | ||
if (client) { | ||
if (!client.metricsAggregator) { | ||
DEBUG_BUILD && | ||
logger.warn('No metrics aggregator enabled. Please add the MetricsAggregator integration to use metrics APIs'); | ||
return; | ||
} | ||
const { unit, tags, timestamp } = data; | ||
const { release, environment } = client.getOptions(); | ||
const transaction = scope.getTransaction(); | ||
const metricTags: Record<string, string> = {}; | ||
if (release) { | ||
metricTags.release = release; | ||
} | ||
if (environment) { | ||
metricTags.environment = environment; | ||
} | ||
if (transaction) { | ||
metricTags.transaction = transaction.name; | ||
} | ||
|
||
DEBUG_BUILD && logger.log(`Adding value of ${value} to ${metricType} metric ${name}`); | ||
client.metricsAggregator.add(metricType, name, value, unit, { ...metricTags, ...tags }, timestamp); | ||
} | ||
} | ||
|
||
/** | ||
* Adds a value to a counter metric | ||
* | ||
* @experimental This API is experimental and might having breaking changes in the future. | ||
*/ | ||
export function increment(name: string, value: number = 1, data?: MetricData): void { | ||
addToMetricsAggregator(COUNTER_METRIC_TYPE, name, value, data); | ||
} | ||
|
||
/** | ||
* Adds a value to a distribution metric | ||
* | ||
* @experimental This API is experimental and might having breaking changes in the future. | ||
*/ | ||
export function distribution(name: string, value: number, data?: MetricData): void { | ||
addToMetricsAggregator(DISTRIBUTION_METRIC_TYPE, name, value, data); | ||
} | ||
|
||
/** | ||
* Adds a value to a set metric. Value must be a string or integer. | ||
* | ||
* @experimental This API is experimental and might having breaking changes in the future. | ||
*/ | ||
export function set(name: string, value: number | string, data?: MetricData): void { | ||
addToMetricsAggregator(SET_METRIC_TYPE, name, value, data); | ||
} | ||
|
||
/** | ||
* Adds a value to a gauge metric | ||
* | ||
* @experimental This API is experimental and might having breaking changes in the future. | ||
*/ | ||
export function gauge(name: string, value: number, data?: MetricData): void { | ||
addToMetricsAggregator(GAUGE_METRIC_TYPE, name, value, data); | ||
} | ||
|
||
export const metrics = { | ||
increment, | ||
distribution, | ||
set, | ||
gauge, | ||
MetricsAggregator, | ||
}; |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.