Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2
* fix(instrumentation-xhr): resolve relative URLs before matching `ignoreUrls` [#6551](https://github.com/open-telemetry/opentelemetry-js/pull/6551) @Maximiliano-Zeballos
* fix(sdk-node): fix setting of ViewOption#name from ConfigurationModel [#6620](https://github.com/open-telemetry/opentelemetry-js/pull/6620) @trentm
* fix(web-common): add limit for timeout [#6601](https://github.com/open-telemetry/opentelemetry-js/pull/6601) @maryliag
* fix(opentelemetry-exporter-prometheus): handle additional edge cases in metric name conversion [#xxxx](https://github.com/open-telemetry/opentelemetry-js/pull/xxxx) @cjihrig
Comment thread
cjihrig marked this conversation as resolved.
Outdated

### :books: Documentation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,11 @@ export class PrometheusSerializer {
private _serializeScopeMetrics(scopeMetrics: ScopeMetrics) {
let str = '';
for (const metric of scopeMetrics.metrics) {
str += this._serializeMetricData(metric, scopeMetrics.scope) + '\n';
const metricStr = this._serializeMetricData(metric, scopeMetrics.scope);

if (metricStr) {
str += metricStr + '\n';
}
}
return str;
}
Expand All @@ -243,6 +247,21 @@ export class PrometheusSerializer {
if (this._prefix) {
name = `${this._prefix}${name}`;
}

if (name === '') {
diag.error(
`Normalization for metric "${metricData.descriptor.name}" resulted in empty name`
);
return '';
} else if (name === '_') {
diag.error(
`Normalization for metric "${metricData.descriptor.name}" resulted in an invalid name`
Comment thread
cjihrig marked this conversation as resolved.
Outdated
);
return '';
} else if (name[0] >= '0' && name[0] <= '9') {
name = `_${name}`;
}

const dataPointType = metricData.dataPointType;

name = enforcePrometheusNamingConvention(name, metricData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,79 @@ describe('PrometheusSerializer', () => {

assert.strictEqual(result, 'test_total 1\n');
});

it('replaces special characters with underscores when escaping is enabled', async () => {
const serializer = new PrometheusSerializer();
const result = await getCounterResult(
'metric@with#special$chars',
serializer,
{
exportAll: true,
}
);

assert.strictEqual(
result,
serializedDefaultResource +
'# HELP metric_with_special_chars_total description missing\n' +
'# TYPE metric_with_special_chars_total counter\n' +
'metric_with_special_chars_total{otel_scope_name="test"} 1\n'
);
});

it('metric names do not start with a digit when escaping is enabled', async () => {
const serializer = new PrometheusSerializer();
const result = await getCounterResult('123metric', serializer, {
exportAll: true,
});

assert.strictEqual(
result,
serializedDefaultResource +
'# HELP _123metric_total description missing\n' +
'# TYPE _123metric_total counter\n' +
'_123metric_total{otel_scope_name="test"} 1\n'
);
});

it('multiple special characters are collapsed to a single underscore when escaping is enabled', async () => {
const serializer = new PrometheusSerializer();
const result = await getCounterResult('metric@@##$$name', serializer, {
exportAll: true,
});

assert.strictEqual(
result,
serializedDefaultResource +
'# HELP metric_name_total description missing\n' +
'# TYPE metric_name_total counter\n' +
'metric_name_total{otel_scope_name="test"} 1\n'
);
});

it('metric names of only special characters throw when escaping is enabled', async () => {
Comment thread
cjihrig marked this conversation as resolved.
Outdated
const serializer = new PrometheusSerializer();
const result = await getCounterResult('@#$%', serializer, {
exportAll: true,
});

assert.strictEqual(
Comment thread
cjihrig marked this conversation as resolved.
result,
serializedDefaultResource + '# no registered metrics'
);
});

it('metrics with empty names are not serialized', async () => {
const serializer = new PrometheusSerializer();
const result = await getCounterResult('', serializer, {
exportAll: true,
});

assert.strictEqual(
Comment thread
cjihrig marked this conversation as resolved.
result,
serializedDefaultResource + '# no registered metrics'
);
});
});

describe('serialize non-normalized values', () => {
Expand Down