Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 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(sdk-node): warn and ignore zero exporter timeout in declarative config [#6711](https://github.com/open-telemetry/opentelemetry-js/pull/6711) @MikeGoldsmith
* fix(sdk-node): pass gRPC credentials and headers to span exporter in declarative config [#6705](https://github.com/open-telemetry/opentelemetry-js/pull/6705) @MikeGoldsmith
* fix(otlp-transformer): do not attempt to skip groups [#6704](https://github.com/open-telemetry/opentelemetry-js/pull/6704) @pichlermarc
* fix(opentelemetry-exporter-prometheus): handle additional edge cases in metric name conversion [#6727](https://github.com/open-telemetry/opentelemetry-js/pull/6727) @cjihrig

### :books: Documentation

Expand Down Expand Up @@ -76,6 +77,9 @@ 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

### :books: Documentation

* fix(otlp-transformer): pin protobufjs@8.0.1 as protobufjs@8.0.3 is broken for browser use [#6646](https://github.com/open-telemetry/opentelemetry-js/pull/6646)

### :house: Internal
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: "_"`
);
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 @@ -4,6 +4,7 @@
*/

import * as assert from 'assert';
import { diag } from '@opentelemetry/api';
import type { Attributes, UpDownCounter } from '@opentelemetry/api';
import type { DataPoint, Histogram } from '@opentelemetry/sdk-metrics';
import {
Expand Down Expand Up @@ -670,6 +671,97 @@ 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 are not serialized when escaping is enabled', async () => {
const serializer = new PrometheusSerializer();
const diagErr = diag.error;
const spy = sinon.spy();
diag.error = spy;
const result = await getCounterResult('@#$%', serializer, {
exportAll: true,
});
diag.error = diagErr;

assert.strictEqual(
Comment thread
cjihrig marked this conversation as resolved.
result,
serializedDefaultResource + '# no registered metrics'
);
assert.strictEqual(spy.calledOnce, true);
sinon.assert.calledWith(
spy,
`Normalization for metric "@#$%" resulted in an invalid name: "_"`
);
});

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

assert.strictEqual(
Comment thread
cjihrig marked this conversation as resolved.
result,
serializedDefaultResource + '# no registered metrics'
);
assert.strictEqual(spy.calledOnce, true);
sinon.assert.calledWith(
spy,
`Normalization for metric "" resulted in empty name`
);
});
});

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