Mercurial > prosody-modules
view mod_prometheus/mod_prometheus.lua @ 5405:c7a5caad28ef
mod_http_oauth2: Enforce response type encoded in client_id
The client promises to only use this response type, so we should hold
them to that.
This makes it fail earlier if the response type is disabled or the
client is trying to use one that it promised not to use. Better than
failing after login and consent.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Tue, 02 May 2023 16:31:25 +0200 |
parents | c406e4bf7ee5 |
children |
line wrap: on
line source
-- Log statistics to Prometheus -- -- Copyright (C) 2014 Daurnimator -- Copyright (C) 2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> -- Copyright (C) 2021 Jonas Schäfer <jonas@zombofant.net> -- -- This module is MIT/X11 licensed. module:set_global(); local tostring = tostring; local t_insert = table.insert; local t_concat = table.concat; local socket = require "socket"; local statsman = require "core.statsmanager"; local get_stats = statsman.get_stats; local get_metric_registry = statsman.get_metric_registry; local collect = statsman.collect; local function escape(text) return text:gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n"); end local function escape_name(name) return name:gsub("/", "__"):gsub("[^A-Za-z0-9_]", "_"):gsub("^[^A-Za-z_]", "_%1"); end local function get_timestamp() -- Using LuaSocket for that because os.time() only has second precision. return math.floor(socket.gettime() * 1000); end local function repr_help(metric, docstring) docstring = docstring:gsub("\\", "\\\\"):gsub("\n", "\\n"); return "# HELP "..escape_name(metric).." "..docstring.."\n"; end local function repr_unit(metric, unit) if not unit then unit = "" else unit = unit:gsub("\\", "\\\\"):gsub("\n", "\\n"); end return "# UNIT "..escape_name(metric).." "..unit.."\n"; end -- local allowed_types = { counter = true, gauge = true, histogram = true, summary = true, untyped = true }; -- local allowed_types = { "counter", "gauge", "histogram", "summary", "untyped" }; local function repr_type(metric, type_) -- if not allowed_types:contains(type_) then -- return; -- end return "# TYPE "..escape_name(metric).." "..type_.."\n"; end local function repr_label(key, value) return key.."=\""..escape(value).."\""; end local function repr_labels(labelkeys, labelvalues, extra_labels) local values = {} if labelkeys then for i, key in ipairs(labelkeys) do local value = labelvalues[i] t_insert(values, repr_label(escape_name(key), escape(value))); end end if extra_labels then for key, value in pairs(extra_labels) do t_insert(values, repr_label(escape_name(key), escape(value))); end end if #values == 0 then return ""; end return "{"..t_concat(values, ",").."}"; end local function repr_sample(metric, labelkeys, labelvalues, extra_labels, value) return escape_name(metric)..repr_labels(labelkeys, labelvalues, extra_labels).." "..string.format("%.17g", value).."\n"; end local get_metrics; if statsman.get_metric_registry then module:log("debug", "detected OpenMetrics statsmanager") -- Prosody 0.12+ with OpenMetrics function get_metrics(event) local response = event.response; response.headers.content_type = "application/openmetrics-text; version=0.0.4"; if collect then -- Ensure to get up-to-date samples when running in manual mode collect() end local registry = get_metric_registry() if registry == nil then response.headers.content_type = "text/plain; charset=utf-8" response.status_code = 404 return "No statistics provider configured\n" end local answer = {}; for metric_family_name, metric_family in pairs(registry:get_metric_families()) do t_insert(answer, repr_help(metric_family_name, metric_family.description)) t_insert(answer, repr_unit(metric_family_name, metric_family.unit)) t_insert(answer, repr_type(metric_family_name, metric_family.type_)) for labelset, metric in metric_family:iter_metrics() do for suffix, extra_labels, value in metric:iter_samples() do t_insert(answer, repr_sample(metric_family_name..suffix, metric_family.label_keys, labelset, extra_labels, value)) end end end t_insert(answer, "# EOF\n") return t_concat(answer, ""); end else module:log("debug", "detected pre-OpenMetrics statsmanager") -- Pre-OpenMetrics local allowed_extras = { min = true, max = true, average = true }; local function insert_extras(data, key, name, timestamp, extra) if not extra then return false; end local has_extra = false; for extra_name in pairs(allowed_extras) do if extra[extra_name] then local field = { value = extra[extra_name], labels = { ["type"] = name, field = extra_name, }, typ = "gauge"; timestamp = timestamp, }; t_insert(data[key], field); has_extra = true; end end return has_extra; end local function parse_stats() local timestamp = tostring(get_timestamp()); local data = {}; local stats, changed_only, extras = get_stats(); for stat, value in pairs(stats) do -- module:log("debug", "changed_stats[%q] = %s", stat, tostring(value)); local extra = extras[stat]; local host, sect, name, typ = stat:match("^/([^/]+)/([^/]+)/(.+):(%a+)$"); if host == nil then sect, name, typ = stat:match("^([^.]+)%.(.+):(%a+)$"); elseif host == "*" then host = nil; end if sect:find("^mod_measure_.") then sect = sect:sub(13); elseif sect:find("^mod_statistics_.") then sect = sect:sub(16); end local key = escape_name("prosody_"..sect); local field = { value = value, labels = { ["type"] = name}, -- TODO: Use the other types where it makes sense. typ = (typ == "rate" and "counter" or "gauge"), timestamp = timestamp, }; if host then field.labels.host = host; end if data[key] == nil then data[key] = {}; end if not insert_extras(data, key, name, timestamp, extra) then t_insert(data[key], field); end end return data; end function get_metrics(event) local response = event.response; response.headers.content_type = "text/plain; version=0.0.4"; if statsman.collect then statsman.collect() end local answer = {}; for key, fields in pairs(parse_stats()) do t_insert(answer, repr_help(key, "")); t_insert(answer, repr_type(key, fields[1].typ)); for _, field in pairs(fields) do t_insert(answer, repr_sample(key, nil, nil, field.labels, field.value, field.timestamp)); end end return t_concat(answer, ""); end end function module.add_host(module) module:depends "http"; module:provides("http", { default_path = "metrics"; route = { GET = get_metrics; }; }); end