Skip to content

Commit 5c1908f

Browse files
authored
Add feature flag state metrics (#340)
1 parent 314ee12 commit 5c1908f

File tree

2 files changed

+55
-2
lines changed

2 files changed

+55
-2
lines changed

src/featureFlag/FeatureFlagProvider.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { writeFileSync } from 'fs';
22
import { join } from 'path';
33
import { LoggerFactory } from '../telemetry/LoggerFactory';
4-
import { Measure } from '../telemetry/TelemetryDecorator';
4+
import { ScopedTelemetry } from '../telemetry/ScopedTelemetry';
5+
import { Measure, Telemetry } from '../telemetry/TelemetryDecorator';
56
import { Closeable } from '../utils/Closeable';
67
import { AwsEnv } from '../utils/Environment';
78
import { readFileIfExists } from '../utils/File';
@@ -12,6 +13,9 @@ import { FeatureFlagSupplier, FeatureFlagConfigKey, TargetedFeatureFlagConfigKey
1213
const log = LoggerFactory.getLogger('FeatureFlagProvider');
1314

1415
export class FeatureFlagProvider implements Closeable {
16+
@Telemetry()
17+
private readonly telemetry!: ScopedTelemetry;
18+
1519
private config: unknown;
1620
private readonly supplier: FeatureFlagSupplier;
1721

@@ -44,6 +48,7 @@ export class FeatureFlagProvider implements Closeable {
4448
5 * 60 * 1000,
4549
);
4650

51+
this.registerGauges();
4752
this.log();
4853
}
4954

@@ -81,6 +86,14 @@ export class FeatureFlagProvider implements Closeable {
8186
);
8287
}
8388

89+
private registerGauges() {
90+
for (const [key, flag] of this.supplier.featureFlags.entries()) {
91+
this.telemetry.registerGaugeProvider(`featureFlag.${key}`, () => (flag.isEnabled() ? 1 : 0), {
92+
description: `State of ${key} feature flag`,
93+
});
94+
}
95+
}
96+
8497
close() {
8598
this.supplier.close();
8699
clearInterval(this.timeout);

tst/unit/featureFlag/FeatureFlagProvider.test.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { readFileSync } from 'fs';
22
import { join } from 'path';
3-
import { describe, it, expect } from 'vitest';
3+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
44
import { FeatureFlagConfigSchema } from '../../../src/featureFlag/FeatureFlagBuilder';
5+
import { FeatureFlagProvider } from '../../../src/featureFlag/FeatureFlagProvider';
6+
import { ScopedTelemetry } from '../../../src/telemetry/ScopedTelemetry';
57

68
describe('FeatureFlagProvider', () => {
79
it('can parse feature flags', () => {
@@ -15,4 +17,42 @@ describe('FeatureFlagProvider', () => {
1517
expect(FeatureFlagConfigSchema.parse(JSON.parse(file))).toBeDefined();
1618
});
1719
});
20+
21+
describe('gauge registration', () => {
22+
let provider: FeatureFlagProvider;
23+
let registerGaugeProviderSpy: ReturnType<typeof vi.spyOn>;
24+
25+
beforeEach(() => {
26+
registerGaugeProviderSpy = vi.spyOn(ScopedTelemetry.prototype, 'registerGaugeProvider');
27+
});
28+
29+
afterEach(() => {
30+
provider?.close();
31+
vi.restoreAllMocks();
32+
});
33+
34+
it('registers gauges for each feature flag', () => {
35+
provider = new FeatureFlagProvider(
36+
() => Promise.resolve({ features: { Constants: { enabled: true } } }),
37+
join(__dirname, '..', '..', '..', 'assets', 'featureFlag', 'alpha.json'),
38+
);
39+
40+
expect(registerGaugeProviderSpy).toHaveBeenCalledWith(
41+
'featureFlag.Constants',
42+
expect.any(Function),
43+
expect.objectContaining({ description: 'State of Constants feature flag' }),
44+
);
45+
});
46+
47+
it('gauge provider reflects current flag state', () => {
48+
provider = new FeatureFlagProvider(
49+
() => Promise.resolve({ features: { Constants: { enabled: false } } }),
50+
join(__dirname, '..', '..', '..', 'assets', 'featureFlag', 'alpha.json'),
51+
);
52+
53+
const gaugeProvider = registerGaugeProviderSpy.mock.calls[0][1] as () => number;
54+
// Alpha config has Constants disabled
55+
expect(gaugeProvider()).toBe(0);
56+
});
57+
});
1858
});

0 commit comments

Comments
 (0)