第三代软件开发-日志模块
[toc]
关键字: Qt
、Qml
、日志
、Log
、SQLite
项目介绍
欢迎来到我们的 QML & C++ 项目!这个项目结合了 QML(Qt Meta-Object Language)和 C++ 的强大功能,旨在开发出色的用户界面和高性能的后端逻辑。
在项目中,我们利用 QML 的声明式语法和可视化设计能力创建出现代化的用户界面。通过直观的编码和可重用的组件,我们能够迅速开发出丰富多样的界面效果和动画效果。同时,我们利用 QML 强大的集成能力,轻松将 C++ 的底层逻辑和数据模型集成到前端界面中。
在后端方面,我们使用 C++ 编写高性能的算法、数据处理和计算逻辑。C++ 是一种强大的编程语言,能够提供卓越的性能和可扩展性。我们的团队致力于优化代码,减少资源消耗,以确保我们的项目在各种平台和设备上都能够高效运行。
无论您是对 QML 和 C++ 开发感兴趣,还是需要我们为您构建复杂的用户界面和后端逻辑,我们都随时准备为您提供支持。请随时联系我们,让我们一同打造现代化、高性能的 QML & C++ 项目!
重要说明☝
☀该专栏在第三代软开发更新完将涨价
日志模块
软件中日志的重要性是不可忽视的,以下是几个关键原因:
故障排除和调试:日志记录是排查和解决软件故障的重要工具。当软件出现问题时,日志记录可以帮助开发人员查看系统行为、错误和异常,以便进行故障排除和调试,快速定位并解决问题。
性能分析和优化:通过记录关键的性能指标和日志信息,开发人员可以分析系统的性能瓶颈和瓶颈所在,以便进行优化。日志记录可以揭示资源使用情况、响应时间、吞吐量等数据,从而帮助开发人员发现并改进性能问题。
安全审计和合规性:日志记录对于安全审计和合规性要求至关重要。通过记录关键事件和活动,可以跟踪系统的访问、操作和行为,以确保合规性要求得到满足,并提供审计追踪功能,帮助监测和检测潜在的安全威胁和异常行为。
用户行为分析:通过记录用户的操作和行为,可以了解用户的需求、偏好和行为模式,从而提供更好的用户体验和个性化服务。通过分析用户日志,可以改进产品功能、优化界面设计和提高用户满意度。
数据分析和决策支持:日志记录产生的数据可以用于进行数据分析,从中提取有价值的信息以支持决策制定。通过分析用户行为、系统运行情况和业务指标等日志数据,可以发现潜在的趋势、问题和机会,为业务决策提供依据。
综上所述,日志记录对于故障排除、性能优化、安全审计、用户分析和决策支持等方面都非常重要。它是软件开发和运维过程中必不可少的一部分,有助于提升系统的可靠性、性能和用户体验。
日志Demo
日志Demo老早就搞过了,最简单的日志记录,可以看这里QtApplets-MyLog
第一代日志系统
如果我没有记错,Demo的日志是在主线程中直接运行的,那么,在程序开发后期,其实我们的的日志量也是不小的,所以呢,在正式项目中,我是把我的日式系统弄到了一个线程里面了,当然,实际我还没有遇到性能瓶颈,因为我们项目一直还是在功能开发阶段,日志还是比较少的,当下的拍错和解Bug大多还是依赖Debug模式,或者DGB调试。这里简单分享下我们第一代日志系统的代码:
头文件
#ifndef xxxxx_H
#define xxxxx_H
#include <QThread>
#include <QObject>
#include <QDir>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QDebug>
#define LOGPATH "./T_log"
/**
* 对外宏
**/
#define LOCATION "$" << __FILE__ << "$" << __LINE__ // 获取代码位置宏
#define CURRENTTHREADID "$" << QThread::currentThread() // 获取线程ID宏
#define LOGINFOR "$" << __FILE__ << "$" << __LINE__ << "$" << QThread::currentThread() // 加上日志内容
/**
* @brief The LogType enum
* 彩色日志控制台输出
* *********************************************************************************************************************************************************
*/
//enum class LogType {
// Reset = 0,
// Bold,
// Unbold,
// FrontBlack,
// FrontRed,
// FrontGreen,
// FrontYellow,
// FrontBlue,
// FrontPurple,
// FrontCyan,
// FrontWhite,
// BackBlack,
// BackRed,
// BackGreen,
// BackYellow,
// BackBlue,
// BackPurple,
// BackCyan,
// BackWhite,
// TypeCount
//};
//static const char* logCommands[] = {
// "\033[0m",
// "\033[1m",
// "\033[2m",
// "\033[30m",
// "\033[31m",
// "\033[32m",
// "\033[33m",
// "\033[34m",
// "\033[35m",
// "\033[36m",
// "\033[37m",
// "\033[40m",
// "\033[41m",
// "\033[42m",
// "\033[43m",
// "\033[44m",
// "\033[45m",
// "\033[46m",
// "\033[47m",
//};
//template <typename EnumType, typename IntType = int>
//int enumToInt(EnumType enumValue);
/*
彩色控制台日志输出 demo
int main(int argc, char *argv[])
{
for (int i = enumToInt(LogType::Bold); i < enumToInt(LogType::TypeCount); ++i)
{
qInfo().nospace() << logCommands[i] << i << " Hello World" << logCommands[0];
}
qWarning() << logCommands[enumToInt(LogType::FrontBlue)]
<< logCommands[enumToInt(LogType::BackRed)]
<< u8"感谢大家对涛哥系列文章的支持,也"
"欢迎直接联系我寻求帮助" << logCommands[0];
return 0;
}
* **********************************************************************************************************************************************************/
/**
* @brief The Log_Base class
* 真实LOG日志处理线程
*/
class Log_Base : public QObject
{
Q_OBJECT
public:
explicit Log_Base(QObject *parent = nullptr);
~Log_Base();
void log(QtMsgType type, const QMessageLogContext &context, const QString &msg);
bool makeLogDir(QString &msg);
void cleanOldLog(bool isClean = false);
bool openLogDataBase(QString& mes);
public slots:
void slot_IntiLog_Base();
private:
QDir* mLogDir = nullptr; // 全局文件夹
QString mpath; // 数据库路径
QSqlDatabase mTuringLogDB; // 日志数据库
QSqlQuery sql_query; // 日志执行器
QString sqlString = "NULL"; // sql语句
QString mThreadID = "NULL"; // 线程ID
QString mFileName = "NULL"; // 文件名称
QString mCurrentLine = "NULL"; // 代码行数
QString mInfor = "NULL"; // 信息
QString messageType = ""; // 消息类型
};
/**
* @brief The xxxxx class
* 日志线程管理类
*/
class xxxxx : public QThread
{
public:
explicit xxxxx(QObject *parent = nullptr);
void log(QtMsgType type, const QMessageLogContext &context, const QString &msg);
bool makeLogDir(QString &msg);
void cleanOldLog(bool isClean = false);
bool openLogDataBase(QString& mes);
private:
Log_Base* mLog_Base = nullptr; // 日志核心模块
QThread* mLogThread = nullptr; // 日志线程
};
#endif // xxxxx_H5
源文件
#include "xxxxx.h"
#include <QSettings>
#include <QSql>
#include <QCoreApplication>
#include <QSqlError>
#include <QSqlDriver>
#include <QDateTime>
#include <QString>
QString mMessage = ""; //消息
xxxxx::xxxxx(QObject *parent)
: QThread{parent}
{
mLog_Base = new Log_Base;
mLogThread = new QThread;
mLog_Base->moveToThread(mLogThread);
connect(mLogThread,&QThread::started,mLog_Base,&Log_Base::slot_IntiLog_Base);
mLogThread->start();
}
/**
* @brief xxxxx::log
* @param type
* @param context
* @param msg
* 解析劫持日志
*/
void xxxxx::log(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
if(mLog_Base)
mLog_Base->log(type,context,msg);
}
/**
* @brief xxxxx::makeLogDir
* @param msg
* @return
* 创建日志目录
*/
bool xxxxx::makeLogDir(QString& msg)
{
if(mLog_Base)
return mLog_Base->makeLogDir(msg);
else
return false;
}
/**
* @brief xxxxx::cleanOldLog
* @param isClean
* 清理旧的日志
*/
void xxxxx::cleanOldLog(bool isClean)
{
if(mLog_Base)
mLog_Base->cleanOldLog(isClean);
}
/**
* @brief xxxxx::openLogDataBase
* @param mes
* @return
* 打开数据库
*/
bool xxxxx::openLogDataBase(QString &mes)
{
if(mLog_Base)
return mLog_Base->openLogDataBase(mes);
else
return false;
}
/**
* @brief Log_Base::log
* @param type
* @param context
* @param msg
* 日志线程
*/
Log_Base::Log_Base(QObject *parent)
{
Q_UNUSED(parent)
}
/**
* @brief Log_Base::~Log_Base
* 析构函数,关闭数据库
*/
Log_Base::~Log_Base()
{
if(mTuringLogDB.isOpen())
mTuringLogDB.close();
}
/**
* @brief Log_Base::log
* @param type
* @param context
* @param msg
* 日志函数
* 目前这里会保存所有的日志记录,后期需要加入标志,仅仅保留需要的数据
*
*
*
*/
void Log_Base::log(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
Q_UNUSED(context);
mMessage = msg;
switch(type)
{
default:
break;
case QtDebugMsg:
messageType = "输出";
break;
case QtInfoMsg:
messageType = "消息";
break;
case QtWarningMsg:
messageType = "警告";
break;
case QtCriticalMsg:
messageType = "严重";
break;
case QtFatalMsg:
messageType = "致命";
break;
}
while (!mMessage.isEmpty()) {
if(mMessage.indexOf("$") > 0)
{
mInfor = mMessage.mid(0,mMessage.indexOf("$"));
mMessage = mMessage.mid(mMessage.indexOf("$")+1);
if(mMessage.indexOf("$") > 0)
{
mFileName = mMessage.mid(0,mMessage.indexOf("$"));
mMessage = mMessage.mid(mMessage.indexOf("$")+1);
if(mMessage.indexOf("$") > 0)
{
mCurrentLine = mMessage.mid(0,mMessage.indexOf("$"));
mMessage = mMessage.mid(mMessage.indexOf("$")+1);
if(mMessage.length() > 1)
{
mThreadID = mMessage;
}
else
mThreadID = "NULL";
}
else
{
mCurrentLine = mMessage;
mThreadID = "NULL";
}
}
else
{
mFileName = mMessage;
mCurrentLine = "NULL";
mThreadID = "NULL";
}
}
else
{
mInfor = mMessage;
mFileName = "NULL";
mCurrentLine = "NULL";
mThreadID = "NULL";
}
if(mThreadID != "NULL") //这里需要做下条件判断,只有给了线程ID的日志信息才被记录
{
sqlString = QString("insert into day%1(Date,Level,Info,File,Line,ThreadId) values ('%2', '%3', '%4','%5','%6','%7')")
.arg(QDateTime::currentDateTime().toString("dd"),
QDateTime::currentDateTime().toString("hh:mm:ss"),
messageType,
mInfor,
mFileName,
mCurrentLine,
mThreadID);
sql_query.exec(sqlString);
}
mMessage.clear();
}
}
/**
* @brief Log_Base::makeLogDir
* @param msg
* @return
* 创建日志文件夹
*/
bool Log_Base::makeLogDir(QString &msg)
{
if(mLogDir->exists(LOGPATH)) //检查日志文件加是否存在
{
mpath = LOGPATH + QString("/") +QDateTime::currentDateTime().toString("yyyy");
if(mLogDir->exists(mpath)) //检查对应年份日志文件夹是否旬在
return true;
else
if(mLogDir->mkdir(mpath))
return true;
else
{
msg = "创建xxxxx文件夹失败";
return false;
}
}
else
{
if(mLogDir->mkdir(LOGPATH))
{
mpath = LOGPATH + QString("/") +QDateTime::currentDateTime().toString("yyyy");
if(mLogDir->exists(mpath))
return true;
else
{
if(mLogDir->mkdir(mpath))
return true;
else
{
msg = QString("创建%1文件夹失败").arg(QDateTime::currentDateTime().toString("yyyy"));
return false;
}
}
}
else
{
msg = "创建xxxxx文件夹失败";
return false;
}
}
return true;
}
/**
* @brief Log_Base::cleanOldLog
* @param isClean
* 清理旧的日志
*/
void Log_Base::cleanOldLog(bool isClean)
{
if(isClean)
{
mLogDir->setPath(LOGPATH);
mLogDir->setFilter(QDir::Dirs);
QFileInfoList list = mLogDir->entryInfoList();
int i = 0;
do{
QFileInfo fileInfo = list.at(i);
if((fileInfo.fileName() != ".") &&
(fileInfo.fileName() != "..") &&
(fileInfo.fileName() != QDateTime::currentDateTime().toString("yyyy")))
{
mLogDir->setPath(LOGPATH + QString("/") +fileInfo.fileName());
mLogDir->removeRecursively();
}
++i;
}while (i<list.size());
}
}
/**
* @brief Log_Base::openLogDataBase
* @param mes
* @return
* 打开数据库
*/
bool Log_Base::openLogDataBase(QString &mes)
{
if(QSqlDatabase::contains("xxxxx_database"))
mTuringLogDB = QSqlDatabase::database("xxxxx_database");
else
{
mpath = LOGPATH + QString("/") +QDateTime::currentDateTime().toString("yyyy")+QString("/")+ QDateTime::currentDateTime().toString("MM") + ".db";
mTuringLogDB = QSqlDatabase::addDatabase("QSQLITE","xxxxx_database");
if(!mTuringLogDB.isValid())
{
mes = "数据库驱动无效";
return false;
}
mTuringLogDB.setDatabaseName(mpath);
mTuringLogDB.setUserName("Root");
mTuringLogDB.setPassword("Root");
if(mTuringLogDB.open())
{
sql_query = QSqlQuery(mTuringLogDB);
sqlString = QString("select count(*) from sqlite_master where type='table' and name='day%1'").arg(QDateTime::currentDateTime().toString("dd"));
sql_query.exec(sqlString);
if(sql_query.next())
{
if(sql_query.value(0).toUInt() == 0)
{
sqlString = QString("create table day%1 (Id INTEGER PRIMARY KEY AUTOINCREMENT,"
"Date varchar(30),"
"Level varchar(30),"
"Info varchar(300),"
"File varchar(300),"
"Line int,"
"ThreadId int)").arg(QDateTime::currentDateTime().toString("dd"));
if(!sql_query.exec(sqlString))
{
mes = "数据库建表";
return false;
}
}
}
}
else
{
mes = "数据库打开失败";
return false;
}
}
return true;
}
/**
* @brief Log_Base::slot_IntiLog_Base
* 初始化日志核心
*/
void Log_Base::slot_IntiLog_Base()
{
mLogDir = new QDir;
QCoreApplication::addLibraryPath("./plugins");
}
//template<typename EnumType, typename IntType>
//int enumToInt(EnumType enumValue)
//{
// static_assert (std::is_enum<EnumType>::value, "EnumType must be enum");
// return static_cast<IntType>(enumValue);
//}
在第一代日志系统中,是存在一定问题的,细心的你有发现吗?欢迎在评论去留言。
第二代日志系统
第二代日志系统,也是在第一代日志系统上的改良,就是在数据库存入的时候,不再是单独一条一条的IO存入,而是采用了事物模式,到达一应数量后批量存入数据库,但是这就会触发一个风险,就是如果我们的日志数量没有达到数量级,那就会有丢日志的风险,所以加入了定时器。哎嗨,这里是不是发现一点套路了,没错,咱的灵感就是来源于汽车的那个 3年8万公里,那个先到算那个。所以这样就就极大的保证了日志数据的完整性。至于代码,不用分享了,就加了一个定时器和事物,聪明的你一定可以,不会的话,留言,我教你。