将 C++ 程序异常信息存入 MySQL 数据库的完整方案
在 C++ 程序中捕获异常并将异常信息存入 MySQL 数据库是一个常见的需求,特别是在需要长期记录和分析程序错误场景时。下面我将详细介绍几种实现方法,从基础到高级逐步讲解。
一、基础实现方案
1.1 使用MySQL C API直接存储异常
这种方法适合简单的 C++ 项目,直接使用 MySQL C API 连接数据库并插入异常信息。
#include <mysql/mysql.h>
#include <iostream>
#include <stdexcept>
// 全局数据库连接
MYSQL *conn = nullptr;
void init_db_connection() {
conn = mysql_init(nullptr);
if (!conn) {
throw std::runtime_error("MySQL initialization failed");
}
if (!mysql_real_connect(conn, "localhost", "user", "password",
"exception_db", 0, nullptr, 0)) {
std::string err = "Connection error: ";
err += mysql_error(conn);
mysql_close(conn);
throw std::runtime_error(err);
}
}
void log_exception_to_db(const std::string& exception_type,
const std::string& message,
const std::string& stack_trace,
const std::string& timestamp) {
std::string query = "INSERT INTO exceptions (type, message, stack_trace, timestamp) VALUES ('" +
exception_type + "', '" + message + "', '" + stack_trace + "', '" + timestamp + "')";
if (mysql_query(conn, query.c_str())) {
std::cerr << "Error logging exception: " << mysql_error(conn) << std::endl;
}
}
int main() {
try {
init_db_connection();
// 程序主逻辑
throw std::runtime_error("Sample exception for testing");
} catch (const std::exception& e) {
// 获取当前时间
time_t now = time(nullptr);
char timestamp[20];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", localtime(&now));
// 记录异常到数据库
log_exception_to_db(typeid(e).name(), e.what(), "stack trace here", timestamp);
std::cerr << "Exception caught: " << e.what() << std::endl;
}
if (conn) mysql_close(conn);
return 0;
}注意事项:
需要先创建异常表:
CREATE TABLE exceptions(id INT AUTO_INCREMENT PRIMARY KEY, type VARCHAR(255), message TEXT, stack_trace TEXT, timestamp DATETIME);此方法存在 SQL 注入风险,实际应用中应使用参数化查询
二、使用MySQL Connector/C++的高级方案
MySQL 官方提供的 Connector/C++ 库提供了更安全、面向对象的接口。
2.1 使用预编译语句防止SQL注入
#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/prepared_statement.h>
#include <iostream>
#include <ctime>
std::unique_ptr<sql::Connection> get_db_connection() {
try {
sql::Driver* driver = get_driver_instance();
std::unique_ptr<sql::Connection> conn(
driver->connect("tcp://127.0.0.1:3306", "user", "password"));
conn->setSchema("exception_db");
return conn;
} catch (sql::SQLException& e) {
std::cerr << "DB Connection Error: " << e.what() << std::endl;
throw;
}
}
void log_exception(const std::exception& e, sql::Connection* conn) {
try {
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement(
"INSERT INTO exceptions(type, message, timestamp) VALUES (?, ?, ?)"));
// 获取当前时间戳
std::time_t now = std::time(nullptr);
std::tm* tm = std::localtime(&now);
char timestamp[20];
std::strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm);
pstmt->setString(1, typeid(e).name());
pstmt->setString(2, e.what());
pstmt->setString(3, timestamp);
pstmt->executeUpdate();
} catch (sql::SQLException& db_err) {
std::cerr << "Failed to log exception: " << db_err.what() << std::endl;
}
}
int main() {
try {
auto conn = get_db_connection();
// 程序主逻辑
throw std::runtime_error("Test exception with Connector/C++");
} catch (const std::exception& e) {
try {
auto conn = get_db_connection();
log_exception(e, conn.get());
} catch (...) {
std::cerr << "Failed to log exception to DB" << std::endl;
}
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}优势:
使用预编译语句防止 SQL 注入
面向对象接口更易用
更好的异常处理机制
三、结合日志系统的综合方案
3.1 使用日志库+数据库的组合方案
对于生产环境,建议结合日志库 (如 spdlog) 和数据库存储:
#include <spdlog/spdlog.h>
#include <cppconn/driver.h>
#include <memory>
class DatabaseLogger {
public:
DatabaseLogger() {
try {
driver_ = get_driver_instance();
conn_ = std::unique_ptr<sql::Connection>(
driver_->connect("tcp://127.0.0.1:3306", "user", "password"));
conn_->setSchema("logging_db");
// 创建表如果不存在
std::unique_ptr<sql::Statement> stmt(conn_->createStatement());
stmt->execute("CREATE TABLE IF NOT EXISTS exception_logs ("
"id INT AUTO_INCREMENT PRIMARY KEY,"
"timestamp DATETIME,"
"level VARCHAR(20),"
"type VARCHAR(255),"
"message TEXT,"
"context TEXT)");
} catch (sql::SQLException& e) {
spdlog::error("DatabaseLogger init failed: {}", e.what());
throw;
}
}
void log_exception(const std::exception& e, const std::string& context = "") {
try {
std::unique_ptr<sql::PreparedStatement> pstmt(
conn_->prepareStatement(
"INSERT INTO exception_logs(timestamp, level, type, message, context) "
"VALUES (NOW(), 'error', ?, ?, ?)"));
pstmt->setString(1, typeid(e).name());
pstmt->setString(2, e.what());
pstmt->setString(3, context);
pstmt->executeUpdate();
// 同时记录到文件日志
spdlog::error("Exception occurred: {} - Context: {}", e.what(), context);
} catch (sql::SQLException& db_err) {
spdlog::error("Failed to log exception to DB: {}", db_err.what());
}
}
private:
sql::Driver* driver_;
std::unique_ptr<sql::Connection> conn_;
};
int main() {
// 初始化日志系统
spdlog::set_level(spdlog::level::debug);
auto file_logger = spdlog::basic_logger_mt("file_logger", "logs/exceptions.log");
spdlog::set_default_logger(file_logger);
try {
DatabaseLogger db_logger;
// 程序主逻辑
throw std::runtime_error("Comprehensive exception test");
} catch (const std::exception& e) {
spdlog::error("Main exception caught: {}", e.what());
// 获取堆栈信息 (简化版)
std::string stack_trace = "Stack trace not available";
#ifdef _WIN32
// Windows下获取堆栈的方法
#else
// Linux下获取堆栈的方法
#endif
try {
DatabaseLogger db_logger;
db_logger.log_exception(e, stack_trace);
} catch (...) {
spdlog::critical("Failed to initialize database logger");
}
}
return 0;
}特点:
数据库和文件双日志记录
包含更多上下文信息
使用现代日志库 spdlog
可扩展添加堆栈跟踪信息
四、异常处理最佳实践
4.1 异常分层处理
class DatabaseException : public std::runtime_error {
public:
DatabaseException(const std::string& msg, int error_code)
: std::runtime_error(msg), error_code_(error_code) {}
int get_error_code() const { return error_code_; }
private:
int error_code_;
};
void log_custom_exception(const DatabaseException& e) {
try {
sql::Driver* driver = get_driver_instance();
std::unique_ptr<sql::Connection> conn(
driver->connect("tcp://127.0.0.1:3306", "user", "password"));
conn->setSchema("exception_db");
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement(
"INSERT INTO custom_exceptions(type, message, code, timestamp) "
"VALUES (?, ?, ?, NOW())"));
pstmt->setString(1, typeid(e).name());
pstmt->setString(2, e.what());
pstmt->setInt(3, e.get_error_code());
pstmt->executeUpdate();
} catch (sql::SQLException& db_err) {
// 如果数据库记录失败,至少记录到本地日志
spdlog::error("Failed to log custom exception: {}", db_err.what());
}
}4.2 使用RAII管理资源
class DBConnectionRAII {
public:
DBConnectionRAII(const std::string& host, const std::string& user,
const std::string& pass, const std::string& db)
: conn_(nullptr) {
conn_ = get_driver_instance()->connect(host, user, pass);
conn_->setSchema(db);
}
~DBConnectionRAII() {
if (conn_) {
try {
conn_->close();
} catch (sql::SQLException& e) {
spdlog::error("Failed to close DB connection: {}", e.what());
}
delete conn_;
}
}
sql::Connection* get() { return conn_; }
private:
sql::Connection* conn_;
};五、性能优化与安全考虑
5.1 批量插入与连接池
对于高频异常场景,考虑使用连接池和批量插入:
class ExceptionLogger {
public:
ExceptionLogger(size_t pool_size = 5) {
for (size_t i = 0; i < pool_size; ++i) {
auto conn = std::make_shared<sql::Connection>(
get_driver_instance()->connect("tcp://127.0.0.1:3306", "user", "password"));
conn->setSchema("exception_db");
conn_pool_.push(conn);
}
}
void batch_log(const std::vector<std::exception_ptr>& exceptions) {
auto conn = get_connection();
try {
conn->setAutoCommit(false);
std::unique_ptr<sql::PreparedStatement> pstmt(
conn->prepareStatement(
"INSERT INTO batched_exceptions(type, message, timestamp) "
"VALUES (?, ?, NOW())"));
for (auto& ex_ptr : exceptions) {
try {
std::rethrow_exception(ex_ptr);
} catch (const std::exception& e) {
pstmt->setString(1, typeid(e).name());
pstmt->setString(2, e.what());
pstmt->addBatch();
}
}
pstmt->executeBatch();
conn->commit();
} catch (...) {
conn->rollback();
throw;
} finally {
release_connection(conn);
}
}
private:
std::shared_ptr<sql::Connection> get_connection() {
std::lock_guard<std::mutex> lock(pool_mutex_);
if (conn_pool_.empty()) {
throw std::runtime_error("Connection pool exhausted");
}
auto conn = conn_pool_.front();
conn_pool_.pop();
return conn;
}
void release_connection(std::shared_ptr<sql::Connection> conn) {
std::lock_guard<std::mutex> lock(pool_mutex_);
conn_pool_.push(conn);
}
std::queue<std::shared_ptr<sql::Connection>> conn_pool_;
std::mutex pool_mutex_;
};5.2 安全注意事项
敏感信息处理:异常信息可能包含敏感数据,应进行脱敏处理
防 SQL 注入:始终使用参数化查询
错误处理:数据库记录失败时应有备用方案(如本地日志)
连接安全:使用 SSL 加密数据库连接
权限控制:数据库用户应只有必要的最小权限
六、完整示例项目结构
exception_logging_system/
├── include/
│ ├── database_logger.h # 数据库日志接口
│ ├── exception_handler.h # 异常处理核心逻辑
│ └── utilities.h # 辅助函数(如时间处理)
├── src/
│ ├── database_logger.cpp # 数据库日志实现
│ ├── exception_handler.cpp # 异常处理实现
│ ├── main.cpp # 应用入口
│ └── CMakeLists.txt # 构建配置
├── third_party/ # 第三方库(如mysql-connector)
└── tests/ # 单元测试构建建议:
使用 CMake 管理项目
将 MySQL Connector/C++ 作为依赖项
添加 spdlog 或其他日志库
七、扩展功能思路
异常分析面板:基于数据库中的异常数据开发 Web 分析界面
自动警报系统:对特定类型或频率的异常触发警报
机器学习分类:对异常自动分类和优先级排序
分布式收集:在微服务架构中集中收集所有服务的异常
性能监控集成:将异常与性能指标关联分析
通过以上方案,您可以构建一个健壮的 C++ 异常日志系统,将异常信息可靠地存入 MySQL 数据库,为后续的分析和调试提供完整的历史记录。根据项目规模和需求,可以选择从简单到复杂的不同实现方案。