Skip to content

Commit 0e0bccb

Browse files
authored
chore: move metrics api into base Unleash (#775)
1 parent 46bf068 commit 0e0bccb

File tree

5 files changed

+150
-132
lines changed

5 files changed

+150
-132
lines changed

src/impact-metrics/context.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { CustomHeaders } from '../headers';
2+
import { StaticContext } from '../unleash';
3+
import { extractEnvironmentFromCustomHeaders } from './environment-resolver';
4+
5+
export const buildImpactMetricContext = (
6+
customHeaders: CustomHeaders | undefined,
7+
staticContext: StaticContext,
8+
): StaticContext => {
9+
const metricsContext: StaticContext = { ...staticContext };
10+
if (customHeaders) {
11+
const environment = extractEnvironmentFromCustomHeaders(customHeaders);
12+
if (environment) {
13+
metricsContext.environment = environment;
14+
}
15+
}
16+
return metricsContext;
17+
};

src/impact-metrics/metric-api.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { EventEmitter } from 'stream';
2+
import { StaticContext, UnleashEvents } from '../unleash';
3+
import { ImpactMetricRegistry, MetricFlagContext, MetricLabels } from './metric-types';
4+
import Client from '../client';
5+
6+
export class MetricsAPI extends EventEmitter {
7+
constructor(
8+
private metricRegistry: ImpactMetricRegistry,
9+
private variantResolver: Pick<Client, 'forceGetVariant'>,
10+
private staticContext: StaticContext,
11+
) {
12+
super();
13+
}
14+
15+
defineCounter(name: string, help: string) {
16+
if (!name || !help) {
17+
this.emit(UnleashEvents.Warn, `Counter name or help cannot be empty: ${name}, ${help}.`);
18+
return;
19+
}
20+
const labelNames = ['featureName', 'appName', 'environment'];
21+
this.metricRegistry.counter({ name, help, labelNames });
22+
}
23+
24+
defineGauge(name: string, help: string) {
25+
if (!name || !help) {
26+
this.emit(UnleashEvents.Warn, `Gauge name or help cannot be empty: ${name}, ${help}.`);
27+
return;
28+
}
29+
const labelNames = ['featureName', 'appName', 'environment'];
30+
this.metricRegistry.gauge({ name, help, labelNames });
31+
}
32+
33+
defineHistogram(name: string, help: string, buckets?: number[]) {
34+
if (!name || !help) {
35+
this.emit(UnleashEvents.Warn, `Histogram name or help cannot be empty: ${name}, ${help}.`);
36+
return;
37+
}
38+
const labelNames = ['featureName', 'appName', 'environment'];
39+
this.metricRegistry.histogram({ name, help, labelNames, buckets: buckets || [] });
40+
}
41+
42+
private getFlagLabels(flagContext?: MetricFlagContext): MetricLabels {
43+
let flagLabels: MetricLabels = {};
44+
if (flagContext) {
45+
for (const flag of flagContext.flagNames) {
46+
const variant = this.variantResolver.forceGetVariant(flag, flagContext.context);
47+
48+
if (variant.enabled) {
49+
flagLabels[flag] = variant.name;
50+
} else if (variant.feature_enabled) {
51+
flagLabels[flag] = 'enabled';
52+
} else {
53+
flagLabels[flag] = 'disabled';
54+
}
55+
}
56+
}
57+
return flagLabels;
58+
}
59+
60+
incrementCounter(name: string, value?: number, flagContext?: MetricFlagContext): void {
61+
const counter = this.metricRegistry.getCounter(name);
62+
if (!counter) {
63+
this.emit(
64+
UnleashEvents.Warn,
65+
`Counter ${name} not defined, this counter will not be incremented.`,
66+
);
67+
return;
68+
}
69+
70+
const flagLabels = this.getFlagLabels(flagContext);
71+
72+
const labels = {
73+
...flagLabels,
74+
...this.staticContext,
75+
};
76+
77+
counter.inc(value, labels);
78+
}
79+
80+
updateGauge(name: string, value: number, flagContext?: MetricFlagContext): void {
81+
const gauge = this.metricRegistry.getGauge(name);
82+
if (!gauge) {
83+
this.emit(UnleashEvents.Warn, `Gauge ${name} not defined, this gauge will not be updated.`);
84+
return;
85+
}
86+
87+
const flagLabels = this.getFlagLabels(flagContext);
88+
89+
const labels = {
90+
...flagLabels,
91+
...this.staticContext,
92+
};
93+
94+
gauge.set(value, labels);
95+
}
96+
97+
observeHistogram(name: string, value: number, flagContext?: MetricFlagContext): void {
98+
const histogram = this.metricRegistry.getHistogram(name);
99+
if (!histogram) {
100+
this.emit(
101+
UnleashEvents.Warn,
102+
`Histogram ${name} not defined, this histogram will not be updated.`,
103+
);
104+
return;
105+
}
106+
107+
const flagLabels = this.getFlagLabels(flagContext);
108+
109+
const labels = {
110+
...flagLabels,
111+
...this.staticContext,
112+
};
113+
114+
histogram.observe(value, labels);
115+
}
116+
}
Lines changed: 6 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,137 +1,12 @@
1-
import { EventEmitter } from 'stream';
2-
import { StaticContext, Unleash, UnleashEvents } from '../unleash';
3-
import { ImpactMetricRegistry, MetricFlagContext, MetricLabels } from './metric-types';
4-
import { extractEnvironmentFromCustomHeaders } from './environment-resolver';
5-
import Client from '../client';
6-
7-
export class MetricsAPI extends EventEmitter {
8-
constructor(
9-
private metricRegistry: ImpactMetricRegistry,
10-
private variantResolver: Pick<Client, 'forceGetVariant'>,
11-
private staticContext: StaticContext,
12-
) {
13-
super();
14-
}
15-
16-
defineCounter(name: string, help: string) {
17-
if (!name || !help) {
18-
this.emit(UnleashEvents.Warn, `Counter name or help cannot be empty: ${name}, ${help}.`);
19-
return;
20-
}
21-
const labelNames = ['featureName', 'appName', 'environment'];
22-
this.metricRegistry.counter({ name, help, labelNames });
23-
}
24-
25-
defineGauge(name: string, help: string) {
26-
if (!name || !help) {
27-
this.emit(UnleashEvents.Warn, `Gauge name or help cannot be empty: ${name}, ${help}.`);
28-
return;
29-
}
30-
const labelNames = ['featureName', 'appName', 'environment'];
31-
this.metricRegistry.gauge({ name, help, labelNames });
32-
}
33-
34-
defineHistogram(name: string, help: string, buckets?: number[]) {
35-
if (!name || !help) {
36-
this.emit(UnleashEvents.Warn, `Histogram name or help cannot be empty: ${name}, ${help}.`);
37-
return;
38-
}
39-
const labelNames = ['featureName', 'appName', 'environment'];
40-
this.metricRegistry.histogram({ name, help, labelNames, buckets: buckets || [] });
41-
}
42-
43-
private getFlagLabels(flagContext?: MetricFlagContext): MetricLabels {
44-
let flagLabels: MetricLabels = {};
45-
if (flagContext) {
46-
for (const flag of flagContext.flagNames) {
47-
const variant = this.variantResolver.forceGetVariant(flag, flagContext.context);
48-
49-
if (variant.enabled) {
50-
flagLabels[flag] = variant.name;
51-
} else if (variant.feature_enabled) {
52-
flagLabels[flag] = 'enabled';
53-
} else {
54-
flagLabels[flag] = 'disabled';
55-
}
56-
}
57-
}
58-
return flagLabels;
59-
}
60-
61-
incrementCounter(name: string, value?: number, flagContext?: MetricFlagContext): void {
62-
const counter = this.metricRegistry.getCounter(name);
63-
if (!counter) {
64-
this.emit(
65-
UnleashEvents.Warn,
66-
`Counter ${name} not defined, this counter will not be incremented.`,
67-
);
68-
return;
69-
}
70-
71-
const flagLabels = this.getFlagLabels(flagContext);
72-
73-
const labels = {
74-
...flagLabels,
75-
...this.staticContext,
76-
};
77-
78-
counter.inc(value, labels);
79-
}
80-
81-
updateGauge(name: string, value: number, flagContext?: MetricFlagContext): void {
82-
const gauge = this.metricRegistry.getGauge(name);
83-
if (!gauge) {
84-
this.emit(UnleashEvents.Warn, `Gauge ${name} not defined, this gauge will not be updated.`);
85-
return;
86-
}
87-
88-
const flagLabels = this.getFlagLabels(flagContext);
89-
90-
const labels = {
91-
...flagLabels,
92-
...this.staticContext,
93-
};
94-
95-
gauge.set(value, labels);
96-
}
97-
98-
observeHistogram(name: string, value: number, flagContext?: MetricFlagContext): void {
99-
const histogram = this.metricRegistry.getHistogram(name);
100-
if (!histogram) {
101-
this.emit(
102-
UnleashEvents.Warn,
103-
`Histogram ${name} not defined, this histogram will not be updated.`,
104-
);
105-
return;
106-
}
107-
108-
const flagLabels = this.getFlagLabels(flagContext);
109-
110-
const labels = {
111-
...flagLabels,
112-
...this.staticContext,
113-
};
114-
115-
histogram.observe(value, labels);
116-
}
117-
}
1+
import { Unleash } from '../unleash';
1182

1193
export class UnleashMetricClient extends Unleash {
120-
public impactMetrics: MetricsAPI;
121-
1224
constructor(...args: ConstructorParameters<typeof Unleash>) {
1235
super(...args);
124-
125-
const config = args[0];
126-
const metricsContext: StaticContext = { ...this.staticContext };
127-
128-
if (config && config.customHeaders) {
129-
const environment = extractEnvironmentFromCustomHeaders(config.customHeaders);
130-
if (environment) {
131-
metricsContext.environment = environment;
132-
}
133-
}
134-
135-
this.impactMetrics = new MetricsAPI(this.metricRegistry, this.client, metricsContext);
6+
console.warn(
7+
'UnleashMetricClient is deprecated. ' +
8+
'This functionality now lives in UnleashClient. ' +
9+
'This class will be removed in the next major release.',
10+
);
13611
}
13712
}

src/test/impact-metrics/metric-client.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MetricsAPI } from '../../impact-metrics/metric-client';
1+
import { MetricsAPI } from '../../impact-metrics/metric-api';
22

33
import test from 'ava';
44
import Client from '../../client';

src/unleash.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { UnleashConfig } from './unleash-config';
2020
import FileStorageProvider from './repository/storage-provider-file';
2121
import { uuidv4 } from './uuidv4';
2222
import { InMemoryMetricRegistry } from './impact-metrics/metric-types';
23+
import { MetricsAPI } from './impact-metrics/metric-api';
24+
import { buildImpactMetricContext } from './impact-metrics/context';
2325
export { Strategy, UnleashEvents, UnleashConfig };
2426

2527
const BACKUP_PATH: string = tmpdir();
@@ -52,6 +54,8 @@ export class Unleash extends EventEmitter {
5254

5355
protected metricRegistry = new InMemoryMetricRegistry();
5456

57+
public impactMetrics: MetricsAPI;
58+
5559
constructor({
5660
appName,
5761
environment = 'default',
@@ -185,6 +189,12 @@ export class Unleash extends EventEmitter {
185189
metricRegistry: this.metricRegistry,
186190
});
187191

192+
this.impactMetrics = new MetricsAPI(
193+
this.metricRegistry,
194+
this.client,
195+
buildImpactMetricContext(customHeaders, this.staticContext),
196+
);
197+
188198
this.metrics.on(UnleashEvents.Error, (err) => {
189199
// eslint-disable-next-line no-param-reassign
190200
err.message = `Unleash Metrics error: ${err.message}`;

0 commit comments

Comments
 (0)