Repository: louislam/uptime-kuma
File: server/prometheus.js (JavaScript)

Code before:
            monitor_name: monitor.name,
            monitor_type: monitor.type,
            monitor_url: monitor.url,
            monitor_hostname: monitor.hostname,
            monitor_port: monitor.port,
            monitor_tags: filteredValidAsciiTags
        };
    }

    /**
     * Filter tags to remove the ones that contain non-ASCII characters
     * See https://github.com/louislam/uptime-kuma/pull/4704#issuecomment-2366524692
     * @param {Array<{name: string, value:?string}>} tags The tags to filter
     * @returns {string[]} The filtered tags
     */
    filterValidAsciiTags(tags) {
        const asciiRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
        return tags.reduce((filteredTags, tag) => {
            if (asciiRegex.test(tag.name)) {
                if (tag.value !== "" && asciiRegex.test(tag.value)) {
                    filteredTags.push(`${tag.name}:${tag.value}`);
                } else {
                    filteredTags.push(tag.name);
                }
            }
            return filteredTags;
        }, []);
    }

    /**
     * Update the metrics page
     * @param {object} heartbeat Heartbeat details
     * @param {object} tlsInfo TLS details
     * @returns {void}
     */
    update(heartbeat, tlsInfo) {
        if (typeof tlsInfo !== "undefined") {
            try {
                let isValid;
                if (tlsInfo.valid === true) {
                    isValid = 1;
                } else {
                    isValid = 0;
                }
                monitorCertIsValid.set(this.monitorLabelValues, isValid);
            } catch (e) {
                log.error("prometheus", "Caught error");
                log.error("prometheus", e);
            }

            try {

Review comment (suggestion):
Instead of filtering, please just drop the non-matching characters after building the tag.

Current algorithm also does not work. `${tag.name}:${tag.value}` is not a valid tag.

Code after:
            "monitor_hostname",
            "monitor_port",
        ];

        monitorCertDaysRemaining = new PrometheusClient.Gauge({
            name: "monitor_cert_days_remaining",
            help: "The number of days remaining until the certificate expires",
            labelNames: commonLabels
        });

        monitorCertIsValid = new PrometheusClient.Gauge({
            name: "monitor_cert_is_valid",
            help: "Is the certificate still valid? (1 = Yes, 0= No)",
            labelNames: commonLabels
        });

        monitorResponseTime = new PrometheusClient.Gauge({
            name: "monitor_response_time",
            help: "Monitor Response Time (ms)",
            labelNames: commonLabels
        });

        monitorStatus = new PrometheusClient.Gauge({
            name: "monitor_status",
            help: "Monitor Status (1 = UP, 0= DOWN, 2= PENDING, 3= MAINTENANCE)",
            labelNames: commonLabels
        });
    }

    /**
     * Sanitize a string to ensure it can be used as a Prometheus label or value.
     * See https://github.com/louislam/uptime-kuma/pull/4704#issuecomment-2366524692
     * @param {string} text The text to sanitize
     * @returns {string} The sanitized text
     */
    static sanitizeForPrometheus(text) {
        text = text.replace(/[^a-zA-Z0-9_]/g, "");
        text = text.replace(/^[^a-zA-Z_]+/, "");
        return text;
    }

    /**
     * Map the tags value to valid labels used in Prometheus. Sanitize them in the process.
     * @param {Array<{name: string, value:?string}>} tags The tags to map
     * @returns {object} The mapped tags, usable as labels
     */
    mapTagsToLabels(tags) {
        let mappedTags = {};
        tags.forEach((tag) => {
            let sanitizedTag = Prometheus.sanitizeForPrometheus(tag.name);
            if (sanitizedTag === "") {