Skip to content

Commit a267b8f

Browse files
authored
Merge branch 'develop' into sig-metrics-rate-limit-v8
2 parents acaeffe + 9d0472b commit a267b8f

File tree

10 files changed

+157
-95
lines changed

10 files changed

+157
-95
lines changed

packages/opentelemetry/src/custom/getCurrentHub.ts renamed to packages/core/src/getCurrentHubShim.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
import type { Client, EventHint, Hub, Integration, IntegrationClass, SeverityLevel } from '@sentry/types';
2-
2+
import { addBreadcrumb } from './breadcrumbs';
3+
import { getClient, getCurrentScope, getIsolationScope, withScope } from './currentScopes';
34
import {
4-
addBreadcrumb,
55
captureEvent,
66
endSession,
7-
getClient,
8-
getCurrentScope,
9-
getIsolationScope,
107
setContext,
118
setExtra,
129
setExtras,
1310
setTag,
1411
setTags,
1512
setUser,
1613
startSession,
17-
withScope,
18-
} from '@sentry/core';
14+
} from './exports';
1915

2016
/**
2117
* This is for legacy reasons, and returns a proxy object instead of a hub to be used.
18+
*
2219
* @deprecated Use the methods directly.
2320
*/
24-
export function getCurrentHub(): Hub {
21+
export function getCurrentHubShim(): Hub {
2522
return {
2623
bindClient(client: Client): void {
2724
const scope = getCurrentScope();
@@ -48,7 +45,8 @@ export function getCurrentHub(): Hub {
4845
setContext,
4946

5047
getIntegration<T extends Integration>(integration: IntegrationClass<T>): T | null {
51-
return getClient()?.getIntegrationByName<T>(integration.id) || null;
48+
const client = getClient();
49+
return (client && client.getIntegrationByName<T>(integration.id)) || null;
5250
},
5351

5452
startSession,

packages/core/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,6 @@ export { BrowserMetricsAggregator } from './metrics/browser-aggregator';
106106
export { getMetricSummaryJsonForSpan } from './metrics/metric-summary';
107107
export { addTracingHeadersToFetchRequest, instrumentFetchRequest } from './fetch';
108108
export { trpcMiddleware } from './trpc';
109+
110+
// eslint-disable-next-line deprecation/deprecation
111+
export { getCurrentHubShim } from './getCurrentHubShim';

packages/core/src/metrics/aggregator.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { Client, MeasurementUnit, MetricsAggregator as MetricsAggregatorBase, Primitive } from '@sentry/types';
22
import { timestampInSeconds } from '@sentry/utils';
33
import { updateMetricSummaryOnActiveSpan } from '../utils/spanUtils';
4-
import { DEFAULT_FLUSH_INTERVAL, MAX_WEIGHT, NAME_AND_TAG_KEY_NORMALIZATION_REGEX, SET_METRIC_TYPE } from './constants';
4+
import { DEFAULT_FLUSH_INTERVAL, MAX_WEIGHT, SET_METRIC_TYPE } from './constants';
55
import { captureAggregateMetrics } from './envelope';
66
import { METRIC_MAP } from './instance';
77
import type { MetricBucket, MetricType } from './types';
8-
import { getBucketKey, sanitizeTags } from './utils';
8+
import { getBucketKey, sanitizeMetricKey, sanitizeTags, sanitizeUnit } from './utils';
99

1010
/**
1111
* A metrics aggregator that aggregates metrics in memory and flushes them periodically.
@@ -46,6 +46,7 @@ export class MetricsAggregator implements MetricsAggregatorBase {
4646
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
4747
this._interval.unref();
4848
}
49+
4950
this._flushShift = Math.floor((Math.random() * DEFAULT_FLUSH_INTERVAL) / 1000);
5051
this._forceFlush = false;
5152
}
@@ -57,13 +58,14 @@ export class MetricsAggregator implements MetricsAggregatorBase {
5758
metricType: MetricType,
5859
unsanitizedName: string,
5960
value: number | string,
60-
unit: MeasurementUnit = 'none',
61+
unsanitizedUnit: MeasurementUnit = 'none',
6162
unsanitizedTags: Record<string, Primitive> = {},
6263
maybeFloatTimestamp = timestampInSeconds(),
6364
): void {
6465
const timestamp = Math.floor(maybeFloatTimestamp);
65-
const name = unsanitizedName.replace(NAME_AND_TAG_KEY_NORMALIZATION_REGEX, '_');
66+
const name = sanitizeMetricKey(unsanitizedName);
6667
const tags = sanitizeTags(unsanitizedTags);
68+
const unit = sanitizeUnit(unsanitizedUnit as string);
6769

6870
const bucketKey = getBucketKey(metricType, name, unit, tags);
6971

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import type { Client, MeasurementUnit, MetricsAggregator, Primitive } from '@sentry/types';
22
import { timestampInSeconds } from '@sentry/utils';
33
import { updateMetricSummaryOnActiveSpan } from '../utils/spanUtils';
4-
import { DEFAULT_BROWSER_FLUSH_INTERVAL, NAME_AND_TAG_KEY_NORMALIZATION_REGEX, SET_METRIC_TYPE } from './constants';
4+
import { DEFAULT_BROWSER_FLUSH_INTERVAL, SET_METRIC_TYPE } from './constants';
55
import { captureAggregateMetrics } from './envelope';
66
import { METRIC_MAP } from './instance';
77
import type { MetricBucket, MetricType } from './types';
8-
import { getBucketKey, sanitizeTags } from './utils';
8+
import { getBucketKey, sanitizeMetricKey, sanitizeTags, sanitizeUnit } from './utils';
99

1010
/**
1111
* A simple metrics aggregator that aggregates metrics in memory and flushes them periodically.
@@ -32,13 +32,14 @@ export class BrowserMetricsAggregator implements MetricsAggregator {
3232
metricType: MetricType,
3333
unsanitizedName: string,
3434
value: number | string,
35-
unit: MeasurementUnit | undefined = 'none',
35+
unsanitizedUnit: MeasurementUnit | undefined = 'none',
3636
unsanitizedTags: Record<string, Primitive> | undefined = {},
3737
maybeFloatTimestamp: number | undefined = timestampInSeconds(),
3838
): void {
3939
const timestamp = Math.floor(maybeFloatTimestamp);
40-
const name = unsanitizedName.replace(NAME_AND_TAG_KEY_NORMALIZATION_REGEX, '_');
40+
const name = sanitizeMetricKey(unsanitizedName);
4141
const tags = sanitizeTags(unsanitizedTags);
42+
const unit = sanitizeUnit(unsanitizedUnit as string);
4243

4344
const bucketKey = getBucketKey(metricType, name, unit, tags);
4445

@@ -79,8 +80,7 @@ export class BrowserMetricsAggregator implements MetricsAggregator {
7980
return;
8081
}
8182

82-
// TODO(@anonrig): Use Object.values() when we support ES6+
83-
const metricBuckets = Array.from(this._buckets).map(([, bucketItem]) => bucketItem);
83+
const metricBuckets = Array.from(this._buckets.values());
8484
captureAggregateMetrics(this._client, metricBuckets);
8585

8686
this._buckets.clear();

packages/core/src/metrics/constants.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,6 @@ export const GAUGE_METRIC_TYPE = 'g' as const;
33
export const SET_METRIC_TYPE = 's' as const;
44
export const DISTRIBUTION_METRIC_TYPE = 'd' as const;
55

6-
/**
7-
* Normalization regex for metric names and metric tag names.
8-
*
9-
* This enforces that names and tag keys only contain alphanumeric characters,
10-
* underscores, forward slashes, periods, and dashes.
11-
*
12-
* See: https://develop.sentry.dev/sdk/metrics/#normalization
13-
*/
14-
export const NAME_AND_TAG_KEY_NORMALIZATION_REGEX = /[^a-zA-Z0-9_/.-]+/g;
15-
16-
/**
17-
* Normalization regex for metric tag values.
18-
*
19-
* This enforces that values only contain words, digits, or the following
20-
* special characters: _:/@.{}[\]$-
21-
*
22-
* See: https://develop.sentry.dev/sdk/metrics/#normalization
23-
*/
24-
export const TAG_VALUE_NORMALIZATION_REGEX = /[^\w\d\s_:/@.{}[\]$-]+/g;
25-
266
/**
277
* This does not match spec in https://develop.sentry.dev/sdk/metrics
288
* but was chosen to optimize for the most common case in browser environments.

packages/core/src/metrics/utils.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { MeasurementUnit, MetricBucketItem, Primitive } from '@sentry/types';
22
import { dropUndefinedKeys } from '@sentry/utils';
3-
import { NAME_AND_TAG_KEY_NORMALIZATION_REGEX, TAG_VALUE_NORMALIZATION_REGEX } from './constants';
43
import type { MetricType } from './types';
54

65
/**
@@ -54,15 +53,72 @@ export function serializeMetricBuckets(metricBucketItems: MetricBucketItem[]): s
5453
return out;
5554
}
5655

56+
/**
57+
* Sanitizes units
58+
*
59+
* These Regex's are straight from the normalisation docs:
60+
* https://develop.sentry.dev/sdk/metrics/#normalization
61+
*/
62+
export function sanitizeUnit(unit: string): string {
63+
return unit.replace(/[^\w]+/gi, '_');
64+
}
65+
66+
/**
67+
* Sanitizes metric keys
68+
*
69+
* These Regex's are straight from the normalisation docs:
70+
* https://develop.sentry.dev/sdk/metrics/#normalization
71+
*/
72+
export function sanitizeMetricKey(key: string): string {
73+
return key.replace(/[^\w\-.]+/gi, '_');
74+
}
75+
76+
/**
77+
* Sanitizes metric keys
78+
*
79+
* These Regex's are straight from the normalisation docs:
80+
* https://develop.sentry.dev/sdk/metrics/#normalization
81+
*/
82+
function sanitizeTagKey(key: string): string {
83+
return key.replace(/[^\w\-./]+/gi, '');
84+
}
85+
86+
/**
87+
* These Regex's are straight from the normalisation docs:
88+
* https://develop.sentry.dev/sdk/metrics/#normalization
89+
*/
90+
const tagValueReplacements: [string, string][] = [
91+
['\n', '\\n'],
92+
['\r', '\\r'],
93+
['\t', '\\t'],
94+
['\\', '\\\\'],
95+
['|', '\\u{7c}'],
96+
[',', '\\u{2c}'],
97+
];
98+
99+
function getCharOrReplacement(input: string): string {
100+
for (const [search, replacement] of tagValueReplacements) {
101+
if (input === search) {
102+
return replacement;
103+
}
104+
}
105+
106+
return input;
107+
}
108+
109+
function sanitizeTagValue(value: string): string {
110+
return [...value].reduce((acc, char) => acc + getCharOrReplacement(char), '');
111+
}
112+
57113
/**
58114
* Sanitizes tags.
59115
*/
60116
export function sanitizeTags(unsanitizedTags: Record<string, Primitive>): Record<string, string> {
61117
const tags: Record<string, string> = {};
62118
for (const key in unsanitizedTags) {
63119
if (Object.prototype.hasOwnProperty.call(unsanitizedTags, key)) {
64-
const sanitizedKey = key.replace(NAME_AND_TAG_KEY_NORMALIZATION_REGEX, '_');
65-
tags[sanitizedKey] = String(unsanitizedTags[key]).replace(TAG_VALUE_NORMALIZATION_REGEX, '');
120+
const sanitizedKey = sanitizeTagKey(key);
121+
tags[sanitizedKey] = sanitizeTagValue(String(unsanitizedTags[key]));
66122
}
67123
}
68124
return tags;

packages/core/test/lib/metrics/utils.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
GAUGE_METRIC_TYPE,
55
SET_METRIC_TYPE,
66
} from '../../../src/metrics/constants';
7-
import { getBucketKey } from '../../../src/metrics/utils';
7+
import { getBucketKey, sanitizeTags } from '../../../src/metrics/utils';
88

99
describe('getBucketKey', () => {
1010
it.each([
@@ -18,4 +18,26 @@ describe('getBucketKey', () => {
1818
])('should return', (metricType, name, unit, tags, expected) => {
1919
expect(getBucketKey(metricType, name, unit, tags)).toEqual(expected);
2020
});
21+
22+
it('should sanitize tags', () => {
23+
const inputTags = {
24+
'f-oo|bar': '%$foo/',
25+
'foo$.$.$bar': 'blah{}',
26+
'foö-bar': 'snöwmän',
27+
route: 'GET /foo',
28+
__bar__: 'this | or , that',
29+
'foo/': 'hello!\n\r\t\\',
30+
};
31+
32+
const outputTags = {
33+
'f-oobar': '%$foo/',
34+
'foo..bar': 'blah{}',
35+
'fo-bar': 'snöwmän',
36+
route: 'GET /foo',
37+
__bar__: 'this \\u{7c} or \\u{2c} that',
38+
'foo/': 'hello!\\n\\r\\t\\\\',
39+
};
40+
41+
expect(sanitizeTags(inputTags)).toEqual(outputTags);
42+
});
2143
});

packages/opentelemetry/src/asyncContextStrategy.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import * as api from '@opentelemetry/api';
2-
import { getDefaultCurrentScope, getDefaultIsolationScope, setAsyncContextStrategy } from '@sentry/core';
2+
import {
3+
getCurrentHubShim,
4+
getDefaultCurrentScope,
5+
getDefaultIsolationScope,
6+
setAsyncContextStrategy,
7+
} from '@sentry/core';
38
import type { withActiveSpan as defaultWithActiveSpan } from '@sentry/core';
49
import type { Hub, Scope } from '@sentry/types';
510

@@ -8,7 +13,6 @@ import {
813
SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY,
914
SENTRY_FORK_SET_SCOPE_CONTEXT_KEY,
1015
} from './constants';
11-
import { getCurrentHub as _getCurrentHub } from './custom/getCurrentHub';
1216
import { startInactiveSpan, startSpan, startSpanManual, withActiveSpan } from './trace';
1317
import type { CurrentScopes } from './types';
1418
import { getScopesFromContext } from './utils/contextData';
@@ -38,7 +42,7 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void {
3842

3943
function getCurrentHub(): Hub {
4044
// eslint-disable-next-line deprecation/deprecation
41-
const hub = _getCurrentHub();
45+
const hub = getCurrentHubShim();
4246
return {
4347
...hub,
4448
getScope: () => {

packages/opentelemetry/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export { suppressTracing } from './utils/suppressTracing';
2828
// eslint-disable-next-line deprecation/deprecation
2929
export { setupGlobalHub } from './custom/hub';
3030
// eslint-disable-next-line deprecation/deprecation
31-
export { getCurrentHub } from './custom/getCurrentHub';
31+
export { getCurrentHubShim } from '@sentry/core';
3232
export { setupEventContextTrace } from './setupEventContextTrace';
3333

3434
export { setOpenTelemetryContextAsyncContextStrategy } from './asyncContextStrategy';

0 commit comments

Comments
 (0)