-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(browser): Add browser metrics sdk #9794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
5d95eca
f1cd311
33395d6
b48096f
e698dfa
f5d6333
43ca415
9f19dec
979f51a
b05729e
4e4cb42
2c2b715
5ead644
261cd27
682fec3
60e8989
365cd32
ecd54ec
69be4b1
e693382
f6c8bbf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,30 @@ | ||||||
export const COUNTER_METRIC_TYPE = 'c'; | ||||||
export const GAUGE_METRIC_TYPE = 'g'; | ||||||
export const SET_METRIC_TYPE = 's'; | ||||||
export const DISTRIBUTION_METRIC_TYPE = 'd'; | ||||||
|
||||||
|
||||||
/** | ||||||
* 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 balues. | ||||||
|
* Normalization regex for metric tag balues. | |
* Normalization regex for metric tag values. |
AbhiPrasad marked this conversation as resolved.
Show resolved
Hide resolved
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
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 type { MetricType } from './types'; | ||
|
||
interface MetricData { | ||
unit?: MeasurementUnit; | ||
tags?: Record<string, Primitive>; | ||
timestamp?: number; | ||
} | ||
|
||
function addToMetricsAggregator(metricType: MetricType, name: string, value: number, 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 Metrics integration to use metrics APIs'); | ||
return; | ||
} | ||
const { unit, tags, timestamp } = data; | ||
const { release, environment } = client.getOptions(); | ||
const transaction = scope.getTransaction(); | ||
AbhiPrasad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const metricTags = { | ||
...tags, | ||
}; | ||
if (release) { | ||
metricTags.release = release; | ||
} | ||
if (environment) { | ||
metricTags.environment = environment; | ||
} | ||
if (transaction) { | ||
metricTags.transaction = transaction.name; | ||
} | ||
Comment on lines
+35
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. l/m: Should we think about applying these before we spread the tags so that people can override them? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know if we want users to override them. Let me start a slack thread. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note that this is also somewhat like this for general events 🤔 e.g. // in scope applyToEvent
if (this._level) {
event.level = this._level;
}
if (this._transactionName) {
event.transaction = this._transactionName;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... but only for some things, for others (e.g. tags etc.) event data takes precedence. |
||
|
||
DEBUG_BUILD && logger.log(`Adding value of ${value} to ${metricType} metric ${name}`); | ||
client.metricsAggregator.add(metricType, name, value, unit, metricTags, timestamp); | ||
} | ||
} | ||
|
||
/** | ||
* Adds a value to a counter metric | ||
* | ||
* @experimental This API is experimental and might having breaking changes in the future. | ||
*/ | ||
export function incr(name: string, value: number = 1, data?: MetricData): void { | ||
AbhiPrasad marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
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, incomingValue: number | string, data?: MetricData): void { | ||
AbhiPrasad marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
const value = typeof incomingValue === 'string' ? parseInt(incomingValue) : Math.floor(incomingValue); | ||
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); | ||
} |
AbhiPrasad marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { COUNTER_METRIC_TYPE, DISTRIBUTION_METRIC_TYPE, GAUGE_METRIC_TYPE, SET_METRIC_TYPE } from './constants'; | ||
|
||
interface MetricInstance { | ||
/** | ||
* Adds a value to a metric. | ||
*/ | ||
add(value: number): void; | ||
/** | ||
* Serializes the metric into a statsd format string. | ||
*/ | ||
toString(): string; | ||
} | ||
|
||
/** | ||
* A metric instance representing a counter. | ||
*/ | ||
export class CounterMetric implements MetricInstance { | ||
public constructor(private _value: number) {} | ||
|
||
/** @inheritdoc */ | ||
public add(value: number): void { | ||
this._value += value; | ||
} | ||
|
||
/** @inheritdoc */ | ||
public toString(): string { | ||
return `${this._value}`; | ||
} | ||
} | ||
|
||
/** | ||
* A metric instance representing a gauge. | ||
*/ | ||
export class GaugeMetric implements MetricInstance { | ||
private _last: number; | ||
private _min: number; | ||
private _max: number; | ||
private _sum: number; | ||
private _count: number; | ||
|
||
public constructor(private _value: number) { | ||
this._last = _value; | ||
this._min = _value; | ||
this._max = _value; | ||
this._sum = _value; | ||
this._count = 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be moved to line 30 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now it can't see #8700 |
||
} | ||
|
||
/** @inheritdoc */ | ||
public add(value: number): void { | ||
this._value = value; | ||
this._value = value; | ||
this._min = Math.min(this._min, value); | ||
this._max = Math.max(this._max, value); | ||
AbhiPrasad marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
this._sum += value; | ||
this._count += 1; | ||
AbhiPrasad marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} | ||
|
||
/** @inheritdoc */ | ||
public toString(): string { | ||
return `${this._last}:${this._min}:${this._max}:${this._sum}:${this._count}`; | ||
} | ||
} | ||
|
||
/** | ||
* A metric instance representing a distribution. | ||
*/ | ||
export class DistributionMetric implements MetricInstance { | ||
private _value: number[]; | ||
|
||
public constructor(first: number) { | ||
this._value = [first]; | ||
} | ||
|
||
/** @inheritdoc */ | ||
public add(value: number): void { | ||
this._value.push(value); | ||
} | ||
|
||
/** @inheritdoc */ | ||
public toString(): string { | ||
return this._value.join(':'); | ||
} | ||
} | ||
|
||
/** | ||
* A metric instance representing a set. | ||
*/ | ||
export class SetMetric implements MetricInstance { | ||
private _value: Set<number>; | ||
|
||
public constructor(public first: number) { | ||
this._value = new Set([first]); | ||
} | ||
|
||
/** @inheritdoc */ | ||
public add(value: number): void { | ||
|
||
this._value.add(value); | ||
} | ||
|
||
/** @inheritdoc */ | ||
public toString(): string { | ||
return `${Array.from(this._value).join(':')}`; | ||
} | ||
} | ||
|
||
export type Metric = CounterMetric | GaugeMetric | DistributionMetric | SetMetric; | ||
|
||
export const METRIC_MAP = { | ||
[COUNTER_METRIC_TYPE]: CounterMetric, | ||
[GAUGE_METRIC_TYPE]: GaugeMetric, | ||
[DISTRIBUTION_METRIC_TYPE]: DistributionMetric, | ||
[SET_METRIC_TYPE]: SetMetric, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { ClientOptions, Integration } from '@sentry/types'; | ||
import type { BaseClient } from '../baseclient'; | ||
import { SimpleMetricsAggregator } from './simpleaggregator'; | ||
|
||
/** | ||
* Enables Sentry metrics monitoring. | ||
* | ||
* @experimental This API is experimental and might having breaking changes in the future. | ||
*/ | ||
export class Metrics implements Integration { | ||
/** | ||
* @inheritDoc | ||
*/ | ||
public static id: string = 'Metrics'; | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public name: string; | ||
|
||
public constructor() { | ||
this.name = Metrics.id; | ||
AbhiPrasad marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public setupOnce(): void { | ||
// Do nothing | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public setup(client: BaseClient<ClientOptions>): void { | ||
client.metricsAggregator = new SimpleMetricsAggregator(client); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.