Skip to content

feat(core): Decouple metrics aggregation from client #10596

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export declare const defaultStackParser: StackParser;
export declare function close(timeout?: number | undefined): PromiseLike<boolean>;
export declare function flush(timeout?: number | undefined): PromiseLike<boolean>;

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.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/browser/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
51 changes: 51 additions & 0 deletions packages/browser/src/metrics.ts
Original file line number Diff line number Diff line change
@@ -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,
};
2 changes: 1 addition & 1 deletion packages/bun/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export {
startInactiveSpan,
startSpanManual,
continueTrace,
metrics,
metricsNode as metrics,
functionToStringIntegration,
inboundFiltersIntegration,
linkedErrorsIntegration,
Expand Down
22 changes: 14 additions & 8 deletions packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been ca
*/
export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
/**
* 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;

Expand Down Expand Up @@ -281,9 +281,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
public flush(timeout?: number): PromiseLike<boolean> {
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);
});
Expand All @@ -298,9 +296,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
public close(timeout?: number): PromiseLike<boolean> {
return this.flush(timeout).then(result => {
this.getOptions().enabled = false;
if (this.metricsAggregator) {
this.metricsAggregator.close();
}
this.emit('close');
return result;
});
}
Expand Down Expand Up @@ -496,6 +492,10 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
/** @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]) {
Expand Down Expand Up @@ -542,6 +542,12 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
/** @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]) {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
52 changes: 52 additions & 0 deletions packages/core/src/metrics/exports-node.ts
Original file line number Diff line number Diff line change
@@ -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,
};
57 changes: 40 additions & 17 deletions packages/core/src/metrics/exports.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<string, Primitive>;
timestamp?: number;
}

type MetricsAggregatorConstructor = {
new (client: BaseClient<ClientOptions>): 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<BaseClient<ClientOptions>>();
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
Expand All @@ -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);
}
}

Expand All @@ -53,35 +76,35 @@ 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);
}

/**
* Adds a value to a distribution metric
*
* @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);
}

/**
* 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.
*/
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);
}

/**
* Adds a value to a gauge metric
*
* @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 = {
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/metrics/integration.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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<ClientOptions>) {
client.metricsAggregator = new BrowserMetricsAggregator(client);
setup(_client: BaseClient<ClientOptions>) {
//
},
};
}) 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<Integration & { setup: (client: Client) => void }>;
2 changes: 1 addition & 1 deletion packages/core/src/metrics/metric-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function getMetricSummaryJsonForSpan(span: Span): Record<string, Array<Me
if (!output[exportKey]) {
output[exportKey] = [];
}

output[exportKey].push(dropUndefinedKeys(summary));
}

Expand Down
5 changes: 0 additions & 5 deletions packages/core/src/server-runtime-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -51,10 +50,6 @@ export class ServerRuntimeClient<
addTracingExtensions();

super(options);

if (options._experiments && options._experiments['metricsAggregator']) {
this.metricsAggregator = new MetricsAggregator(this);
}
}

/**
Expand Down
Loading