diff --git a/include/MySQLFFTO.hpp b/include/MySQLFFTO.hpp index fa513a520..943f14e23 100644 --- a/include/MySQLFFTO.hpp +++ b/include/MySQLFFTO.hpp @@ -108,6 +108,13 @@ private: * @param rows_sent Number of rows returned. */ void report_query_stats(const std::string& query, unsigned long long duration_us, uint64_t affected_rows = 0, uint64_t rows_sent = 0); + + /** + * @brief Records an error from a MySQL ERR packet into stats_mysql_errors. + * @param data Pointer to the ERR packet payload (starting with 0xFF). + * @param len Length of the payload. + */ + void report_error(const unsigned char* data, size_t len); }; #endif // MYSQL_FFTO_HPP diff --git a/lib/MySQLFFTO.cpp b/lib/MySQLFFTO.cpp index 8d199ec56..df42f9c4b 100644 --- a/lib/MySQLFFTO.cpp +++ b/lib/MySQLFFTO.cpp @@ -7,6 +7,7 @@ #include "MySQL_Protocol.h" #include "MySQL_Variables.h" #include "MySQLFFTO.hpp" +#include "MySQLProtocolUtils.h" #ifndef SPOOKYV2 #include "SpookyV2.h" #define SPOOKYV2 @@ -17,6 +18,7 @@ #include extern class MySQL_Query_Processor* GloMyQPro; +extern MySQL_HostGroups_Manager* MyHGM; /** * @brief Helper function to read a length-encoded integer from a MySQL packet buffer. @@ -187,6 +189,7 @@ void MySQLFFTO::process_server_packet(const unsigned char* data, size_t len) { m_state = IDLE; m_pending_prepare_query.clear(); } else if (first_byte == 0xFF) { + report_error(data, len); m_state = IDLE; m_pending_prepare_query.clear(); } @@ -201,6 +204,7 @@ void MySQLFFTO::process_server_packet(const unsigned char* data, size_t len) { } else if (first_byte == 0xFF) { // ERR unsigned long long duration = monotonic_time() - m_query_start_time; report_query_stats(m_current_query, duration); + report_error(data, len); m_state = IDLE; clear_active_query(); } else if (first_byte == 0xFE && len < 9) { // EOF @@ -217,6 +221,7 @@ void MySQLFFTO::process_server_packet(const unsigned char* data, size_t len) { } else if (first_byte == 0xFF) { // ERR while reading column metadata unsigned long long duration = monotonic_time() - m_query_start_time; report_query_stats(m_current_query, duration); + report_error(data, len); m_state = IDLE; clear_active_query(); } @@ -234,6 +239,7 @@ void MySQLFFTO::process_server_packet(const unsigned char* data, size_t len) { } else if (first_byte == 0xFF) { // ERR unsigned long long duration = monotonic_time() - m_query_start_time; report_query_stats(m_current_query, duration, 0, m_rows_sent); + report_error(data, len); m_state = IDLE; clear_active_query(); } else { @@ -280,6 +286,47 @@ void MySQLFFTO::report_query_stats(const std::string& query, unsigned long long if (fst_cmnt) free(fst_cmnt); } +void MySQLFFTO::report_error(const unsigned char* data, size_t len) { + if (!m_session || !MyHGM) return; + if (!m_session->client_myds || !m_session->client_myds->myconn || + !m_session->client_myds->myconn->userinfo) return; + + uint16_t err_no = 0; + const char* errmsg = nullptr; + size_t errmsg_len = 0; + if (!mysql_parse_err_packet(data, len, &err_no, &errmsg, &errmsg_len)) return; + + auto* ui = m_session->client_myds->myconn->userinfo; + if (!ui->username || !ui->schemaname) return; + + // Build a null-terminated copy of the error message + std::string msg(errmsg ? errmsg : "", errmsg_len); + + // Get backend server info if available + char* hostname = (char*)""; + int port = 0; + int hostgroup = m_session->current_hostgroup; + if (m_session->mybe && m_session->mybe->server_myds && + m_session->mybe->server_myds->myconn && + m_session->mybe->server_myds->myconn->parent) { + auto* parent = m_session->mybe->server_myds->myconn->parent; + hostname = parent->address; + port = parent->port; + hostgroup = parent->myhgc->hid; + } + + char* client_addr = (char*)"unknown"; + if (m_session->client_myds->addr.addr) { + client_addr = m_session->client_myds->addr.addr; + } + + MyHGM->add_mysql_errors( + hostgroup, hostname, port, + ui->username, client_addr, ui->schemaname, + err_no, (char*)msg.c_str() + ); +} + std::size_t MySQLFFTO::get_buffered_size() const { return m_client_buffer.size() + m_server_buffer.size(); }