@platformatic/metrics 3.33.0 → 3.34.1-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +39 -10
- package/package.json +1 -1
- package/test/index.test.js +113 -1
package/index.js
CHANGED
|
@@ -284,12 +284,12 @@ export async function collectThreadMetrics (applicationId, workerId, metricsConf
|
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
if (metricsConfig.httpMetrics && !ensureMetricsGroup(registry, 'http')) {
|
|
287
|
+
// Build custom labels configuration
|
|
288
|
+
const { customLabels, getCustomLabels } = buildCustomLabelsConfig(metricsConfig.httpCustomLabels)
|
|
289
|
+
|
|
287
290
|
collectHttpMetrics(registry, {
|
|
288
|
-
customLabels
|
|
289
|
-
getCustomLabels
|
|
290
|
-
const telemetryId = req.headers?.['x-plt-telemetry-id'] ?? 'unknown'
|
|
291
|
-
return { telemetry_id: telemetryId }
|
|
292
|
-
},
|
|
291
|
+
customLabels,
|
|
292
|
+
getCustomLabels,
|
|
293
293
|
histogram: {
|
|
294
294
|
name: 'http_request_all_duration_seconds',
|
|
295
295
|
help: 'request duration in seconds summary for all requests',
|
|
@@ -313,6 +313,35 @@ export async function collectThreadMetrics (applicationId, workerId, metricsConf
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
// Build custom labels configuration from metrics config
|
|
317
|
+
// Returns { customLabels: string[], getCustomLabels: (req) => object }
|
|
318
|
+
export function buildCustomLabelsConfig (customLabelsConfig) {
|
|
319
|
+
// Default: use telemetry_id from x-plt-telemetry-id header
|
|
320
|
+
if (!customLabelsConfig || customLabelsConfig.length === 0) {
|
|
321
|
+
return {
|
|
322
|
+
customLabels: ['telemetry_id'],
|
|
323
|
+
getCustomLabels: req => {
|
|
324
|
+
const telemetryId = req.headers?.['x-plt-telemetry-id'] ?? 'unknown'
|
|
325
|
+
return { telemetry_id: telemetryId }
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Build custom labels from configuration
|
|
331
|
+
const customLabels = customLabelsConfig.map(label => label.name)
|
|
332
|
+
|
|
333
|
+
const getCustomLabels = req => {
|
|
334
|
+
const labels = {}
|
|
335
|
+
for (const labelConfig of customLabelsConfig) {
|
|
336
|
+
const headerValue = req.headers?.[labelConfig.header.toLowerCase()]
|
|
337
|
+
labels[labelConfig.name] = headerValue ?? labelConfig.default ?? 'unknown'
|
|
338
|
+
}
|
|
339
|
+
return labels
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return { customLabels, getCustomLabels }
|
|
343
|
+
}
|
|
344
|
+
|
|
316
345
|
// Original function for backward compatibility (collects all metrics)
|
|
317
346
|
export async function collectMetrics (applicationId, workerId, metricsConfig = {}, registry = undefined) {
|
|
318
347
|
if (!registry) {
|
|
@@ -341,12 +370,12 @@ export async function collectMetrics (applicationId, workerId, metricsConfig = {
|
|
|
341
370
|
}
|
|
342
371
|
|
|
343
372
|
if (metricsConfig.httpMetrics && !ensureMetricsGroup(registry, 'http')) {
|
|
373
|
+
// Build custom labels configuration
|
|
374
|
+
const { customLabels, getCustomLabels } = buildCustomLabelsConfig(metricsConfig.httpCustomLabels)
|
|
375
|
+
|
|
344
376
|
collectHttpMetrics(registry, {
|
|
345
|
-
customLabels
|
|
346
|
-
getCustomLabels
|
|
347
|
-
const telemetryId = req.headers?.['x-plt-telemetry-id'] ?? 'unknown'
|
|
348
|
-
return { telemetry_id: telemetryId }
|
|
349
|
-
},
|
|
377
|
+
customLabels,
|
|
378
|
+
getCustomLabels,
|
|
350
379
|
histogram: {
|
|
351
380
|
name: 'http_request_all_duration_seconds',
|
|
352
381
|
help: 'request duration in seconds summary for all requests',
|
package/package.json
CHANGED
package/test/index.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import { test } from 'node:test'
|
|
3
|
-
import { client, collectMetrics } from '../index.js'
|
|
3
|
+
import { buildCustomLabelsConfig, client, collectMetrics } from '../index.js'
|
|
4
4
|
|
|
5
5
|
const nextTick = () => new Promise(resolve => process.nextTick(resolve))
|
|
6
6
|
|
|
@@ -126,3 +126,115 @@ test('httpMetrics summary resets after metric collection', async () => {
|
|
|
126
126
|
assert.strictEqual(sum?.value || 0, 0, 'summary sum should be reset to 0')
|
|
127
127
|
assert.strictEqual(count?.value || 0, 0, 'summary count should be reset to 0')
|
|
128
128
|
})
|
|
129
|
+
|
|
130
|
+
// Tests for buildCustomLabelsConfig
|
|
131
|
+
test('buildCustomLabelsConfig returns default telemetry_id when no config provided', () => {
|
|
132
|
+
const result = buildCustomLabelsConfig(undefined)
|
|
133
|
+
|
|
134
|
+
assert.deepStrictEqual(result.customLabels, ['telemetry_id'])
|
|
135
|
+
assert.strictEqual(typeof result.getCustomLabels, 'function')
|
|
136
|
+
|
|
137
|
+
// Test default getCustomLabels function
|
|
138
|
+
const labels = result.getCustomLabels({ headers: { 'x-plt-telemetry-id': 'test-id' } })
|
|
139
|
+
assert.deepStrictEqual(labels, { telemetry_id: 'test-id' })
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('buildCustomLabelsConfig returns default telemetry_id when empty array provided', () => {
|
|
143
|
+
const result = buildCustomLabelsConfig([])
|
|
144
|
+
|
|
145
|
+
assert.deepStrictEqual(result.customLabels, ['telemetry_id'])
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test('buildCustomLabelsConfig uses unknown as default when header is missing', () => {
|
|
149
|
+
const result = buildCustomLabelsConfig(undefined)
|
|
150
|
+
|
|
151
|
+
const labels = result.getCustomLabels({ headers: {} })
|
|
152
|
+
assert.deepStrictEqual(labels, { telemetry_id: 'unknown' })
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('buildCustomLabelsConfig builds custom labels from configuration', () => {
|
|
156
|
+
const config = [
|
|
157
|
+
{ name: 'domain', header: 'x-forwarded-host' },
|
|
158
|
+
{ name: 'api_version', header: 'x-api-version' }
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
const result = buildCustomLabelsConfig(config)
|
|
162
|
+
|
|
163
|
+
assert.deepStrictEqual(result.customLabels, ['domain', 'api_version'])
|
|
164
|
+
assert.strictEqual(typeof result.getCustomLabels, 'function')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test('buildCustomLabelsConfig getCustomLabels extracts values from headers', () => {
|
|
168
|
+
const config = [
|
|
169
|
+
{ name: 'domain', header: 'x-forwarded-host' },
|
|
170
|
+
{ name: 'api_version', header: 'x-api-version' }
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
const result = buildCustomLabelsConfig(config)
|
|
174
|
+
|
|
175
|
+
const labels = result.getCustomLabels({
|
|
176
|
+
headers: {
|
|
177
|
+
'x-forwarded-host': 'example.com',
|
|
178
|
+
'x-api-version': 'v2'
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
assert.deepStrictEqual(labels, { domain: 'example.com', api_version: 'v2' })
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('buildCustomLabelsConfig uses custom default value when header is missing', () => {
|
|
186
|
+
const config = [
|
|
187
|
+
{ name: 'domain', header: 'x-forwarded-host', default: 'default-domain' },
|
|
188
|
+
{ name: 'api_version', header: 'x-api-version' }
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
const result = buildCustomLabelsConfig(config)
|
|
192
|
+
|
|
193
|
+
const labels = result.getCustomLabels({ headers: {} })
|
|
194
|
+
|
|
195
|
+
assert.deepStrictEqual(labels, { domain: 'default-domain', api_version: 'unknown' })
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('buildCustomLabelsConfig handles case-insensitive header names', () => {
|
|
199
|
+
const config = [
|
|
200
|
+
{ name: 'domain', header: 'X-Forwarded-Host' }
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
const result = buildCustomLabelsConfig(config)
|
|
204
|
+
|
|
205
|
+
// HTTP headers are case-insensitive, and Node.js lowercases them
|
|
206
|
+
const labels = result.getCustomLabels({
|
|
207
|
+
headers: {
|
|
208
|
+
'x-forwarded-host': 'example.com'
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
assert.deepStrictEqual(labels, { domain: 'example.com' })
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test('httpMetrics with custom labels configuration', async () => {
|
|
216
|
+
const httpCustomLabels = [
|
|
217
|
+
{ name: 'domain', header: 'x-forwarded-host', default: 'localhost' }
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
const result = await collectMetrics('test-service', 1, {
|
|
221
|
+
httpMetrics: true,
|
|
222
|
+
httpCustomLabels
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
const metrics = await result.registry.getMetricsAsJSON()
|
|
226
|
+
const histogram = metrics.find(m => m.name === 'http_request_all_duration_seconds')
|
|
227
|
+
|
|
228
|
+
assert.ok(histogram, 'histogram metric should exist')
|
|
229
|
+
|
|
230
|
+
// Verify custom label is used by observing a value
|
|
231
|
+
const histogramMetric = result.registry.getSingleMetric('http_request_all_duration_seconds')
|
|
232
|
+
histogramMetric.observe({ method: 'GET', domain: 'example.com' }, 0.1)
|
|
233
|
+
|
|
234
|
+
const metricsAfterObserve = await result.registry.getMetricsAsJSON()
|
|
235
|
+
const histogramAfterObserve = metricsAfterObserve.find(m => m.name === 'http_request_all_duration_seconds')
|
|
236
|
+
|
|
237
|
+
// Check that the domain label is present in the recorded values
|
|
238
|
+
const hasCustomLabel = histogramAfterObserve.values.some(v => v.labels?.domain === 'example.com')
|
|
239
|
+
assert.ok(hasCustomLabel, 'custom domain label should be present in histogram values')
|
|
240
|
+
})
|