@meltwater/mlabs-http
Version:
HTTP client wrapper around Got.
211 lines (184 loc) • 5.6 kB
JavaScript
import { Histogram, Gauge, Counter } from 'prom-client'
export const defaultPrefix = 'http_client_'
const sharedLabels = ['client', 'resource', 'method']
const doneLabels = ['status_code']
const defaultMetrics = [
{
name: 'requests_total',
help: 'Number of initiated requests',
type: Counter
},
{
name: 'requests_open',
help: 'Number of open requests',
type: Gauge
},
{
name: 'requests_failed_total',
help: 'Number of failed requests',
labelNames: doneLabels,
type: Counter
},
{
name: 'requests_failed_cached_total',
help: 'Number of failed requests from cache',
labelNames: doneLabels,
type: Counter
},
{
name: 'requests_retried_total',
help: 'Number of retried requests',
labelNames: doneLabels,
type: Counter
},
{
name: 'requests_completed_total',
help: 'Number of successfully completed requests',
labelNames: doneLabels,
type: Counter
},
{
name: 'requests_completed_cached_total',
help: 'Number of successfully completed requests from cache',
labelNames: doneLabels,
type: Counter
},
{
name: 'request_duration_milliseconds',
help: 'Total request time in milliseconds',
labelNames: doneLabels,
buckets: [0, 20, 50, 100, 150, 200, 300, 500, 800, 1200],
type: Histogram
}
]
export const metricNames = new Set(defaultMetrics.map(({ name }) => name))
export const collectMetrics = ({
prefix = defaultPrefix,
metricOptions = {},
register
}) => {
const registry = register
const metrics = createMetrics(metricOptions)
const prefixedMetrics = getPrefixedMetrics(metrics, prefix)
for (const prefixedMetric of prefixedMetrics) {
const {
prefixedName,
labelNames = [],
type: MetricType,
...args
} = prefixedMetric
const metric = new MetricType({
...args,
labelNames: [...sharedLabels, ...labelNames],
name: prefixedName
})
registry.registerMetric(metric)
}
}
const checkIfRegistered = (metrics) => {
for (const m of Object.values(metrics)) {
if (!m) {
throw new Error(
'To use metrics, they must be registered first with collectMetrics'
)
}
}
}
export const handleStart = ({ prefix = defaultPrefix, register, ...args }) => {
const metrics = getMetrics(register, prefix)
checkIfRegistered(metrics)
const { common } = getLabels(args)
metrics.requests_total.inc(common)
metrics.requests_open.inc(common)
}
export const handleSuccess = ({
prefix = defaultPrefix,
register,
...args
}) => {
const metrics = getMetrics(register, prefix)
checkIfRegistered(metrics)
const { success } = getLabels(args)
const { isFromCache = false } = args.data || {}
metrics.requests_completed_total.inc(success)
if (isFromCache) metrics.requests_completed_cached_total.inc(success)
}
export const handleFail = ({ prefix = defaultPrefix, register, ...args }) => {
const metrics = getMetrics(register, prefix)
checkIfRegistered(metrics)
const { fail } = getLabels(args)
const { isFromCache = false } = args.err || {}
metrics.requests_failed_total.inc(fail)
if (isFromCache) metrics.requests_failed_cached_total.inc(fail)
}
export const handleDone = ({ prefix = defaultPrefix, register, ...args }) => {
const metrics = getMetrics(register, prefix)
checkIfRegistered(metrics)
const { common } = getLabels(args)
const { err = {}, data = {} } = args
const timings = getTimings(data) || getTimings(err)
metrics.requests_open.dec(common)
if (!timings) return
const duration = timings.phases.total
if (duration !== null) {
metrics.request_duration_milliseconds.observe(common, duration)
}
}
export const handleRetry = ({ prefix = defaultPrefix, register, ...args }) => {
const metrics = getMetrics(register, prefix)
checkIfRegistered(metrics)
const { common, fail } = getLabels(args)
metrics.requests_retried_total.inc(fail)
const { err = {}, data = {} } = args
const timings = getTimings(data) || getTimings(err)
if (!timings) return
const duration = timings.phases.total
if (duration !== null) {
metrics.request_duration_milliseconds.observe(common, duration)
}
}
const getLabels = ({ name, resourceName, method, data, err }) => {
const common = {
method: method.toLowerCase(),
...(name ? { client: name } : {}),
...(resourceName ? { resource: resourceName } : {})
}
const getFail = () => {
const { statusCode } = err
return {
...common,
...(!isNil(statusCode) ? { status_code: statusCode } : {})
}
}
const getSuccess = () => {
const { statusCode } = data
return {
...common,
...(!isNil(statusCode) ? { status_code: statusCode } : {})
}
}
return {
common,
success: data ? getSuccess() : common,
fail: err ? getFail() : common
}
}
const isNil = (x) => x == null
const getPrefixedMetrics = (metrics, prefix) =>
metrics.map(({ name, ...rest }) => ({
prefixedName: `${prefix}${name}`,
name,
...rest
}))
const getMetrics = (register, prefix) => {
const getMetric = ({ name, prefixedName }) => ({
[name]: register.getSingleMetric(prefixedName)
})
const prefixedMetrics = getPrefixedMetrics(defaultMetrics, prefix)
const singleMetrics = prefixedMetrics.map(getMetric)
return Object.assign(...singleMetrics)
}
const createMetrics = (options) =>
defaultMetrics.map((metric) => ({ ...metric, ...options[metric.name] }))
const has = (obj, k) => Object.prototype.hasOwnProperty.call(obj, k)
const getTimings = (x) => x && has(x, 'timings') && x.timings