Skip to content

Commit ea49336

Browse files
authored
feat(shared): Introduce event-specific sampling rates (#2487)
1 parent 55f955f commit ea49336

File tree

5 files changed

+36
-5
lines changed

5 files changed

+36
-5
lines changed

.changeset/dirty-sheep-warn.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/shared': patch
3+
---
4+
5+
Update TelemetryCollector to consider event-specific sampling rates.

packages/shared/src/__tests__/telemetry.test.ts

+18
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,22 @@ describe('TelemetryCollector', () => {
152152

153153
fetchSpy.mockRestore();
154154
});
155+
156+
test('does not send events if the random seed does not exceed the event-specific sampling rate', async () => {
157+
const fetchSpy = jest.spyOn(global, 'fetch');
158+
const randomSpy = jest.spyOn(Math, 'random').mockReturnValue(0.1);
159+
160+
const collector = new TelemetryCollector({
161+
publishableKey: TEST_PK,
162+
});
163+
164+
collector.record({ event: 'TEST_EVENT', eventSamplingRate: 0.01, payload: {} });
165+
166+
jest.runAllTimers();
167+
168+
expect(fetchSpy).not.toHaveBeenCalled();
169+
170+
fetchSpy.mockRestore();
171+
randomSpy.mockRestore;
172+
});
155173
});

packages/shared/src/telemetry/collector.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { InstanceType } from '@clerk/types';
1414

1515
import { parsePublishableKey } from '../keys';
1616
import { isTruthy } from '../underscore';
17-
import type { TelemetryCollectorOptions, TelemetryEvent } from './types';
17+
import type { TelemetryCollectorOptions, TelemetryEvent, TelemetryEventRaw } from './types';
1818

1919
type TelemetryCollectorConfig = Pick<
2020
TelemetryCollectorOptions,
@@ -105,12 +105,12 @@ export class TelemetryCollector {
105105
return this.#config.debug || (typeof process !== 'undefined' && isTruthy(process.env.CLERK_TELEMETRY_DEBUG));
106106
}
107107

108-
record(event: Pick<TelemetryEvent, 'event' | 'payload'>): void {
108+
record(event: TelemetryEventRaw): void {
109109
const preparedPayload = this.#preparePayload(event.event, event.payload);
110110

111111
this.#logEvent(preparedPayload.event, preparedPayload);
112112

113-
if (!this.#shouldRecord()) {
113+
if (!this.#shouldRecord(event.eventSamplingRate)) {
114114
return;
115115
}
116116

@@ -119,8 +119,13 @@ export class TelemetryCollector {
119119
this.#scheduleFlush();
120120
}
121121

122-
#shouldRecord(): boolean {
123-
return this.isEnabled && !this.isDebug && Math.random() <= this.#config.samplingRate;
122+
#shouldRecord(eventSamplingRate?: number): boolean {
123+
const randomSeed = Math.random();
124+
const shouldBeSampled =
125+
randomSeed <= this.#config.samplingRate &&
126+
(typeof eventSamplingRate === 'undefined' || randomSeed <= eventSamplingRate);
127+
128+
return this.isEnabled && !this.isDebug && shouldBeSampled;
124129
}
125130

126131
#scheduleFlush(): void {

packages/shared/src/telemetry/events/component-mounted.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { TelemetryEventRaw } from '../types';
22

33
const EVENT_COMPONENT_MOUNTED = 'COMPONENT_MOUNTED' as const;
4+
const EVENT_SAMPLING_RATE = 0.1;
45

56
type EventComponentMounted = {
67
component: string;
@@ -19,6 +20,7 @@ export function eventComponentMounted(
1920
): TelemetryEventRaw<EventComponentMounted> {
2021
return {
2122
event: EVENT_COMPONENT_MOUNTED,
23+
eventSamplingRate: EVENT_SAMPLING_RATE,
2224
payload: {
2325
component,
2426
appearanceProp: Boolean(props?.appearance),

packages/shared/src/telemetry/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -70,5 +70,6 @@ export type TelemetryEvent = {
7070

7171
export type TelemetryEventRaw<Payload = TelemetryEvent['payload']> = {
7272
event: TelemetryEvent['event'];
73+
eventSamplingRate?: number;
7374
payload: Payload;
7475
};

0 commit comments

Comments
 (0)