import type { Context } from 'hono' import type { CounterConfiguration, HistogramConfiguration, Metric } from 'prom-client' import { Counter, Histogram, type Registry } from 'prom-client' export type MetricOptions = { disabled?: boolean; customLabels?: Record string>; } & ( ({ type: 'counter' } & CounterConfiguration) | ({ type: 'histogram' } & HistogramConfiguration) ) const standardMetrics = { requestDuration: { type: 'histogram', name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'status', 'ok', 'route'], // OpenTelemetry recommendation for histogram buckets of http request duration: // https://opentelemetry.io/docs/specs/semconv/http/http-metrics/#metric-httpserverrequestduration buckets: [ 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10 ], }, requestsTotal: { type: 'counter', name: 'http_requests_total', help: 'Total number of HTTP requests', labelNames: ['method', 'status', 'ok', 'route'], }, } satisfies Record export type MetricName = keyof typeof standardMetrics export type CustomMetricsOptions = { [Name in MetricName]?: Partial> } type CreatedMetrics = { [Name in MetricName]: (typeof standardMetrics)[Name]['type'] extends 'counter' ? Counter : Histogram } const getMetricConstructor = (type: MetricOptions['type']) => ({ counter: Counter, histogram: Histogram, })[type] export const createStandardMetrics = ({ registry, prefix = '', customOptions, } : { registry: Registry; prefix?: string; customOptions?: CustomMetricsOptions; }) => { const createdMetrics: Record = {} for (const [metric, options] of Object.entries(standardMetrics)) { const opts: MetricOptions = { ...options, ...customOptions?.[metric as MetricName], } if (opts.disabled) { continue } const MetricConstructor = getMetricConstructor(opts.type) createdMetrics[metric] = new MetricConstructor({ ...(opts as object), name: `${prefix}${opts.name}`, help: opts.help, registers: [...opts.registers ?? [], registry], labelNames: [...opts.labelNames ?? [], ...Object.keys(opts.customLabels ?? {})], ...(opts.type === 'histogram' && opts.buckets && { buckets: opts.buckets, }), }) } return createdMetrics as CreatedMetrics }