import type { Context } from 'hono' import { Counter, Histogram } from 'prom-client' import type { CounterConfiguration, HistogramConfiguration, Metric, Registry } from 'prom-client' interface CounterOptions extends CounterConfiguration { type: 'counter' disabled?: boolean customLabels?: Record string> } interface HistogramOptions extends HistogramConfiguration { type: 'histogram' disabled?: boolean customLabels?: Record string> } export type MetricOptions = CounterOptions | HistogramOptions const standardMetrics: { requestDuration: HistogramOptions requestsTotal: CounterOptions } = { 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'], }, } 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 }