Skip to content
Merged
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ app.listen({ port: 0 }, (err, address) => {
- __`ignore(req, res, server)`__ `<function>` A function that returns a boolean indicating whether to ignore the request when collecting metrics. The function receives the request object as a first argument and a response object as a second argument.
- __`histogram`__ `<object>` prom-client [histogram options](https://github.com/siimon/prom-client?tab=readme-ov-file#histogram). Use it if you want to customize the histogram.
- __`summary`__ `<object>` prom-client [summary options](https://github.com/siimon/prom-client?tab=readme-ov-file#summary). Use it if you want to customize the summary.
- __`zeroFill`__ `<boolean>` Whether to zero-fill the histogram and summary buckets. Default: `false`.

## License

Expand Down
17 changes: 15 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = fp(async function (fastify, opts) {
const ignoreMethods = opts.ignoreMethods || defaultIgnoreMethods
const ignoreRoutes = opts.ignoreRoutes || []
const ignore = opts.ignore || (() => false)
const zeroFill = opts.zeroFill || false

function ignoreRoute (request) {
if (ignoreMethods.includes(request.method)) return true
Expand All @@ -35,6 +36,10 @@ module.exports = fp(async function (fastify, opts) {
...opts.summary,
})

if (zeroFill) {
summary.observe({ method: 'GET', route: '/__empty_metrics', status_code: 404 }, 0)
}

const histogram = new Histogram({
name: 'http_request_duration_seconds',
help: 'request duration in seconds',
Expand All @@ -43,6 +48,10 @@ module.exports = fp(async function (fastify, opts) {
...opts.histogram,
})

if (zeroFill) {
histogram.zero({ method: 'GET', route: '/__empty_metrics', status_code: 404 })
}

const timers = new WeakMap()

fastify.addHook('onRequest', async (req) => {
Expand Down Expand Up @@ -73,8 +82,12 @@ module.exports = fp(async function (fastify, opts) {
...getCustomLabels(req, reply),
}

if (summaryTimer) summaryTimer(labels)
if (histogramTimer) histogramTimer(labels)
if (summaryTimer) {
summaryTimer(labels)
}
if (histogramTimer) {
histogramTimer(labels)
}
})
}, {
name: 'fastify-http-metrics'
Expand Down
102 changes: 98 additions & 4 deletions test/http-metrics.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,18 @@ test('should calculate the http request duration histogram', async (t) => {

{
const histogramCount = histogramValues.find(
({ metricName }) => metricName === 'http_request_duration_seconds_count'
({ metricName, labels: { route } }) => {
return metricName === 'http_request_duration_seconds_count' && route !== '/__empty_metrics'
}
)
assert.strictEqual(histogramCount.value, expectedMeasurements.length)
}

{
const histogramSum = histogramValues.find(
({ metricName }) => metricName === 'http_request_duration_seconds_sum'
({ metricName, labels: { route } }) => {
return metricName === 'http_request_duration_seconds_sum' && route !== '/__empty_metrics'
}
)
const value = histogramSum.value
const expectedValue = expectedMeasurements.reduce((a, b) => a + b, 0)
Expand Down Expand Up @@ -305,14 +309,14 @@ test('should calculate the http request duration histogram for injects', async (

{
const histogramCount = histogramValues.find(
({ metricName }) => metricName === 'http_request_duration_seconds_count'
({ metricName, labels: { route } }) => metricName === 'http_request_duration_seconds_count' && route !== '/__empty_metrics'
)
assert.strictEqual(histogramCount.value, expectedMeasurements.length)
}

{
const histogramSum = histogramValues.find(
({ metricName }) => metricName === 'http_request_duration_seconds_sum'
({ metricName, labels: { route } }) => metricName === 'http_request_duration_seconds_sum' && route !== '/__empty_metrics'
)
const value = histogramSum.value
const expectedValue = expectedMeasurements.reduce((a, b) => a + b, 0)
Expand All @@ -324,6 +328,8 @@ test('should calculate the http request duration histogram for injects', async (
}

for (const { metricName, labels, value } of histogramValues) {
if (labels.route === '/__empty_metrics') continue

assert.strictEqual(labels.method, 'GET')
assert.strictEqual(labels.status_code, 200)

Expand Down Expand Up @@ -446,3 +452,91 @@ test('should not throw if request timers are not found', async (t) => {
const histogramValues = histogramMetric.values
assert.strictEqual(histogramValues.length, 0)
})

test('should provide a default timer value so that the summary and histogram are not empty', async (t) => {
const app = createFastifyApp()

const registry = new Registry()
app.register(httpMetrics, { registry, zeroFill: true })

await app.listen({ port: 0 })
t.after(() => app.close())

const metrics = await registry.getMetricsAsJSON()
assert.strictEqual(metrics.length, 2)

const histogramMetric = metrics.find(
(metric) => metric.name === 'http_request_duration_seconds'
)
assert.strictEqual(histogramMetric.name, 'http_request_duration_seconds')
assert.strictEqual(histogramMetric.type, 'histogram')
assert.strictEqual(histogramMetric.help, 'request duration in seconds')
assert.strictEqual(histogramMetric.aggregator, 'sum')

const histogramValues = histogramMetric.values

{
const histogramCount = histogramValues.find(
({ metricName }) => metricName === 'http_request_duration_seconds_count'
)
assert.strictEqual(histogramCount.value, 0)
}

{
const histogramSum = histogramValues.find(
({ metricName }) => metricName === 'http_request_duration_seconds_sum'
)
const value = histogramSum.value
assert.ok(
value < 0.1
)
}

for (const { metricName, labels, value } of histogramValues) {
assert.strictEqual(labels.method, 'GET')
assert.strictEqual(labels.status_code, 404)

if (metricName !== 'http_request_duration_seconds_bucket') continue

assert.strictEqual(value, 0)
}

const summaryMetric = metrics.find(
(metric) => metric.name === 'http_request_summary_seconds'
)
assert.strictEqual(summaryMetric.name, 'http_request_summary_seconds')
assert.strictEqual(summaryMetric.type, 'summary')
assert.strictEqual(summaryMetric.help, 'request duration in seconds summary')
assert.strictEqual(summaryMetric.aggregator, 'sum')

const summaryValues = summaryMetric.values

{
const summaryCount = summaryValues.find(
({ metricName }) => metricName === 'http_request_summary_seconds_count'
)
assert.strictEqual(summaryCount.value, 1)
}

{
const summarySum = summaryValues.find(
({ metricName }) => metricName === 'http_request_summary_seconds_sum'
)
const value = summarySum.value
assert.ok(
value < 0.1
)
}

for (const { labels, value } of summaryValues) {
assert.strictEqual(labels.method, 'GET')
assert.strictEqual(labels.status_code, 404)

const quantile = labels.quantile
if (quantile === undefined) continue

assert.ok(
value < 0.1
)
}
})
Loading