一、前言

之前已经开源过基础版本,近期根据客户需求和自己的项目需求,提炼出通用需求部分,对整个日志重定向输出类重新规划和重写代码。

用Qt这个一站式超大型GUI超市做开发已经十二年了,陆陆续续开发过至少几十个程序,除了一些算不算项目的小工具外,大部分的程序都需要有个日志的输出功能,希望可以将程序的运行状态存储到文本文件或者数据库或者做其他处理等,Qt对这个日志输出也做了很好的封装,在Qt4是qInstallMsgHandler,Qt5及Qt6里边是qInstallMessageHandler,有了这个神器,只要在你的项目中所有qDebug qInfo等输出的日志信息,都会重定向接收到。

网上大部分人写的demo都是接收到输出打印日志存储到文本文件,其实这就带给很多人误解,容易产生以为日志只能输出到文本文件,其实安装了日志钩子以后,拿到了所有调试打印信息,你完全可以用来存储到数据库及输出html有颜色区分格式的文件,或者网络转发输出(尤其适用于嵌入式linux无界面程序,现场不方便外接调试打印的设备)。

做过的这么多项目中,Qt4、Qt5、Qt6的都有,我一般保留四个版本,4.8.7,为了兼容Qt4, 5.7.0,最后的支持XP的版本, 最新的长期支持版本5.15.2 最高的新版本6.2.1。毫无疑问,我要封装的这个日志类,也要同时支持Qt4、Qt5、Qt6的,而且提供友好的接口。

二、主要功能

  1. 支持动态启动和停止。
  2. 支持日志存储的目录。
  3. 支持网络发出打印日志。
  4. 支持输出日志上下文信息比如所在代码文件、行号、函数名等。
  5. 支持设置日志文件大小限制,超过则自动分文件,默认128kb。
  6. 支持按照日志行数自动分文件,和日志大小条件互斥。
  7. 可选按照日期时间区分文件名存储日志。
  8. 日志文件命名规则优先级:行数》大小》日期。
  9. 自动加锁支持多线程。
  10. 可以分别控制哪些类型的日志需要重定向输出。
  11. 支持Qt4+Qt5+Qt6,开箱即用。
  12. 使用方式最简单,调用函数start()启动服务,stop()停止服务。

三、效果图

四、开源主页

  • 以上作品完整源码下载都在开源主页,会持续不断更新作品数量和质量,欢迎各位关注。
  • 本开源项目已经成功升级到V2.0版本,分门别类,图文并茂,保你爽到爆。
  • Qt开源武林秘籍开发经验,看完学完,20K起薪,没有找我!
  1. 国内站点:https://gitee.com/feiyangqingyun/QWidgetDemo
  2. 国际站点:https://github.com/feiyangqingyun/QWidgetDemo
  3. 开源秘籍:https://gitee.com/feiyangqingyun/qtkaifajingyan
  4. 个人主页:https://qtchina.blog.csdn.net/
  5. 知乎主页:https://www.zhihu.com/people/feiyangqingyun/

五、核心代码

#pragma execution_character_set("utf-8")

#include "savelog.h"
#include "qmutex.h"
#include "qdir.h"
#include "qfile.h"
#include "qtcpsocket.h"
#include "qtcpserver.h"
#include "qdatetime.h"
#include "qapplication.h"
#include "qtimer.h"
#include "qstringlist.h" #define QDATE qPrintable(QDate::currentDate().toString("yyyy-MM-dd"))
#define QDATETIMS qPrintable(QDateTime::currentDateTime().toString("yyyy-MM-dd-HH-mm-ss")) //日志重定向
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
void Log(QtMsgType type, const QMessageLogContext &context, const QString &msg)
#else
void Log(QtMsgType type, const char *msg)
#endif
{
//加锁,防止多线程中qdebug太频繁导致崩溃
static QMutex mutex;
QMutexLocker locker(&mutex);
QString content; //这里可以根据不同的类型加上不同的头部用于区分
int msgType = SaveLog::Instance()->getMsgType();
switch (type) {
case QtDebugMsg:
if ((msgType & MsgType_Debug) == MsgType_Debug) {
content = QString("Debug %1").arg(msg);
}
break;
#if (QT_VERSION >= QT_VERSION_CHECK(5,5,0))
case QtInfoMsg:
if ((msgType & MsgType_Info) == MsgType_Info) {
content = QString("Infox %1").arg(msg);
}
break;
#endif
case QtWarningMsg:
if ((msgType & MsgType_Warning) == MsgType_Warning) {
content = QString("Warnx %1").arg(msg);
}
break;
case QtCriticalMsg:
if ((msgType & MsgType_Critical) == MsgType_Critical) {
content = QString("Error %1").arg(msg);
}
break;
case QtFatalMsg:
if ((msgType & MsgType_Fatal) == MsgType_Fatal) {
content = QString("Fatal %1").arg(msg);
}
break;
} //没有内容则返回
if (content.isEmpty()) {
return;
} //加上打印代码所在代码文件、行号、函数名
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
if (SaveLog::Instance()->getUseContext()) {
int line = context.line;
QString file = context.file;
QString function = context.function;
if (line > 0) {
content = QString("行号: %1 文件: %2 函数: %3\n%4").arg(line).arg(file).arg(function).arg(content);
}
}
#endif //还可以将数据转成html内容分颜色区分
//将内容传给函数进行处理
SaveLog::Instance()->save(content);
} QScopedPointer<SaveLog> SaveLog::self;
SaveLog *SaveLog::Instance()
{
if (self.isNull()) {
static QMutex mutex;
QMutexLocker locker(&mutex);
if (self.isNull()) {
self.reset(new SaveLog);
}
} return self.data();
} SaveLog::SaveLog(QObject *parent) : QObject(parent)
{
//必须用信号槽形式,不然提示 QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
//估计日志钩子可能单独开了线程
connect(this, SIGNAL(send(QString)), SendLog::Instance(), SLOT(send(QString))); isRun = false;
maxRow = currentRow = 0;
maxSize = 128;
toNet = false;
useContext = true; //全局的文件对象,在需要的时候打开而不是每次添加日志都打开
file = new QFile(this);
//默认取应用程序根目录
path = qApp->applicationDirPath();
//默认取应用程序可执行文件名称
QString str = qApp->applicationFilePath();
QStringList list = str.split("/");
name = list.at(list.count() - 1).split(".").at(0);
fileName = ""; //默认所有类型都输出
msgType = MsgType(MsgType_Debug | MsgType_Info | MsgType_Warning | MsgType_Critical | MsgType_Fatal);
} SaveLog::~SaveLog()
{
file->close();
} void SaveLog::openFile(const QString &fileName)
{
//当文件名改变时才新建和打开文件而不是每次都打开文件(效率极低)或者一开始打开文件
if (this->fileName != fileName) {
this->fileName = fileName;
//先关闭之前的
if (file->isOpen()) {
file->close();
}
//重新设置新的日志文件
file->setFileName(fileName);
//以 Append 追加的形式打开
file->open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text);
}
} bool SaveLog::getUseContext()
{
return this->useContext;
} MsgType SaveLog::getMsgType()
{
return this->msgType;
} //安装日志钩子,输出调试信息到文件,便于调试
void SaveLog::start()
{
if (isRun) {
return;
} isRun = true;
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
qInstallMessageHandler(Log);
#else
qInstallMsgHandler(Log);
#endif
} //卸载日志钩子
void SaveLog::stop()
{
if (!isRun) {
return;
} isRun = false;
this->clear();
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
qInstallMessageHandler(0);
#else
qInstallMsgHandler(0);
#endif
} void SaveLog::clear()
{
currentRow = 0;
fileName.clear();
if (file->isOpen()) {
file->close();
}
} void SaveLog::save(const QString &content)
{
//如果重定向输出到网络则通过网络发出去,否则输出到日志文件
if (toNet) {
emit send(content);
} else {
//目录不存在则先新建目录
QDir dir(path);
if (!dir.exists()) {
dir.mkdir(path);
} //日志存储规则有多种策略 优先级 行数>大小>日期
//1: 设置了最大行数限制则按照行数限制来
//2: 设置了大小则按照大小来控制日志文件
//3: 都没有设置都存储到日期命名的文件,只有当日期变化了才会切换到新的日志文件
bool needOpen = false;
if (maxRow > 0) {
currentRow++;
if (fileName.isEmpty()) {
needOpen = true;
} else if (currentRow >= maxRow) {
needOpen = true;
}
} else if (maxSize > 0) {
//1MB=1024*1024 经过大量测试 QFile().size() 方法速度非常快
//首次需要重新打开文件以及超过大小需要重新打开文件
if (fileName.isEmpty()) {
needOpen = true;
} else if (file->size() > (maxSize * 1024)) {
needOpen = true;
}
} else {
//日期改变了才会触发
QString fileName = QString("%1/%2_log_%3.txt").arg(path).arg(name).arg(QDATE);
openFile(fileName);
} if ((maxRow > 0 || maxSize > 0) && needOpen) {
currentRow = 0;
QString fileName = QString("%1/%2_log_%3.txt").arg(path).arg(name).arg(QDATETIMS);
openFile(fileName);
} //用文本流的输出速度更快
QTextStream stream(file);
stream << content << "\n";
}
} void SaveLog::setMaxRow(int maxRow)
{
//这里可以限定最大最小值
if (maxRow >= 0) {
this->maxRow = maxRow;
this->clear();
}
} void SaveLog::setMaxSize(int maxSize)
{
//这里可以限定最大最小值
if (maxSize >= 0) {
this->maxSize = maxSize;
this->clear();
}
} void SaveLog::setListenPort(int listenPort)
{
SendLog::Instance()->setListenPort(listenPort);
} void SaveLog::setToNet(bool toNet)
{
this->toNet = toNet;
if (toNet) {
SendLog::Instance()->start();
} else {
SendLog::Instance()->stop();
}
} void SaveLog::setUseContext(bool useContext)
{
this->useContext = useContext;
} void SaveLog::setPath(const QString &path)
{
this->path = path;
} void SaveLog::setName(const QString &name)
{
this->name = name;
} void SaveLog::setMsgType(const MsgType &msgType)
{
this->msgType = msgType;
} //网络发送日志数据类
QScopedPointer<SendLog> SendLog::self;
SendLog *SendLog::Instance()
{
if (self.isNull()) {
static QMutex mutex;
QMutexLocker locker(&mutex);
if (self.isNull()) {
self.reset(new SendLog);
}
} return self.data();
} SendLog::SendLog(QObject *parent) : QObject(parent)
{
listenPort = 6000;
socket = NULL; //实例化网络通信服务器对象
server = new QTcpServer(this);
connect(server, SIGNAL(newConnection()), this, SLOT(newConnection()));
} SendLog::~SendLog()
{
if (socket != NULL) {
socket->disconnectFromHost();
} server->close();
} void SendLog::newConnection()
{
//限定就一个连接
while (server->hasPendingConnections()) {
socket = server->nextPendingConnection();
}
} void SendLog::setListenPort(int listenPort)
{
this->listenPort = listenPort;
} void SendLog::start()
{
//启动端口监听
#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
server->listen(QHostAddress::AnyIPv4, listenPort);
#else
server->listen(QHostAddress::Any, listenPort);
#endif
} void SendLog::stop()
{
if (socket != NULL) {
socket->disconnectFromHost();
socket = NULL;
} server->close();
} void SendLog::send(const QString &content)
{
if (socket != NULL && socket->isOpen()) {
socket->write(content.toUtf8());
//socket->flush();
}
}

Qt开源作品39-日志输出增强版V2022的更多相关文章

  1. Qt开源作品38-无边框窗体方案(无抖动,支持win、linux、mac等系统,侧边半屏顶部全屏)

    一 前言 不知道各位程序员有没有遇到过这样一种困惑,好不容易在开源网站找到了类似的想要的项目代码,结果down下来一编译,我勒个去,几百个错误,根本没法用,熟悉的人还好可以直接阅读代码进行修改(有些只 ...

  2. Qt编写调试日志输出类带网络转发(开源)

    用qt开发商业程序已经九年了,陆陆续续开发过至少几十个程序,除了一些算不算项目的小工具外,大部分的程序都需要有个日志的输出功能,希望可以将程序的运行状态存储到文本文件或者数据库或者做其他处理等,qt对 ...

  3. Qt之日志输出文件

    在Qt开发过程当中经常使用qDebug等一些输出来调试程序,但是到了正式发布的时候,都会被注释或者删除,采用日志输出来代替.     做过项目的童鞋可能都使用过日志功能,以便有异常错误能够快速跟踪.定 ...

  4. Qt之日志输出窗口

    来源:http://blog.sina.com.cn/s/blog_a6fb6cc90101guz0.html 继上节所讲,Qt可以很容易的将一些日志信息保存到文件中,那么日志信息如何输出到窗口呢? ...

  5. Qt 日志输出

    Qt学习(3)日志输出 普通的打印输出 用 QtCreator 开发 Qt 程序时, 经常需要向控制台打印一些参数.有时候是查看对象的属性是否被正确设置,有时候是查看程序是否执行了某一段代码,或者执行 ...

  6. Qt 日志输出文件

    在Qt开发过程当中经常使用qDebug等一些输出来调试程序,但是到了正式发布的时候,都会被注释或者删除,采用日志输出来代替.     做过项目的童鞋可能都使用过日志功能,以便有异常错误能够快速跟踪.定 ...

  7. Log4J是Apache组织的开源一个开源项目,通过Log4J,可以指定日志信息输出的目的地,如console、file等。Log4J采用日志级别机制,请按照输出级别由低到高的顺序写出日志输出级别。

    Log4J是Apache组织的开源一个开源项目,通过Log4J,可以指定日志信息输出的目的地,如console.file等.Log4J采用日志级别机制,请按照输出级别由低到高的顺序写出日志输出级别. ...

  8. iOS开发进阶 - 日志输出框架CocoaLumberjack与XcodeColors插件的简单使用(swift版)

    CocoaLumberjack是Mac和iOS上一个集快捷.简单.强大和灵活于一身的日志框架.XcodeColors是用于控制台着色的工具,配合着CocoaLumberjack用有更好的效果,不废话, ...

  9. Haproxy安装配置及日志输出问题

    简介: 软件负载均衡一般通过两种方式来实现:基于操作系统的软负载实现和基于第三方应用的软负载实现.LVS就是基于Linux操作系统实现的一种软负载,HAProxy就是开源的并且基于第三应用实现的软负载 ...

  10. Github上的一些高分Qt开源项目【多图】

    游戏2D地图编辑器: 著名的TileMap编辑器,做2D游戏开发的一定不会陌生. Go 语言的IDE: Go语言的集成开发环境. Clementine Music Player: 功能很完善且跨平台支 ...

随机推荐

  1. day07-数据类型及标识符

    数据类型 强类型语言 要求变量的使用严格要严格符合规定,所有变量都必须先定义后才能使用 弱类型语言 java的数据类型分为两大类 基本类型(primitive type) 数值类型 整数类型 byte ...

  2. 手撸二叉树——AVL平衡二叉树

    还记得上一篇中我们遗留的问题吗?我们再简要回顾一下,现在有一颗空的二叉查找树,我们分别插入1,2,3,4,5,五个节点,那么得到的树是什么样子呢?这个不难想象,二叉树如下: 树的高度是4,并且数据结构 ...

  3. Fluent Operator 2.5.0 发布:新增多个插件

    日前,Fluent Operator 发布了 v2.5.0. Fluent Operator v2.5.0 新增 11 个 features, 其中 Fluent Bit 新增支持 7 个插件, Fl ...

  4. 初学Linux的可以看看

    Linux常用命令总结 自己简单总结的,希望对大家有帮助吧! 关机/重启 系统信息 文件以及目录 vim文件编辑器 打包压缩 用户和组 权限所有者 RPM YUM mount find 自己简单总结的 ...

  5. C#线性查找算法

    前言 线性查找算法是一种简单的查找算法,用于在一个数组或列表中查找一个特定的元素.它从数组的第一个元素开始,逐个检查每个元素,直到找到所需的元素或搜索完整个数组.线性查找的时间复杂度为O(n),其中n ...

  6. 银河麒麟操作系统root用户登录图形化界面

    第一步.为root用户设置密码 sudo passwd 设置root用户密码 第二步.开启root登录权限 vim /usr/share/lightdm/lightdm.conf.d/60-kylin ...

  7. 3-2 C++ string类型

    目录 3.2.1 定义和初始化 3.2.2 对string的操作 (一)重要操作一览 (二)读写操作 cin/cout getline : 可读取整行 (三)获取大小 .size() (四)比较.赋值 ...

  8. NZOJ 模拟赛5

    T1 逃离遗迹 根据外星人的回信,在遗迹中有分布着三样道具.当三样道具都拿走后,遗迹就很快自动毁灭,所以必须要在最短时间内离开.遗迹可以看作是由N个房间(编号1..N)和N-1条长度不等通道所组成,并 ...

  9. 鸿蒙NEXT开发案例:随机数生成

    [引言] 本项目是一个简单的随机数生成器应用,用户可以通过设置随机数的范围和个数,并选择是否允许生成重复的随机数,来生成所需的随机数列表.生成的结果可以通过点击"复制"按钮复制到剪 ...

  10. 抓包工具之Charles(windows)

    激活码:  https://www.zzzmode.com/mytools/charles/ 官方地址:https://www.charlesproxy.com/ PC端如何配置才能抓取到https请 ...