Repository: louislam/uptime-kuma
File: server/monitor-types/rabbitmq.js (JavaScript)

Code before:

    /**
     * @inheritdoc
     */
    async check(monitor, heartbeat, server) {
        let baseUrls = [];
        try {
            baseUrls = JSON.parse(monitor.rabbitmqNodes);
        } catch (error) {
            throw new Error("Invalid RabbitMQ Nodes");
        }

        if (baseUrls.length === 0) {
            throw new Error("No RabbitMQ nodes configured");
        }

        const errors = [];

        for (let i = 0; i < baseUrls.length; i++) {
            const baseUrl = baseUrls[i];
            const nodeIndex = i + 1;
            const totalNodes = baseUrls.length;

            try {
                await this.checkSingleNode(monitor, heartbeat, baseUrl, nodeIndex, totalNodes);
                // If checkSingleNode succeeds, heartbeat is set to UP and we can return
                return;
            } catch (error) {
                const errorMsg = `Node ${nodeIndex}: ${error.message}`;
                log.warn("monitor", `[${monitor.name}] ${errorMsg}`);
                errors.push(errorMsg);
            }
        }

        // If we reach here, all nodes failed
        const consolidatedError = errors.length > 1 
            ? `All ${errors.length} nodes failed: ${errors.join("; ")}`
            : errors[0] || "All RabbitMQ nodes are unavailable";
        log.error("monitor", `[${monitor.name}] ${consolidatedError}`);
        throw new Error(consolidatedError);
    }

    /**
     * Check a single RabbitMQ node
     * @param {object} monitor Monitor configuration
     * @param {object} heartbeat Heartbeat object to update
     * @param {string} baseUrl Base URL of the RabbitMQ node
     * @param {number} nodeIndex Index of the current node (1-based for logging)
     * @param {number} totalNodes Total number of nodes configured
     * @returns {Promise<void>}
     * @throws {Error} If the node check fails

Review comment (bug):
Please set it to UP here instead of in `checkSingleNode`, this way it is also simpler to test and reason about.

Note that we don't need to log that this node is UP at info level (this is already done outside this function), maybe at debug level, not sure.

Code after:

    /**
     * @inheritdoc
     */
    async check(monitor, heartbeat, server) {
        let baseUrls = [];
        try {
            baseUrls = JSON.parse(monitor.rabbitmqNodes);
        } catch (error) {
            throw new Error("Invalid RabbitMQ Nodes");
        }

        if (baseUrls.length === 0) {
            throw new Error("No RabbitMQ nodes configured");
        }

        const errors = [];

        for (let i = 0; i < baseUrls.length; i++) {
            const baseUrl = baseUrls[i];
            const nodeIndex = i + 1;

            try {
                await this.checkSingleNode(monitor, baseUrl, `${nodeIndex}/${baseUrls.length}`);
                // If checkSingleNode succeeds (doesn't throw), set heartbeat to UP
                heartbeat.status = UP;
                heartbeat.msg = baseUrls.length === 1 ? "Node is reachable and there are no alerts in the cluster" : `One of the ${baseUrls.length} nodes is reachable and there are no alerts in the cluster`;
                return;
            } catch (error) {
                log.warn(this.name, `Node ${nodeIndex}: ${error.message}`);
                errors.push(`Node ${nodeIndex}: ${error.message}`);
            }
        }

        // If we reach here, all nodes failed
        throw new Error(`All ${errors.length} nodes failed because ${errors.join("; ")}`);
    }

    /**
     * Check a single RabbitMQ node
     * @param {object} monitor Monitor configuration
     * @param {string} baseUrl Base URL of the RabbitMQ node
     * @param {string} nodeInfo Node index info for logging (e.g., "1/3")
     * @returns {Promise<void>}
     * @throws {Error} If the node check fails
     */
    async checkSingleNode(monitor, baseUrl, nodeInfo) {
        // Without a trailing slash, path in baseUrl will be removed. https://example.com/api -> https://example.com
        let normalizedUrl = baseUrl;
        if (!normalizedUrl.endsWith("/")) {
            normalizedUrl += "/";