Skip to content

Commit bc7be91

Browse files
committed
add metrics rate limits
1 parent 18aa8e6 commit bc7be91

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

packages/utils/src/ratelimit.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export function updateRateLimits(
6767
* rate limit headers are of the form
6868
* <header>,<header>,..
6969
* where each <header> is of the form
70-
* <retry_after>: <categories>: <scope>: <reason_code>
70+
* <retry_after>: <categories>: <scope>: <reason_code>: <namespaces>
7171
* where
7272
* <retry_after> is a delay in seconds
7373
* <categories> is the event type(s) (error, transaction, etc) being rate limited and is of the form
@@ -78,14 +78,21 @@ export function updateRateLimits(
7878
* Only present if rate limit applies to the metric_bucket data category.
7979
*/
8080
for (const limit of rateLimitHeader.trim().split(',')) {
81-
const [retryAfter, categories] = limit.split(':', 2);
81+
const [retryAfter, categories, , , namespaces] = limit.split(':', 5);
8282
const headerDelay = parseInt(retryAfter, 10);
8383
const delay = (!isNaN(headerDelay) ? headerDelay : 60) * 1000; // 60sec default
8484
if (!categories) {
8585
updatedRateLimits.all = now + delay;
8686
} else {
8787
for (const category of categories.split(';')) {
88-
updatedRateLimits[category] = now + delay;
88+
if (category === 'metric_bucket') {
89+
// namespaces will be present when category === 'metric_bucket'
90+
if (!namespaces || namespaces.split(';').includes('custom')) {
91+
updatedRateLimits[category] = now + delay;
92+
}
93+
} else {
94+
updatedRateLimits[category] = now + delay;
95+
}
8996
}
9097
}
9198
}

packages/utils/test/ratelimit.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,43 @@ describe('updateRateLimits()', () => {
197197
expect(updatedRateLimits.all).toEqual(60_000);
198198
});
199199
});
200+
201+
describe('data category "metric_bucket"', () => {
202+
test('should add limit for `metric_bucket` category when namespaces contain "custom"', () => {
203+
const rateLimits: RateLimits = {};
204+
const headers = {
205+
'retry-after': null,
206+
'x-sentry-rate-limits': '42:metric_bucket:::custom',
207+
};
208+
const updatedRateLimits = updateRateLimits(rateLimits, { headers }, 0);
209+
expect(updatedRateLimits.metric_bucket).toEqual(42 * 1000);
210+
});
211+
212+
test('should not add limit for `metric_bucket` category when namespaces do not contain "custom"', () => {
213+
const rateLimits: RateLimits = {};
214+
const headers = {
215+
'retry-after': null,
216+
'x-sentry-rate-limits': '42:metric_bucket:::namespace1;namespace2',
217+
};
218+
const updatedRateLimits = updateRateLimits(rateLimits, { headers }, 0);
219+
expect(updatedRateLimits.metric_bucket).toBeUndefined();
220+
});
221+
222+
test('should add limit for `metric_bucket` category when namespaces are empty', () => {
223+
const rateLimits: RateLimits = {};
224+
225+
const headers1 = {
226+
'retry-after': null,
227+
'x-sentry-rate-limits': '42:metric_bucket', // without semicolon at the end
228+
};
229+
const updatedRateLimits1 = updateRateLimits(rateLimits, { headers: headers1 }, 0);
230+
expect(updatedRateLimits1.metric_bucket).toEqual(42 * 1000);
231+
232+
const headers2 = {
233+
'retry-after': null,
234+
'x-sentry-rate-limits': '42:metric_bucket:organization:quota_exceeded:', // with semicolon at the end
235+
};
236+
const updatedRateLimits2 = updateRateLimits(rateLimits, { headers: headers2 }, 0);
237+
expect(updatedRateLimits2.metric_bucket).toEqual(42 * 1000);
238+
});
239+
});

0 commit comments

Comments
 (0)