Skip to content

Commit 02b0dad

Browse files
authored
feat(core): Allow metrics aggregator per client (#10949)
This PR adds the ability to override the client used for metrics rather than being tied to using the single client `getClient()` returns. ```ts Sentry.metrics.increment('counter', 1, { client }); ``` It also adds a `getMetricsAggregatorForClient` exported function which is required so the Electron SDK can access the current aggregator. `sendEnvelope` was added to the `Client` type so that all usages of `BaseClient<ClientOptions>` could be replaced with `Client`.
1 parent aa61cd8 commit 02b0dad

File tree

7 files changed

+79
-58
lines changed

7 files changed

+79
-58
lines changed

packages/core/src/baseclient.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
534534
/**
535535
* @inheritdoc
536536
*/
537-
public sendEnvelope(envelope: Envelope): PromiseLike<TransportMakeRequestResponse> | void {
537+
public sendEnvelope(envelope: Envelope): PromiseLike<TransportMakeRequestResponse> {
538538
this.emit('beforeEnvelope', envelope);
539539

540540
if (this._isEnabled() && this._transport) {
@@ -545,6 +545,8 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
545545
}
546546

547547
DEBUG_BUILD && logger.error('Transport disabled');
548+
549+
return resolvedSyncPromise({});
548550
}
549551

550552
/* eslint-enable @typescript-eslint/unified-signatures */

packages/core/src/metrics/aggregator.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
import type {
2-
ClientOptions,
3-
MeasurementUnit,
4-
MetricsAggregator as MetricsAggregatorBase,
5-
Primitive,
6-
} from '@sentry/types';
1+
import type { Client, MeasurementUnit, MetricsAggregator as MetricsAggregatorBase, Primitive } from '@sentry/types';
72
import { timestampInSeconds } from '@sentry/utils';
8-
import type { BaseClient } from '../baseclient';
93
import { DEFAULT_FLUSH_INTERVAL, MAX_WEIGHT, NAME_AND_TAG_KEY_NORMALIZATION_REGEX, SET_METRIC_TYPE } from './constants';
104
import { captureAggregateMetrics } from './envelope';
115
import { METRIC_MAP } from './instance';
@@ -40,7 +34,7 @@ export class MetricsAggregator implements MetricsAggregatorBase {
4034
// Force flush is used on either shutdown, flush() or when we exceed the max weight.
4135
private _forceFlush: boolean;
4236

43-
public constructor(private readonly _client: BaseClient<ClientOptions>) {
37+
public constructor(private readonly _client: Client) {
4438
this._buckets = new Map();
4539
this._bucketsTotalWeight = 0;
4640
this._interval = setInterval(() => this._flush(), DEFAULT_FLUSH_INTERVAL);

packages/core/src/metrics/browser-aggregator.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import type { ClientOptions, MeasurementUnit, MetricsAggregator, Primitive } from '@sentry/types';
1+
import type { Client, MeasurementUnit, MetricsAggregator, Primitive } from '@sentry/types';
22
import { timestampInSeconds } from '@sentry/utils';
3-
import type { BaseClient } from '../baseclient';
43
import { DEFAULT_BROWSER_FLUSH_INTERVAL, NAME_AND_TAG_KEY_NORMALIZATION_REGEX, SET_METRIC_TYPE } from './constants';
54
import { captureAggregateMetrics } from './envelope';
65
import { METRIC_MAP } from './instance';
@@ -21,7 +20,7 @@ export class BrowserMetricsAggregator implements MetricsAggregator {
2120
private _buckets: MetricBucket;
2221
private readonly _interval: ReturnType<typeof setInterval>;
2322

24-
public constructor(private readonly _client: BaseClient<ClientOptions>) {
23+
public constructor(private readonly _client: Client) {
2524
this._buckets = new Map();
2625
this._interval = setInterval(() => this.flush(), DEFAULT_BROWSER_FLUSH_INTERVAL);
2726
}

packages/core/src/metrics/envelope.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
1-
import type {
2-
ClientOptions,
3-
DsnComponents,
4-
MetricBucketItem,
5-
SdkMetadata,
6-
StatsdEnvelope,
7-
StatsdItem,
8-
} from '@sentry/types';
1+
import type { Client, DsnComponents, MetricBucketItem, SdkMetadata, StatsdEnvelope, StatsdItem } from '@sentry/types';
92
import { createEnvelope, dsnToString, logger } from '@sentry/utils';
10-
import type { BaseClient } from '../baseclient';
113
import { serializeMetricBuckets } from './utils';
124

135
/**
146
* Captures aggregated metrics to the supplied client.
157
*/
16-
export function captureAggregateMetrics(
17-
client: BaseClient<ClientOptions>,
18-
metricBucketItems: Array<MetricBucketItem>,
19-
): void {
8+
export function captureAggregateMetrics(client: Client, metricBucketItems: Array<MetricBucketItem>): void {
209
logger.log(`Flushing aggregated metrics, number of metrics: ${metricBucketItems.length}`);
2110
const dsn = client.getDsn();
2211
const metadata = client.getSdkMetadata();

packages/core/src/metrics/exports-default.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Client, MetricsAggregator as MetricsAggregatorInterface } from '@sentry/types';
12
import { MetricsAggregator } from './aggregator';
23
import type { MetricData } from './exports';
34
import { metrics as metricsCore } from './exports';
@@ -38,9 +39,20 @@ function gauge(name: string, value: number, data?: MetricData): void {
3839
metricsCore.gauge(MetricsAggregator, name, value, data);
3940
}
4041

42+
/**
43+
* Returns the metrics aggregator for a given client.
44+
*/
45+
function getMetricsAggregatorForClient(client: Client): MetricsAggregatorInterface {
46+
return metricsCore.getMetricsAggregatorForClient(client, MetricsAggregator);
47+
}
48+
4149
export const metricsDefault = {
4250
increment,
4351
distribution,
4452
set,
4553
gauge,
54+
/**
55+
* @ignore This is for internal use only.
56+
*/
57+
getMetricsAggregatorForClient,
4658
};

packages/core/src/metrics/exports.ts

Lines changed: 55 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import type {
2-
ClientOptions,
2+
Client,
33
MeasurementUnit,
44
MetricsAggregator as MetricsAggregatorInterface,
55
Primitive,
66
} from '@sentry/types';
77
import { logger } from '@sentry/utils';
8-
import type { BaseClient } from '../baseclient';
98
import { getCurrentScope } from '../currentScopes';
109
import { getClient } from '../currentScopes';
1110
import { DEBUG_BUILD } from '../debug-build';
@@ -17,18 +16,43 @@ export interface MetricData {
1716
unit?: MeasurementUnit;
1817
tags?: Record<string, Primitive>;
1918
timestamp?: number;
19+
client?: Client;
2020
}
2121

2222
type MetricsAggregatorConstructor = {
23-
new (client: BaseClient<ClientOptions>): MetricsAggregatorInterface;
23+
new (client: Client): MetricsAggregatorInterface;
2424
};
2525

2626
/**
27-
* Global metrics aggregator instance.
28-
*
29-
* This is initialized on the first call to any `Sentry.metric.*` method.
27+
* A metrics aggregator instance per Client.
3028
*/
31-
let globalMetricsAggregator: MetricsAggregatorInterface | undefined;
29+
let globalMetricsAggregators: WeakMap<Client, MetricsAggregatorInterface> | undefined;
30+
31+
/**
32+
* Gets the metrics aggregator for a given client.
33+
* @param client The client for which to get the metrics aggregator.
34+
* @param Aggregator Optional metrics aggregator class to use to create an aggregator if one does not exist.
35+
*/
36+
function getMetricsAggregatorForClient(
37+
client: Client,
38+
Aggregator: MetricsAggregatorConstructor,
39+
): MetricsAggregatorInterface {
40+
if (!globalMetricsAggregators) {
41+
globalMetricsAggregators = new WeakMap();
42+
}
43+
44+
const aggregator = globalMetricsAggregators.get(client);
45+
if (aggregator) {
46+
return aggregator;
47+
}
48+
49+
const newAggregator = new Aggregator(client);
50+
client.on('flush', () => newAggregator.flush());
51+
client.on('close', () => newAggregator.close());
52+
globalMetricsAggregators.set(client, newAggregator);
53+
54+
return newAggregator;
55+
}
3256

3357
function addToMetricsAggregator(
3458
Aggregator: MetricsAggregatorConstructor,
@@ -37,38 +61,32 @@ function addToMetricsAggregator(
3761
value: number | string,
3862
data: MetricData | undefined = {},
3963
): void {
40-
const client = getClient<BaseClient<ClientOptions>>();
64+
const client = data.client || getClient<Client>();
65+
4166
if (!client) {
4267
return;
4368
}
4469

45-
if (!globalMetricsAggregator) {
46-
const aggregator = (globalMetricsAggregator = new Aggregator(client));
47-
48-
client.on('flush', () => aggregator.flush());
49-
client.on('close', () => aggregator.close());
70+
const scope = getCurrentScope();
71+
const { unit, tags, timestamp } = data;
72+
const { release, environment } = client.getOptions();
73+
// eslint-disable-next-line deprecation/deprecation
74+
const transaction = scope.getTransaction();
75+
const metricTags: Record<string, string> = {};
76+
if (release) {
77+
metricTags.release = release;
5078
}
51-
52-
if (client) {
53-
const scope = getCurrentScope();
54-
const { unit, tags, timestamp } = data;
55-
const { release, environment } = client.getOptions();
56-
// eslint-disable-next-line deprecation/deprecation
57-
const transaction = scope.getTransaction();
58-
const metricTags: Record<string, string> = {};
59-
if (release) {
60-
metricTags.release = release;
61-
}
62-
if (environment) {
63-
metricTags.environment = environment;
64-
}
65-
if (transaction) {
66-
metricTags.transaction = spanToJSON(transaction).description || '';
67-
}
68-
69-
DEBUG_BUILD && logger.log(`Adding value of ${value} to ${metricType} metric ${name}`);
70-
globalMetricsAggregator.add(metricType, name, value, unit, { ...metricTags, ...tags }, timestamp);
79+
if (environment) {
80+
metricTags.environment = environment;
81+
}
82+
if (transaction) {
83+
metricTags.transaction = spanToJSON(transaction).description || '';
7184
}
85+
86+
DEBUG_BUILD && logger.log(`Adding value of ${value} to ${metricType} metric ${name}`);
87+
88+
const aggregator = getMetricsAggregatorForClient(client, Aggregator);
89+
aggregator.add(metricType, name, value, unit, { ...metricTags, ...tags }, timestamp);
7290
}
7391

7492
/**
@@ -112,4 +130,8 @@ export const metrics = {
112130
distribution,
113131
set,
114132
gauge,
133+
/**
134+
* @ignore This is for internal use only.
135+
*/
136+
getMetricsAggregatorForClient,
115137
};

packages/types/src/client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ export interface Client<O extends ClientOptions = ClientOptions> {
170170
/** Submits the session to Sentry */
171171
sendSession(session: Session | SessionAggregates): void;
172172

173+
/** Sends an envelope to Sentry */
174+
sendEnvelope(envelope: Envelope): PromiseLike<TransportMakeRequestResponse>;
175+
173176
/**
174177
* Record on the client that an event got dropped (ie, an event that will not be sent to sentry).
175178
*

0 commit comments

Comments
 (0)