Skip to content

Commit

Permalink
feat(exporter-prometheus): add option to add resource attributes as m…
Browse files Browse the repository at this point in the history
…etric attributes
  • Loading branch information
Marius Müller committed Jan 13, 2025
1 parent fc0edd8 commit bd68f6b
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 7 deletions.
3 changes: 3 additions & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ All notable changes to experimental packages in this project will be documented

### :rocket: (Enhancement)

* feat(exporter-prometheus): add additional attributes option [#5317](/~https://github.com/open-telemetry/opentelemetry-js/pull/5317) @marius-a-mueller
* Add `withResourceConstantLabels` option to `ExporterConfig`. It can be used to define a regex pattern to choose which resource attributes will be used as static labels on the metrics. The default is to not set any static labels.

### :bug: (Bug Fix)

* fix(exporter-metrics-otlp-http): browser OTLPMetricExporter was not passing config to OTLPMetricExporterBase super class [#5331](/~https://github.com/open-telemetry/opentelemetry-js/pull/5331) @trentm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class PrometheusExporter extends MetricReader {
endpoint: '/metrics',
prefix: '',
appendTimestamp: false,
withResourceConstantLabels: undefined,
};

private readonly _host?: string;
Expand All @@ -43,6 +44,7 @@ export class PrometheusExporter extends MetricReader {
private readonly _server: Server;
private readonly _prefix?: string;
private readonly _appendTimestamp: boolean;
private readonly _withResourceConstantLabels?: string | RegExp;
private _serializer: PrometheusSerializer;
private _startServerPromise: Promise<void> | undefined;

Expand Down Expand Up @@ -82,11 +84,15 @@ export class PrometheusExporter extends MetricReader {
typeof config.appendTimestamp === 'boolean'
? config.appendTimestamp
: PrometheusExporter.DEFAULT_OPTIONS.appendTimestamp;
this._withResourceConstantLabels =
config.withResourceConstantLabels ||
PrometheusExporter.DEFAULT_OPTIONS.withResourceConstantLabels;
// unref to prevent prometheus exporter from holding the process open on exit
this._server = createServer(this._requestHandler).unref();
this._serializer = new PrometheusSerializer(
this._prefix,
this._appendTimestamp
this._appendTimestamp,
this._withResourceConstantLabels
);

this._baseUrl = `http://${this._host}:${this._port}/`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,29 @@ const NO_REGISTERED_METRICS = '# no registered metrics';
export class PrometheusSerializer {
private _prefix: string | undefined;
private _appendTimestamp: boolean;
private _additionalAttributes: Attributes | undefined;
private _withResourceConstantLabels: string | RegExp | undefined;

constructor(prefix?: string, appendTimestamp = false) {
constructor(
prefix?: string,
appendTimestamp = false,
withResourceConstantLabels?: string | RegExp
) {
if (prefix) {
this._prefix = prefix + '_';
}
this._appendTimestamp = appendTimestamp;
this._withResourceConstantLabels = withResourceConstantLabels;
}

serialize(resourceMetrics: ResourceMetrics): string {
let str = '';

this._additionalAttributes = this._filterResourceConstantLabels(
resourceMetrics.resource.attributes,
this._withResourceConstantLabels
);

for (const scopeMetrics of resourceMetrics.scopeMetrics) {
str += this._serializeScopeMetrics(scopeMetrics);
}
Expand All @@ -196,6 +208,23 @@ export class PrometheusSerializer {
return this._serializeResource(resourceMetrics.resource) + str;
}

private _filterResourceConstantLabels(
attributes: Attributes,
pattern: string | RegExp | undefined
) {
if (pattern) {
const filteredAttributes: Attributes = {};
for (const [key, value] of Object.entries(attributes)) {
const sanitizedAttributeName = sanitizePrometheusMetricName(key);
if (sanitizedAttributeName.match(pattern)) {
filteredAttributes[sanitizedAttributeName] = value;
}
}
return filteredAttributes;
}
return;
}

private _serializeScopeMetrics(scopeMetrics: ScopeMetrics) {
let str = '';
for (const metric of scopeMetrics.metrics) {
Expand Down Expand Up @@ -263,7 +292,7 @@ export class PrometheusSerializer {
attributes,
value,
this._appendTimestamp ? timestamp : undefined,
undefined
this._additionalAttributes
);
return results;
}
Expand All @@ -288,7 +317,7 @@ export class PrometheusSerializer {
attributes,
value,
this._appendTimestamp ? timestamp : undefined,
undefined
this._additionalAttributes
);
}

Expand All @@ -315,12 +344,12 @@ export class PrometheusSerializer {
attributes,
cumulativeSum,
this._appendTimestamp ? timestamp : undefined,
{
Object.assign({}, this._additionalAttributes ?? {}, {
le:
upperBound === undefined || upperBound === Infinity
? '+Inf'
: String(upperBound),
}
})
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,12 @@ export interface ExporterConfig {
* @experimental
*/
metricProducers?: MetricProducer[];

/**
* Regex pattern for defining which resource attributes will be applied
* as constant labels to the metrics.
* e.g. 'telemetry_.+' for all attributes starting with 'telemetry'.
* @default undefined (no resource attributes are applied)
*/
withResourceConstantLabels?: string | RegExp;
}
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,40 @@ describe('PrometheusExporter', () => {
'',
]);
});

it('should export a metric with all resource attributes', async () => {
exporter = new PrometheusExporter({
withResourceConstantLabels: '.*',
});
setup(exporter);
const body = await request('http://localhost:9464/metrics');
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedDefaultResourceLines,
'# HELP counter_total description missing',
'# TYPE counter_total counter',
`counter_total{key1="attributeValue1",service_name="${serviceName}",telemetry_sdk_language="${sdkLanguage}",telemetry_sdk_name="${sdkName}",telemetry_sdk_version="${sdkVersion}"} 10`,
'',
]);
});

it('should export a metric with two resource attributes', async () => {
exporter = new PrometheusExporter({
withResourceConstantLabels: 'telemetry_sdk_language|telemetry_sdk_name',
});
setup(exporter);
const body = await request('http://localhost:9464/metrics');
const lines = body.split('\n');

assert.deepStrictEqual(lines, [
...serializedDefaultResourceLines,
'# HELP counter_total description missing',
'# TYPE counter_total counter',
`counter_total{key1="attributeValue1",telemetry_sdk_language="${sdkLanguage}",telemetry_sdk_name="${sdkName}"} 10`,
'',
]);
});
});
});

Expand Down
Loading

0 comments on commit bd68f6b

Please sign in to comment.