Skip to content

Commit 2953a29

Browse files
dyladanmayurkale22
authored andcommitted
feat: validate metric names (#468)
* feat: validate metric names * fix: log invalid metric names * test: fix compilation errors in tests * style: fix linting issues * test: add name tests to measure metric
1 parent 5c49c6c commit 2953a29

File tree

2 files changed

+141
-25
lines changed

2 files changed

+141
-25
lines changed

packages/opentelemetry-metrics/src/Meter.ts

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
*/
1616

1717
import * as types from '@opentelemetry/types';
18-
import { ConsoleLogger } from '@opentelemetry/core';
19-
import { CounterHandle, GaugeHandle, MeasureHandle } from './Handle';
20-
import { Metric, CounterMetric, GaugeMetric } from './Metric';
18+
import {
19+
ConsoleLogger,
20+
NOOP_COUNTER_METRIC,
21+
NOOP_GAUGE_METRIC,
22+
NOOP_MEASURE_METRIC,
23+
} from '@opentelemetry/core';
24+
import { CounterMetric, GaugeMetric } from './Metric';
2125
import {
2226
MetricOptions,
2327
DEFAULT_METRIC_OPTIONS,
@@ -46,7 +50,13 @@ export class Meter implements types.Meter {
4650
createMeasure(
4751
name: string,
4852
options?: types.MetricOptions
49-
): Metric<MeasureHandle> {
53+
): types.Metric<types.MeasureHandle> {
54+
if (!this._isValidName(name)) {
55+
this._logger.warn(
56+
`Invalid metric name ${name}. Defaulting to noop metric implementation.`
57+
);
58+
return NOOP_MEASURE_METRIC;
59+
}
5060
// @todo: implement this method
5161
throw new Error('not implemented yet');
5262
}
@@ -61,7 +71,13 @@ export class Meter implements types.Meter {
6171
createCounter(
6272
name: string,
6373
options?: types.MetricOptions
64-
): Metric<CounterHandle> {
74+
): types.Metric<types.CounterHandle> {
75+
if (!this._isValidName(name)) {
76+
this._logger.warn(
77+
`Invalid metric name ${name}. Defaulting to noop metric implementation.`
78+
);
79+
return NOOP_COUNTER_METRIC;
80+
}
6581
const opt: MetricOptions = {
6682
// Counters are defined as monotonic by default
6783
monotonic: true,
@@ -83,7 +99,13 @@ export class Meter implements types.Meter {
8399
createGauge(
84100
name: string,
85101
options?: types.MetricOptions
86-
): Metric<GaugeHandle> {
102+
): types.Metric<types.GaugeHandle> {
103+
if (!this._isValidName(name)) {
104+
this._logger.warn(
105+
`Invalid metric name ${name}. Defaulting to noop metric implementation.`
106+
);
107+
return NOOP_GAUGE_METRIC;
108+
}
87109
const opt: MetricOptions = {
88110
// Gauges are defined as non-monotonic by default
89111
monotonic: false,
@@ -93,4 +115,21 @@ export class Meter implements types.Meter {
93115
};
94116
return new GaugeMetric(name, opt);
95117
}
118+
119+
/**
120+
* Ensure a metric name conforms to the following rules:
121+
*
122+
* 1. They are non-empty strings
123+
*
124+
* 2. The first character must be non-numeric, non-space, non-punctuation
125+
*
126+
* 3. Subsequent characters must be belong to the alphanumeric characters, '_', '.', and '-'.
127+
*
128+
* Names are case insensitive
129+
*
130+
* @param name Name of metric to be created
131+
*/
132+
private _isValidName(name: string): boolean {
133+
return Boolean(name.match(/^[a-z][a-z0-9_.\-]*$/i));
134+
}
96135
}

packages/opentelemetry-metrics/test/Meter.test.ts

Lines changed: 96 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
*/
1616

1717
import * as assert from 'assert';
18-
import { Meter, Metric } from '../src';
18+
import { Meter, Metric, CounterMetric, GaugeMetric } from '../src';
1919
import * as types from '@opentelemetry/types';
20-
import { NoopLogger } from '@opentelemetry/core';
20+
import { NoopLogger, NoopMetric } from '@opentelemetry/core';
2121

2222
describe('Meter', () => {
2323
let meter: Meter;
@@ -48,7 +48,7 @@ describe('Meter', () => {
4848

4949
describe('.getHandle()', () => {
5050
it('should create a counter handle', () => {
51-
const counter = meter.createCounter('name');
51+
const counter = meter.createCounter('name') as CounterMetric;
5252
const handle = counter.getHandle(labelValues);
5353
handle.add(10);
5454
assert.strictEqual(handle['_data'], 10);
@@ -57,7 +57,7 @@ describe('Meter', () => {
5757
});
5858

5959
it('should return the timeseries', () => {
60-
const counter = meter.createCounter('name');
60+
const counter = meter.createCounter('name') as CounterMetric;
6161
const handle = counter.getHandle(['value1', 'value2']);
6262
handle.add(20);
6363
assert.deepStrictEqual(handle.getTimeSeries(hrTime), {
@@ -67,7 +67,7 @@ describe('Meter', () => {
6767
});
6868

6969
it('should add positive values by default', () => {
70-
const counter = meter.createCounter('name');
70+
const counter = meter.createCounter('name') as CounterMetric;
7171
const handle = counter.getHandle(labelValues);
7272
handle.add(10);
7373
assert.strictEqual(handle['_data'], 10);
@@ -78,7 +78,7 @@ describe('Meter', () => {
7878
it('should not add the handle data when disabled', () => {
7979
const counter = meter.createCounter('name', {
8080
disabled: true,
81-
});
81+
}) as CounterMetric;
8282
const handle = counter.getHandle(labelValues);
8383
handle.add(10);
8484
assert.strictEqual(handle['_data'], 0);
@@ -87,14 +87,14 @@ describe('Meter', () => {
8787
it('should add negative value when monotonic is set to false', () => {
8888
const counter = meter.createCounter('name', {
8989
monotonic: false,
90-
});
90+
}) as CounterMetric;
9191
const handle = counter.getHandle(labelValues);
9292
handle.add(-10);
9393
assert.strictEqual(handle['_data'], -10);
9494
});
9595

9696
it('should return same handle on same label values', () => {
97-
const counter = meter.createCounter('name');
97+
const counter = meter.createCounter('name') as CounterMetric;
9898
const handle = counter.getHandle(labelValues);
9999
handle.add(10);
100100
const handle1 = counter.getHandle(labelValues);
@@ -106,7 +106,7 @@ describe('Meter', () => {
106106

107107
describe('.removeHandle()', () => {
108108
it('should remove a counter handle', () => {
109-
const counter = meter.createCounter('name');
109+
const counter = meter.createCounter('name') as CounterMetric;
110110
const handle = counter.getHandle(labelValues);
111111
assert.strictEqual(counter['_handles'].size, 1);
112112
counter.removeHandle(labelValues);
@@ -122,18 +122,46 @@ describe('Meter', () => {
122122
});
123123

124124
it('should clear all handles', () => {
125-
const counter = meter.createCounter('name');
125+
const counter = meter.createCounter('name') as CounterMetric;
126126
counter.getHandle(labelValues);
127127
assert.strictEqual(counter['_handles'].size, 1);
128128
counter.clear();
129129
assert.strictEqual(counter['_handles'].size, 0);
130130
});
131131
});
132+
133+
describe('names', () => {
134+
it('should create counter with valid names', () => {
135+
const counter1 = meter.createCounter('name1');
136+
const counter2 = meter.createCounter(
137+
'Name_with-all.valid_CharacterClasses'
138+
);
139+
assert.ok(counter1 instanceof CounterMetric);
140+
assert.ok(counter2 instanceof CounterMetric);
141+
});
142+
143+
it('should return no op metric if name is an empty string', () => {
144+
const counter = meter.createCounter('');
145+
assert.ok(counter instanceof NoopMetric);
146+
});
147+
148+
it('should return no op metric if name does not start with a letter', () => {
149+
const counter1 = meter.createCounter('1name');
150+
const counter_ = meter.createCounter('_name');
151+
assert.ok(counter1 instanceof NoopMetric);
152+
assert.ok(counter_ instanceof NoopMetric);
153+
});
154+
155+
it('should return no op metric if name is an empty string contain only letters, numbers, ".", "_", and "-"', () => {
156+
const counter = meter.createCounter('name with invalid characters^&*(');
157+
assert.ok(counter instanceof NoopMetric);
158+
});
159+
});
132160
});
133161

134162
describe('#gauge', () => {
135163
it('should create a gauge', () => {
136-
const gauge = meter.createGauge('name');
164+
const gauge = meter.createGauge('name') as GaugeMetric;
137165
assert.ok(gauge instanceof Metric);
138166
});
139167

@@ -149,7 +177,7 @@ describe('Meter', () => {
149177

150178
describe('.getHandle()', () => {
151179
it('should create a gauge handle', () => {
152-
const gauge = meter.createGauge('name');
180+
const gauge = meter.createGauge('name') as GaugeMetric;
153181
const handle = gauge.getHandle(labelValues);
154182
handle.set(10);
155183
assert.strictEqual(handle['_data'], 10);
@@ -158,7 +186,7 @@ describe('Meter', () => {
158186
});
159187

160188
it('should return the timeseries', () => {
161-
const gauge = meter.createGauge('name');
189+
const gauge = meter.createGauge('name') as GaugeMetric;
162190
const handle = gauge.getHandle(['v1', 'v2']);
163191
handle.set(150);
164192
assert.deepStrictEqual(handle.getTimeSeries(hrTime), {
@@ -168,7 +196,7 @@ describe('Meter', () => {
168196
});
169197

170198
it('should go up and down by default', () => {
171-
const gauge = meter.createGauge('name');
199+
const gauge = meter.createGauge('name') as GaugeMetric;
172200
const handle = gauge.getHandle(labelValues);
173201
handle.set(10);
174202
assert.strictEqual(handle['_data'], 10);
@@ -179,7 +207,7 @@ describe('Meter', () => {
179207
it('should not set the handle data when disabled', () => {
180208
const gauge = meter.createGauge('name', {
181209
disabled: true,
182-
});
210+
}) as GaugeMetric;
183211
const handle = gauge.getHandle(labelValues);
184212
handle.set(10);
185213
assert.strictEqual(handle['_data'], 0);
@@ -188,14 +216,14 @@ describe('Meter', () => {
188216
it('should not set negative value when monotonic is set to true', () => {
189217
const gauge = meter.createGauge('name', {
190218
monotonic: true,
191-
});
219+
}) as GaugeMetric;
192220
const handle = gauge.getHandle(labelValues);
193221
handle.set(-10);
194222
assert.strictEqual(handle['_data'], 0);
195223
});
196224

197225
it('should return same handle on same label values', () => {
198-
const gauge = meter.createGauge('name');
226+
const gauge = meter.createGauge('name') as GaugeMetric;
199227
const handle = gauge.getHandle(labelValues);
200228
handle.set(10);
201229
const handle1 = gauge.getHandle(labelValues);
@@ -207,7 +235,7 @@ describe('Meter', () => {
207235

208236
describe('.removeHandle()', () => {
209237
it('should remove the gauge handle', () => {
210-
const gauge = meter.createGauge('name');
238+
const gauge = meter.createGauge('name') as GaugeMetric;
211239
const handle = gauge.getHandle(labelValues);
212240
assert.strictEqual(gauge['_handles'].size, 1);
213241
gauge.removeHandle(labelValues);
@@ -223,12 +251,61 @@ describe('Meter', () => {
223251
});
224252

225253
it('should clear all handles', () => {
226-
const gauge = meter.createGauge('name');
254+
const gauge = meter.createGauge('name') as GaugeMetric;
227255
gauge.getHandle(labelValues);
228256
assert.strictEqual(gauge['_handles'].size, 1);
229257
gauge.clear();
230258
assert.strictEqual(gauge['_handles'].size, 0);
231259
});
232260
});
261+
262+
describe('names', () => {
263+
it('should create gauges with valid names', () => {
264+
const gauge1 = meter.createGauge('name1');
265+
const gauge2 = meter.createGauge(
266+
'Name_with-all.valid_CharacterClasses'
267+
);
268+
assert.ok(gauge1 instanceof GaugeMetric);
269+
assert.ok(gauge2 instanceof GaugeMetric);
270+
});
271+
272+
it('should return no op metric if name is an empty string', () => {
273+
const gauge = meter.createGauge('');
274+
assert.ok(gauge instanceof NoopMetric);
275+
});
276+
277+
it('should return no op metric if name does not start with a letter', () => {
278+
const gauge1 = meter.createGauge('1name');
279+
const gauge_ = meter.createGauge('_name');
280+
assert.ok(gauge1 instanceof NoopMetric);
281+
assert.ok(gauge_ instanceof NoopMetric);
282+
});
283+
284+
it('should return no op metric if name is an empty string contain only letters, numbers, ".", "_", and "-"', () => {
285+
const gauge = meter.createGauge('name with invalid characters^&*(');
286+
assert.ok(gauge instanceof NoopMetric);
287+
});
288+
});
289+
});
290+
291+
describe('#measure', () => {
292+
describe('names', () => {
293+
it('should return no op metric if name is an empty string', () => {
294+
const gauge = meter.createMeasure('');
295+
assert.ok(gauge instanceof NoopMetric);
296+
});
297+
298+
it('should return no op metric if name does not start with a letter', () => {
299+
const gauge1 = meter.createMeasure('1name');
300+
const gauge_ = meter.createMeasure('_name');
301+
assert.ok(gauge1 instanceof NoopMetric);
302+
assert.ok(gauge_ instanceof NoopMetric);
303+
});
304+
305+
it('should return no op metric if name is an empty string contain only letters, numbers, ".", "_", and "-"', () => {
306+
const gauge = meter.createMeasure('name with invalid characters^&*(');
307+
assert.ok(gauge instanceof NoopMetric);
308+
});
309+
});
233310
});
234311
});

0 commit comments

Comments
 (0)