UNPKG

@webda/core

Version:

Expose API with Lambda

160 lines 6.64 kB
import * as http from "http"; import { collectDefaultMetrics, Counter, Gauge, Histogram, register } from "prom-client"; import { Service, ServiceParameters } from "./service.js"; export class PrometheusParameters extends ServiceParameters { constructor(params) { super(params); this.url ?? (this.url = "/metrics"); this.labels ?? (this.labels = {}); this.includeNodeMetrics ?? (this.includeNodeMetrics = true); this.includeRequestMetrics ?? (this.includeRequestMetrics = true); this.prefix ?? (this.prefix = ""); } } /** * This service expose metrics to Prometheus * * It can use the same server or a dedicated http server * We do not currently support push-gateway * * @WebdaModda */ export class PrometheusService extends Service { /** * * @param params * @returns */ loadParameters(params) { return new PrometheusParameters(params); } /** * @override */ initMetrics() { if (this.parameters.includeRequestMetrics) { this.metrics = { http_request_total: register.getSingleMetric(`${this.parameters.prefix}http_request_total`) || new Counter({ name: `${this.parameters.prefix}http_request_total`, help: "metric_help", labelNames: ["method", "statuscode", "handler"] }), http_request_in_flight: register.getSingleMetric(`${this.parameters.prefix}http_request_in_flight`) || new Gauge({ name: `${this.parameters.prefix}http_request_in_flight`, help: "in flight" }), http_request_duration_milliseconds: register.getSingleMetric(`${this.parameters.prefix}http_request_duration_milliseconds`) || new Histogram({ name: `${this.parameters.prefix}http_request_duration_milliseconds`, labelNames: ["method", "statuscode", "handler"], help: "ms" }), http_request_sizes: register.getSingleMetric(`${this.parameters.prefix}http_request_sizes`) || new Histogram({ name: `${this.parameters.prefix}http_request_sizes`, labelNames: ["method", "statuscode", "handler"], help: "o" }), http_response_sizes: register.getSingleMetric(`${this.parameters.prefix}http_response_sizes`) || new Histogram({ name: `${this.parameters.prefix}http_response_sizes`, labelNames: ["method", "statuscode", "handler"], help: "o" }) }; } if (this.parameters.includeNodeMetrics && !PrometheusService.nodeMetricsRegistered) { PrometheusService.nodeMetricsRegistered = true; collectDefaultMetrics({ register, labels: this.parameters.labels, prefix: this.parameters.prefix }); } } /** * Close the http server if exists */ async stop() { this.http?.close(); this.http = undefined; } /** * * @returns */ resolve() { super.resolve(); register.setDefaultLabels(this.parameters.labels); if (!this.parameters.portNumber) { this.addRoute(this.parameters.url || "/metrics", ["GET"], this.serveMetrics); } else { this.log("INFO", `Listening for prometheus scraper on ${this.parameters.bind || ""}:${this.parameters.portNumber}`); this.http ?? (this.http = http .createServer(async (req, res) => { if (req.method === "GET" && req.url === this.parameters.url) { res.writeHead(200, { "Content-Type": register.contentType }); res.write(await register.metrics()); } else { res.writeHead(404); } res.end(); }) .listen(this.parameters.portNumber, this.parameters.bind)); } // Only register on events if we need to get metrics if (this.parameters.includeRequestMetrics) { this.getWebda().on("Webda.Request", ({ context }) => { if (context.getHttpContext().getRelativeUri() === this.parameters.url) { return; } this.metrics.http_request_in_flight.inc(); context.setExtension("prometheus", { timer: this.metrics.http_request_duration_milliseconds.startTimer() }); }); this.getWebda().on("Webda.Result", ({ context }) => { if (context.getHttpContext().getRelativeUri() === this.parameters.url) { return; } const staticLabels = this.parameters.labels || {}; const labels = { handler: context.getHttpContext().getRelativeUri(), method: context.getHttpContext().getMethod(), statuscode: context.statusCode, ...staticLabels }; context .getExtension("prometheus") .timer(this.parameters.partitionHistogram ? labels : staticLabels); this.metrics.http_request_total.inc(labels, 1); if (["PUT", "POST", "PATCH"].includes(context.getHttpContext().getMethod())) { this.metrics.http_request_sizes.observe(this.parameters.partitionHistogram ? labels : staticLabels, Number.parseInt(context.getHttpContext().getHeaders()["content-length"] || "0")); this.metrics.http_response_sizes.observe(this.parameters.partitionHistogram ? labels : staticLabels, Number.parseInt(context.getResponseHeaders["content-length"] || "0")); } this.metrics.http_request_in_flight.dec(staticLabels, 1); }); } return this; } /** * Serve the metrics in a webda context * @param ctx */ async serveMetrics(ctx) { ctx.write(await register.metrics()); } } /** * Metrics registered */ PrometheusService.requestMetricsRegistered = false; /** * Metrics registered */ PrometheusService.nodeMetricsRegistered = false; //# sourceMappingURL=prometheus.js.map