异常捕获及日志记录
前言
任何软件及程序都离不开异常机制的处理,如果程序对异常不进行处理则会造成程序奔溃退出。
一、Qt程序异常崩溃退出
当程序发生异常导致崩溃时,操作系统会运行以下两个函数以进行程序退出的处理:
1. std::terminate():std::terminate() 函数是在 C++
标准中定义的函数,用于终止当前正在运行的程序。当程序中出现无法处理的异常或其他严重错误时,系统会调用 std::terminate()
来结束程序的执行。
2. qFatal():qFatal()是Qt 框架提供的一个函数,也用于终止程序的执行。与 std::terminate()
类似,qFatal()用于处理无法恢复的错误或异常情况。
在调用这两个函数后,程序将立即退出。同时,操作系统也会进行一些清理工作,例如关闭文件、释放资源等。
二、Qt中的异常捕获
try-catch
在Qt中使用我们熟悉的try-catch即可,代码如下:
//异常捕获测试
try{
vector<int> arry(2);
arry[5] = 100;
int a = 1 , b = 0;
int c = a /b;
}catch(out_of_range &e){
qWarning() << "数组越界!" << e.what();
cout << "数组越界" << e.what() <<"\n";
}catch(const std::exception& e){
qWarning() << "异常出错1";
}catch(...){
QMessageBox(QMessageBox::Warning, "警告", "除数不能为0").exec();
qDebug() << "系统异常!";
}
**注意:**经过测试,在Qt环境为MinGW64/32环境下无法捕获99%的异常。
在 Qt 使用 MinGW 64时,这是由于 MinGW 默认使用的是 Dwarf 异常处理机制,而 Qt 在 Windows 上使用的是 SEH(Structured Exception Handling)异常处理机制。
解决办法1:
在.pro文件中添加如下代码
QMAKE_CXXFLAGS += -fexceptions
QMAKE_LFLAGS_EXCEPTIONS = -Wl,-enable-stdcall-fixup
解决办法2:
安装MSVC64/32版本微软环境运行程序,并在.pro文件中添加如下代码:
QMAKE_CXXFLAGS_EXCEPTIONS_ON = /EHa
QMAKE_CXXFLAGS_STL_ON = /EHa
如果你在MinGW环境下添加上面两行代码则会出现如下错误:
/EHa 是针对 MSVC (Microsoft Visual C++)的编译选项。
如果你用的是VS+Qt,在工程属性->C/C+±>Code Genaration里,Enable C++ Exceptions默认是Yes(/EHsc),
改成 Yes With SEH Exceptions(/EHa)就好了。
在捕获到异常后我们可以自定义异常处理机制来处理捕获到的异常。
方法如下:
qInstallMessageHandler 函数:使用 qInstallMessageHandler 函数可以安装自定义的消息处理器。通过设置自定义的消息处理器,可以在程序运行时捕获和处理 Qt 框架发出的异常以及其他消息。
void myMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
// 自定义消息处理逻辑
}
int main(int argc, char* argv[]) {
QCoreApplication app(argc, argv);
qInstallMessageHandler(myMessageHandler); // 安装自定义消息处理器
// 程序逻辑
return app.exec();
}
Qt::CriticalMsg 消息类型:Qt 框架会在关键错误发生时发出 Qt::CriticalMsg 类型的消息。你可以重写 qInstallMessageHandler 函数中的自定义消息处理器,并在其中根据消息类型决定是否处理异常。
void myMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
if (type == QtMsgType::QtCriticalMsg) {
// 处理关键错误
}
// 其他消息处理逻辑
}
这些方法都可用于在 Qt 程序中处理运行时异常。通过捕获异常、安装自定义消息处理器或根据消息类型进行相应处理,可以更好地了解和处理程序运行时的异常情况,并采取适当的措施来处理异常。
三、日志记录
在软件运行过程中通常会加入一些日志进行记录,也可以用于异常的记录,方便我们通过日志观察到软件的运行情况,有无异常发生等。
在软件中我使用的是某位博主大佬写的日志文件,找不到原链接了。
代码如下:
#ifndef X_LOGGER_H
#define X_LOGGER_H
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <time.h>
#include <chrono>
#include "../spdlog/spdlog.h"
#include "../spdlog/async.h"
#include "../spdlog/sinks/stdout_color_sinks.h"
#include "../spdlog/sinks/basic_file_sink.h"
#include "../spdlog/sinks/rotating_file_sink.h"
#include "../spdlog/sinks/daily_file_sink.h"
static inline int NowDateToInt()
{
time_t now;
time(&now);
// 在每个平台中选择线程保存版本
tm p;
#ifdef _WIN64
localtime_s(&p, &now);
#else
localtime_r(&now, &p);
#endif // _WIN64
int now_date = (1900 + p.tm_year) * 10000 + (p.tm_mon + 1) * 100 + p.tm_mday;
return now_date;
}
static inline int NowTimeToInt()
{
time_t now;
time(&now);
// 在每个平台中选择线程保存版本
tm p;
#ifdef _WIN64
localtime_s(&p, &now);
#else
localtime_r(&now, &p);
#endif // _WIN64
int now_int = p.tm_hour * 10000 + p.tm_min * 100 + p.tm_sec;
return now_int;
}
class XLogger
{
public:
static XLogger* getInstance()
{
static XLogger xlogger;
return &xlogger;
}
std::shared_ptr<spdlog::logger> getLogger()
{
return m_logger;
}
std::shared_ptr<spdlog::logger> getLoggerd()
{
return m_loggerd;
}
std::shared_ptr<spdlog::logger> getLoggeri()
{
return m_loggeri;
}
std::shared_ptr<spdlog::logger> getLoggerw()
{
return m_loggerw;
}
std::shared_ptr<spdlog::logger> getLoggere()
{
return m_loggere;
}
private:
// 将构造函数设为私有以避免外部实例
XLogger()
{
//对日志路径进行编辑
const std::string log_dir = "./log"; // 如果不存在,应创建文件夹
const std::string logger_name_prefix = "SIMS_";
// 决定打印到控制台或日志文件
bool console = false;
// 确定日志级别
std::string level = "debug";
try
{
// 带时间戳的记录器
int date = NowDateToInt();
int time = NowTimeToInt();
const std::string logger_name = logger_name_prefix + std::to_string(date) + "_" + std::to_string(time);
if (console)
m_logger = spdlog::stdout_color_st(logger_name); // 单线程控制台输出更快
else
{
//m_logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>(logger_name, log_dir + "/" + logger_name + ".log"); // 只有一个日志文件
//m_logger = spdlog::create_async<spdlog::sinks::rotating_file_sink_mt>(logger_name, log_dir + "/" + logger_name + ".log", 10 * 1024 * 1024, 10); // 多部分日志文件,每个部分 10M,最多 10 个文件
m_logger = spdlog::create_async_nb<spdlog::sinks::daily_file_sink_mt>(logger_name, log_dir + "/" + logger_name_prefix + ".log", 8,30); //多次打开软件产生的文件名如果相同则一天内只创建一个
m_loggerd = spdlog::create_async_nb<spdlog::sinks::daily_file_sink_mt>("logger_d", log_dir + "/DebugLog/Debug.log", 8,30);
m_loggeri = spdlog::create_async_nb<spdlog::sinks::daily_file_sink_mt>("logger_i", log_dir + "/InfoLog/Info.log", 8,30);
m_loggerw = spdlog::create_async_nb<spdlog::sinks::daily_file_sink_mt>("logger_w", log_dir + "/WarnLog/Warn.log", 8,30);
m_loggere = spdlog::create_async_nb<spdlog::sinks::daily_file_sink_mt>("logger_e", log_dir + "/ErrorLog/Err.log", 8,30);
}
//自定义格式
m_logger->set_pattern("[%Y-%m-%d %H:%M:%S.%f] [%l] <thread %t> [%@] %v"); // 带有时间戳、thread_id、文件名和行号
m_loggerd->set_pattern("[%Y-%m-%d %H:%M:%S.%f] [%l] <thread %t> [%@] %v");
m_loggeri->set_pattern("[%Y-%m-%d %H:%M:%S.%f] [%l] <thread %t> [%@] %v");
m_loggerw->set_pattern("[%Y-%m-%d %H:%M:%S.%f] [%l] <thread %t> [%@] %v");
m_loggere->set_pattern("[%Y-%m-%d %H:%M:%S.%f] [%l] <thread %t> [%@] %v");
if (level == "trace")
{
m_logger->set_level(spdlog::level::trace);
m_logger->flush_on(spdlog::level::trace);
}
else if (level == "debug")
{
m_loggerd->set_level(spdlog::level::debug);
m_loggerd->flush_on(spdlog::level::debug);
}
else if (level == "info")
{
m_loggeri->set_level(spdlog::level::info);
m_loggeri->flush_on(spdlog::level::info);
}
else if (level == "warn")
{
m_loggerw->set_level(spdlog::level::warn);
m_loggerw->flush_on(spdlog::level::warn);
}
else if (level == "error")
{
m_loggere->set_level(spdlog::level::err);
m_loggere->flush_on(spdlog::level::err);
}
}
catch (const spdlog::spdlog_ex& ex)
{
std::cout << "Log initialization failed: " << ex.what() << std::endl;
}
}
~XLogger()
{
spdlog::drop_all(); // 释放所有logger
}
void* operator new(size_t size)
{}
XLogger(const XLogger&) = delete;
XLogger& operator=(const XLogger&) = delete;
private:
std::shared_ptr<spdlog::logger> m_logger;
std::shared_ptr<spdlog::logger> m_loggerd;
std::shared_ptr<spdlog::logger> m_loggeri;
std::shared_ptr<spdlog::logger> m_loggerw;
std::shared_ptr<spdlog::logger> m_loggere;
};
//使用嵌入式宏支持文件和行号
#define FTRACE(...) SPDLOG_LOGGER_CALL(XLogger::getInstance()->getLogger().get(), spdlog::level::trace, __VA_ARGS__)
#define FDEBUG(...) SPDLOG_LOGGER_CALL(XLogger::getInstance()->getLoggerd().get(), spdlog::level::debug, __VA_ARGS__)
#define FINFO(...) SPDLOG_LOGGER_CALL(XLogger::getInstance()->getLoggeri().get(), spdlog::level::info, __VA_ARGS__)
#define FWARN(...) SPDLOG_LOGGER_CALL(XLogger::getInstance()->getLoggerw().get(), spdlog::level::warn, __VA_ARGS__)
#define FERROR(...) SPDLOG_LOGGER_CALL(XLogger::getInstance()->getLoggere().get(), spdlog::level::err, __VA_ARGS__)
#endif // X_LOGGER_H
在软件中使用:
直接在需要日志记录的类中添加该头文件,然后再catch中添加日志记录级别类型的方法
try {
if(event->key() == Qt::Key_Delete && customPlot->graphCount() > 1){
for (int i = 0; i < customPlot->graphCount(); ++i) {
if(customPlot->graph(i)->selected()){
FINFO("曲线删除!");
remove_curve(i);
setdatatype(newHeaders, newPheaders, newSecondIons);
customPlot->replot();
}
}
}
} catch (...) {
FERROR("曲线删除异常!");
}
try {
if(event->key() == Qt::Key_Delete){
QCPItemText *label = customPlot->itemAt<QCPItemText>(point);
if(label){
FINFO("删除标签");
dose2->delete_label(label);
customPlot->removeItem(label);
customPlot->replot();
}
}
} catch (...) {
FERROR("标签删除异常!");
}
效果如下:
他会在你的软件目录下生成一个log文件夹,文件下存放各种级别的日志文件。
他的好处就是可以清楚的看到在你的软件目录中那个文件哪一行打印了信息或者出现的异常,有利于文件去修改Bug.
这是我在使用的一个日志机制,还有其他很多的日志机制,大家可以自行搜索添加,也可以自己DIY自己的日志格式样式(避免重复造轮子)。
总结
以上就是今天要讲的内容,本文仅仅简单介绍了Qt中异常机制的使用及处理和日志的整合处理,异常处理机制及日志记录是开发中必不可少的一道程序,可以帮助我们提高开发效率。
基础概念-Qt QCustomPlot 介绍
Qt-QCustomplot图像和QChart的区别
Qt-QCustomplot图像设计功能-坐标轴
Qt-QCustomplot图像设计功能-游标
Qt-QCustomplot图像功能设计-曲线绘制