Qt重定向QDebug,Qt/C++开源作品39-日志输出增强版V2022

news2024/9/20 5:36:51

Qt重定向QDebug,自定义一个简易的日志管理类

  • Chapter1 Qt重定向QDebug,自定义一个简易的日志管理类
    • 0.前言
    • 1.最简单的操作
    • 运行结果
    • 2.实现一个简易的日志管理类
  • Chapter2 Qt::Qt Log日志模块
    • Qt Log日志模块
    • 官方解释
    • 官方Demo
    • 思路
  • Chapter3 QT日志模块的个性化使用
    • 格式化日志输出
    • 输出日志到文本
    • 日志输出对象信息
  • Chapter4 Qt 自定义日志类($$$)
  • Chapter5 简单易用的Qt日志模块($$$)
    • 引言
    • 一、日志实现方法
      • 代码实现
    • 二、崩溃处理
      • 代码实现
    • 小结
  • Chapter6 Qt/C++开源作品39-日志输出增强版V2022($$$)
    • 一、前言
    • 二、主要功能
    • 三、效果图
    • 四、开源主页
    • 五、核心代码


Chapter1 Qt重定向QDebug,自定义一个简易的日志管理类

原文链接:https://blog.csdn.net/gongjianbo1992/article/details/108030391

0.前言

相对于第三方的日志库,在 Qt 中使用 QDebug 打印更便捷,有时候也需要对 QDebug 输出进行重定向,如写入文件等。

在 Qt4 中使用 qInstallMsgHandler 函数设置重定向的函数指针:

typedef void (*QtMsgHandler)(QtMsgType, const char *);
Q_CORE_EXPORT QT_DEPRECATED QtMsgHandler qInstallMsgHandler(QtMsgHandler);

在 Qt5 中应该使用 qInstallMessageHandler 来注册函数指针:

typedef void (*QtMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &);
Q_CORE_EXPORT QtMessageHandler qInstallMessageHandler(QtMessageHandler);

返回的函数指针我们可以保存起来,需要输出到控制台时进行调用。

默认 Release 模式 QMessageLogContext 不含上下文信息,可以用宏定义 QT_MESSAGELOGCONTEXT 开启,pro 文件加上:

DEFINES += QT_MESSAGELOGCONTEXT

1.最简单的操作

一个最简单的示例如下,重定向到文件:

#include <QApplication>
#include <QMutex>
#include <QMutexLocker>
#include <QFile>
#include <QTextStream>
#include <QDebug>
 
//重定向qdebug输出到文件
void myMessageHandle(QtMsgType , const QMessageLogContext& , const QString& msg)
{
    static QMutex mut; //多线程打印时需要加锁
    QMutexLocker locker(&mut);
    QFile file("log.txt");
    if(file.open(QIODevice::WriteOnly|QIODevice::Append))
    {
        QTextStream stream(&file);
        stream<<msg<<endl;
        file.close();
    }
}
 
int main()
{
    //设置重定向操作的函数
    qInstallMessageHandler(myMessageHandle);
 
    qDebug()<<"Test debug111";
    qDebug()<<"Test debug222";
 
    return 0;
}

运行结果

在这里插入图片描述

2.实现一个简易的日志管理类

需求很简单,同时输出到界面中的编辑框、文件、控制台。

代码链接:https://github.com/gongjianbo/SimpleQtLogger

运行效果(图片为旧版截图):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输出到控制台,我是保存了调用 qInstallMessageHandler 时返回的函数指针,然后调用进行默认的输出。

输出到文件,因为函数调用发生在 qDebug() 调用的线程,所以需要加锁。

输出到界面,我使用了信号槽的方式,将文本发送给 connect 的槽。

此外,增加了按日期和文件大小来新建文件的逻辑。

当然,还可以添加一些细节,如日志等级的控制等。

下面是部分源码:

#include <QApplication>
 
#include "LogManager.h"
#include "mainwindow.h"
 
int main(int argc, char *argv[])
{
    LogManager::getInstance()->initManager();//初始化
 
    QApplication a(argc, argv);
 
    MainWindow w;
    w.show();
 
    return a.exec();
}
#pragma once
#include <QObject>
#include <QFile>
#include <QMutex>
#include <QDebug>
 
/**
 * @brief 简易的日志管理类,作为单例
 * @author 龚建波 - https://github.com/gongjianbo
 * @date 2020-08-13
 * @details
 * 1.初始化时调用 initManager 重定向 QDebug 输出
 * 析构时自动调用 freeManager,也可以手动调用 freeManager
 * 2.根据时间戳每天重新生成一个文件,超过文件大小也会重新生成
 */
class LogManager : public QObject
{
    Q_OBJECT
    Q_DISABLE_COPY_MOVE(LogManager)
    LogManager();
public:
    ~LogManager();
 
    // 获取单例实例
    static LogManager *getInstance();
    // 获取带 html 样式标签的富文本
    Q_INVOKABLE static QString richText(int msgType, const QString &log);
 
    // 初始化,如重定向等
    void initManager(const QString &dir = QString());
    // 释放
    void freeManager();
 
    // 文件最大大小,超过则新建文件,单位字节
    qint64 getFileSizeLimit() const;
    void setFileSizeLimit(qint64 limit);
 
private:
    // 重定向到此接口
    static void outputHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
    // 获取重定向的打印信息,在静态函数种回调该接口
    void outputLog(QtMsgType type, const QMessageLogContext &context, const QString &msg);
    // 计算下一次生成文件的时间
    qint64 calcNextTime() const;
    // 每次写入时判断是否打开,是否需要新建文件
    void prepareFile();
 
signals:
    // 可以关联信号接收日志信息,如显示到 ui 中
    // 注意,如果槽函数为 lambda 或者其他没有接收者的情况,需要保证槽函数中的变量有效性
    // 因为 static 变量的生命周期更长,可能槽函数所在模块已经释放资源,最好 connect 加上接收者
    void newLog(int msgType, const QString &log);
 
private:
    // 保留默认 handle,用于输出到控制台
    QtMessageHandler defaultOutput = nullptr;
 
    // 输出到文件
    QFile logFile;
    // 输出路径
    QString logDir;
    // 多线程操作时需要加锁
    mutable QMutex logMutex;
 
    // 下一次生成文件的时间戳,单位毫秒
    qint64 fileNextTime{ 0 };
    // 文件最大大小,超过则新建文件,单位字节
    qint64 fileSizeLimit{ 1024 * 1024 * 32 };
};
#include "LogManager.h"
#include <QCoreApplication>
#include <QDir>
#include <QThread>
#include <QTextStream>
#include <QDateTime>
 
LogManager::LogManager()
{
 
}
 
LogManager::~LogManager()
{
    freeManager();
}
 
LogManager *LogManager::getInstance()
{
    // 单例,初次调用时实例化
    static LogManager instance;
    return &instance;
}
 
QString LogManager::richText(int msgType, const QString &log)
{
    QString log_text;
    QTextStream stream(&log_text);
    switch (msgType) {
    case QtDebugMsg: stream << "<span style='color:green;'>"; break;
    case QtInfoMsg: stream << "<span style='color:blue;'>"; break;
    case QtWarningMsg: stream << "<span style='color:gold;'>"; break;
    case QtCriticalMsg: stream << "<span style='color:red;'>"; break;
    case QtFatalMsg: stream << "<span style='color:red;'>"; break;
    default: stream << "<span style='color:red;'>"; break;
    }
    stream << log << "</span>";
    return log_text;
}
 
void LogManager::initManager(const QString &dir)
{
    QMutexLocker locker(&logMutex);
 
    // 保存路径
    logDir = dir;
    if (logDir.isEmpty())
    {
        // 用到了 QCoreApplication::applicationDirPath(),需要先实例化一个app
        if (qApp) {
            logDir = qApp->applicationDirPath() + "/log";
        } else {
            int argc = 0;
            QCoreApplication app(argc,nullptr);
            logDir = app.applicationDirPath() + "/log";
        }
    }
 
    // 计算下次创建文件的时间点
    fileNextTime = calcNextTime();
    // 重定向qdebug到自定义函数
    defaultOutput = qInstallMessageHandler(LogManager::outputHandler);
}
 
void LogManager::freeManager()
{
    QMutexLocker locker(&logMutex);
 
    logFile.close();
    if (defaultOutput) {
        qInstallMessageHandler(defaultOutput);
        defaultOutput = nullptr;
    }
}
 
qint64 LogManager::getFileSizeLimit() const
{
    return fileSizeLimit;
}
 
void LogManager::setFileSizeLimit(qint64 limit)
{
    fileSizeLimit = limit;
}
 
void LogManager::outputHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    // 转发给单例的成员函数
    LogManager::getInstance()->outputLog(type, context, msg);
}
 
void LogManager::outputLog(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    // widget 中的 log,context.category = default
    // qml 中的 log,context.category = qml,此时默认的 output 会增加一个 "qml:" 前缀输出
    // fprintf(stderr, "print: type = %d, category = %s \n", type, context.category);
 
    // 如果要写文件需要加锁,因为函数调用在 debug 调用线程
    QMutexLocker locker(&logMutex);
 
    QString out_text;
    QTextStream stream(&out_text);
 
    // 时间
    stream << QDateTime::currentDateTime().toString("[yyyy-MM-dd hh:mm:ss]");
    // 日志类型
    switch (type) {
    case QtDebugMsg: stream << "[Debug]"; break;
    case QtInfoMsg: stream << "[Info]"; break;
    case QtWarningMsg: stream << "[Warning]"; break;
    case QtCriticalMsg: stream << "[Critical]"; break;
    case QtFatalMsg: stream << "[Fatal]"; break;
    default: stream << "[Unknown]"; break;
    }
    // 线程 id
    stream << "[" << QThread::currentThreadId() << "]";
    // 输出位置
    stream << "[" << context.file << ":" << context.line << "]";
    // 日志信息
    stream << msg;
 
    // 判断是否需要打开或者新建文件
    prepareFile();
    if (logFile.isOpen()) {
        // 写入文件
        stream.setDevice(&logFile);
        stream << out_text << Qt::endl;
    }
 
    // 发送信号给需要的对象,如 ui 上显示日志
    emit newLog(type, out_text);
 
    // 默认的输出,控制台
    // 区分日志类型给文本加颜色
    // 常见格式为:\e[显示方式;背景颜色;前景文字颜色m << 输出字符串 << \e[0m
    // 其中 \e=\033
    // -----------------
    // 背景色  字体色
    // 40:    30:    黑
    // 41:    31:    红
    // 42:    32:    绿
    // 43:    33:    黄
    // 44:    34:    蓝
    // 45:    35:    紫
    // 46:    36:    深绿
    // 47:    37:    白
    // -----------------
    QString cmd_text;
    stream.setString(&cmd_text);
    switch (type) {
    case QtDebugMsg: // debug 绿色
        stream << "\033[32m"; break;
    case QtInfoMsg: // info 蓝色
        stream << "\033[34m"; break;
    case QtWarningMsg: // warning 黄色
        stream << "\033[33m"; break;
    case QtCriticalMsg: // critical 红字
        stream << "\033[31m"; break;
    case QtFatalMsg: // fatal 黑底红字
        // qFatal 表示致命错误,默认处理会报异常的
        stream << "\033[0;31;40m"; break;
    default: // defualt 默认颜色
        stream << "\033[0m"; break;
    }
    stream << out_text << "\033[0m";
    defaultOutput(type, context, cmd_text);
}
 
qint64 LogManager::calcNextTime() const
{
    // 可以参考 spdlog 的 daily_file_sink 优化,这里先用 Qt 接口进行实现
    return QDate::currentDate().addDays(1).startOfDay().toMSecsSinceEpoch();
}
 
void LogManager::prepareFile()
{
    // 写入文件
    // 先计算好下一次生成文件的时间点,然后和当前进行比较,这里没有考虑调节系统日期的情况
    if (fileNextTime <= QDateTime::currentDateTime().toMSecsSinceEpoch()){
        logFile.close();
        // 计算下次创建文件的时间点
        fileNextTime = calcNextTime();
    }
    // 文件超过了大小
    if (logFile.isOpen() && logFile.size() >= fileSizeLimit) {
        logFile.close();
    }
    // 生成文件名,打开文件
    if (!logFile.isOpen()) {
        // 创建文件前创建目录,QFile 不会自动创建不存在的目录
        QDir dir(logDir);
        if (!dir.exists()) {
            dir.mkpath(logDir);
        }
        // 文件日期
        QString file_day = QDate::currentDate().toString("yyyyMMdd");
        QString file_path = QString("%1/log_%2.txt").arg(logDir).arg(file_day);
        logFile.setFileName(file_path);
        if (logFile.exists() && logFile.size() >= fileSizeLimit) {
            QString file_time = QTime::currentTime().toString("hhmmss");
            file_path = QString("%1/log_%2_%3.txt").arg(logDir).arg(file_day).arg(file_time);
            logFile.setFileName(file_path);
        }
        // 打开新的文件
        // Append 追加模式,避免同一文件被清除
        if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
            emit newLog(QtWarningMsg, "Open log file error:" + logFile.errorString() + logFile.fileName());
        }
    }
}

Chapter2 Qt::Qt Log日志模块

原文链接:https://blog.csdn.net/u011218356/article/details/103344231

Qt Log日志模块

简介
这几天在交接工作,把之前手头的一个项目交接一下,想着增加一个日志模块,去查了一下,Qt自带的日志模块 qInstallMessageHandler 。

qInstallMessageHandler–说明
qInstallMessageHandler 位于 - Global Qt Declarations下边,属于全局函数。

官方解释

QtMessageHandler qInstallMessageHandler(QtMessageHandler handler)

Installs a Qt message handler which has been defined previously. Returns a pointer to the previous message handler.
The message handler is a function that prints out debug messages, warnings, critical and fatal error messages. The Qt library (debug mode) contains hundreds of warning messages that are printed when internal errors (usually invalid function arguments) occur. Qt built in release mode also contains such warnings unless QT_NO_WARNING_OUTPUT and/or QT_NO_DEBUG_OUTPUT have been set during compilation. If you implement your own message handler, you get total control of these messages.
The default message handler prints the message to the standard output under X11 or to the debugger under Windows. If it is a fatal message, the application aborts immediately.
Only one message handler can be defined, since this is usually done on an application-wide basis to control debug output.
To restore the message handler, call qInstallMessageHandler(0).

官方解释主要说了 qInstallMesageHandler 的作用,其实主要是是对 打印消息的控制,能控制打印的输出,调试,信息,警告,严重,致命错误等五个等级。

官方Demo

  #include <qapplication.h>
  #include <stdio.h>
  #include <stdlib.h>

  void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
  {
      QByteArray localMsg = msg.toLocal8Bit();
      switch (type) {
      case QtDebugMsg:
          fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
          break;
      case QtInfoMsg:
          fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
          break;
      case QtWarningMsg:
          fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
          break;
      case QtCriticalMsg:
          fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
          break;
      case QtFatalMsg:
          fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
          abort();
      }
  }

  int main(int argc, char **argv)
  {
      qInstallMessageHandler(myMessageOutput);
      QApplication app(argc, argv);
      ...
      return app.exec();
  }

源码

思路

日志模块思路如下:
1.读取日志配置文件,设置文件输出等级。可以用做,在正式项目中调试与日常关键信息打印。
2.Log消息输出方法–输出文本。
3.消息模块注册(个人理解)

源码
枚举变量–设置日志等级 0~4 5个等级

enum{
    Fatal = 0,
    Critical,
    Warning,
    Info,
    Debug
}LogLeaver;

int LogType = 4; //日志等级初始化设置
//初始化读取配置文件
void ReadLogInit()
{
    QFile file ("./log_conf.ini");
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)){//判断文件是否可执行
        return;
    }
    while (!file.atEnd()) {
        QByteArray  strBuf = file.readLine();
        if(strBuf == "[LOG_CONFIG]\n")
        {
            strBuf = file.readLine();
            LogType = strBuf.mid(strBuf.size()-1,1).toInt();
        }
    }
}
//配置文件格式
[LOG_CONFIG]
LogLeaver=4
void message_output(QtMsgType type,const QMessageLogContext &context,const QString &msg)
{
	//加锁:避免对文件的同时读写
    static QMutex mutex;
    mutex.lock();
    //读写消息
    QByteArray localMsg = msg.toLocal8Bit();
    //输出的字符串
    QString strOutStream = "";
    //case 生成要求格式日志文件,加日志等级过滤
    switch (type) {
          case QtDebugMsg:
              if(LogType == Debug)
              {
                  strOutStream = QString("%1 %2 %3 %4 [Debug] %5 \n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(QString(context.file)).arg(context.line).arg(QString(context.function)).arg(QString(localMsg));
              }
              break;
          case QtInfoMsg:
              if(LogType >= Info)
              {
                  strOutStream = QString("%1 %2 %3 %4 [Info]: %5 \n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(QString(context.file)).arg(context.line).arg(QString(context.function)).arg(QString(localMsg));
              }
              break;
          case QtWarningMsg:
              if(LogType >= Warning)
              {
                  strOutStream = QString("%1 %2 %3 %4 [Warning]: %5 \n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(QString(context.file)).arg(context.line).arg(QString(context.function)).arg(QString(localMsg));
              }
              break;
          case QtCriticalMsg:
              if(LogType >= Critical)
              {
                  strOutStream = QString("%1 %2 %3 %4 [Critical]: %5 \n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(QString(context.file)).arg(context.line).arg(QString(context.function)).arg(QString(localMsg));
              }
              break;
          case QtFatalMsg:
              if(LogType >= Fatal)
              {
                  strOutStream = QString("%1 %2 %3 %4 [Fatal]: %5 \n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(QString(context.file)).arg(context.line).arg(QString(context.function)).arg(QString(localMsg));
              }
              abort();
          }
	//每天生成一个新的log日志文件,文件名 yyyyMMdd.txt
     QString strFileName = QString("%1.txt").arg(QDateTime::currentDateTime().date().toString("yyyyMMdd"));
     QFile logfile(strFileName);
     logfile.open(QIODevice::WriteOnly | QIODevice::Append);
     if(strOutStream != "")
     {
         QTextStream logStream(&logfile);
         logStream<<strOutStream<<"\r\n";
     }
	//清楚缓存文件,解锁
     logfile.flush();
     logfile.close();
     mutex.unlock();
}
int main(int argc, char *argv[])
{
    ReadLogInit();//读取日志等级
    qInstallMessageHandler(message_output);//安装消息处理函数,依靠回调函数,重定向,全局处理
    QApplication a(argc, argv);
    qInfo()<<"\r\n\r\n\r\n**PCCamera start**";
    //to doing......
}

Chapter3 QT日志模块的个性化使用

原文链接:https://blog.csdn.net/yang1fei2/article/details/125210633

在开发QT程序的时候,很多开发者也就仅仅用QT的日志模块qDebug一下调试信息,在真正的日志记录上还是采用一些别的日志库。其实QT的日志模块还是很强大的,可以满足日常的基本需求。这里就详细介绍一下QT日志模块的个性化使用方法。

格式化日志输出

默认情况下,日志格式是只输出对应的日志内容没有额外信息的。我们可以通过修改环境变量QT_MESSAGE_PATTERN或者调用方法 qSetMessagePattern来修改日志的输出格式。日志格式中常用的占位符号如下所示:

%{appname}     应用程序的名称(QCoreApplication::applicationName())
%{category}    日志所处的领域
%{file}        打印该日志的文件路径 
%{function}    打印日志的函数
%{line}        打印日志在文件中的行数
%{message}     日志的内容
%{pid}         打印日志的程序的PID(QCoreApplication::applicationPid())
%{threadid}    打印日志的线程ID
%{qthreadptr}  打印日志的线程指针
%{type}        日志级别("debug", "warning", "critical" or "fatal")
%{time process}日志发生时程序启动了多久
%{time boot}   日志发生时系统启动了多久
%{time [format]}以固定时间格式输出日志打印的时间,默认为QISODate格式

格式化日志的调用方法如下:

int main(int argc,char*argv[])
{
    QCoreApplication app(argc, argv);
    qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss}--[%{type}]--%{function}:%{message}");
    qDebug() << "exception occured";
    qInfo() <<  "call other function";
    return app.exec();
}

输出的日志内容格式如下:

2022-06-09 10:09:54--[debug]--main:exception occured
2022-06-09 10:09:55--[info]--main:call other function

我们还可以使用条件变量

%{if-debug}, %{if-info} %{if-warning}, %{if-critical} or %{if-fatal}

给不同级别的日志指定不同的格式,使用方法如下:

int main(int argc,char*argv[])
{
    QCoreApplication app(argc, argv);
    //针对Warning信息和Fatal信息输出了额外的内容
    qputenv("QT_MESSAGE_PATTERN", QByteArray("%{time yyyy-MM-dd hh:mm:ss} [%{type}]%{if-warning}[%{function}]%{endif}%{if-fatal}[%{function}--%{line}]%{endif}:%{message}"));
    qDebug() << "debuginfo occured";
    qInfo() <<  "call other function";
    qWarning() << "doesn't work";
    qFatal("fatal error");
   return app.exec();
}

输出的日内容如下:

2022-06-09 10:48:32 [debug]:debuginfo occured
2022-06-09 10:48:32 [info]:call other function
2022-06-09 10:48:32 [warning][main]:doesn't work
2022-06-09 10:48:32 [fatal][main--116]:fatal error

我们可以通过修改QT_MESSAGE_PATTERN环境变量动态的修改日志的输出格式。如果同时调用qSetMessagePattern和QT_MESSAGE_PATTERN环境变量来修改日志的输出格式,那么QT_MESSAGE_PATTERN的优先级要高于qSetMessagePattern。

输出日志到文本

QT默认的日志内容是输出到终端的,不会输出到文件里面,如果需要将日志内容输出到文件中我们需要通过qInstallMessageHandler设置日志信息处理函数。使用方法如下:

//日志消息的处理函数
void logmessageHander(QtMsgType type, const QMessageLogContext& context, const QString& message)
{
    //获取格式化的日志信息
    QString typeStr = qFormatLogMessage(type,context,message);
    //可以根据日志的级别进行过滤
    QString levelText;
    switch (type) {
        case QtDebugMsg:
            levelText = "Debug";
            break;
        case QtInfoMsg:
            levelText = "Info";
            break;
        case QtWarningMsg:
            levelText = "Warning";
            break;
        case QtCriticalMsg:
            levelText = "Critical";
            break;
        case QtFatalMsg:
            levelText = "Fatal";
            break;
    }
    QFile file("myapp.log");
    file.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream textStream(&file);
    textStream << typeStr << endl;
}
int main(int argc,char*argv[])
{
    QCoreApplication app(argc, argv);
    qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} [%{type}]%{if-warning}[%{function}]%{endif}%{if-fatal}[%{function}--%{line}]%{endif}:%{message}");
    qInstallMessageHandler(logmessageHander);
    qDebug() << "debuginfo occured";
    qInfo() <<  "call other function";
    qWarning() << "doesn't work";
    qFatal("fatal error");
    return app.exec();
}

如果需要关闭日志输出,取消之前注册的日志处理函数,我们可以调用

//取消注册的日志处理函数
qInstallMessageHandler(0);

日志输出对象信息

在调试一些复杂对象的时候,我们需要输出对象的成员信息到日志当中。但是默认情况下qt的日志库是不支持输出自定义对象的。这时候我们可以通过重写操作符实现对自定义象的日志输出。使用方法如下:

//消费者信息类
struct Customer {
    QString name;
    int age;
    QString clientid;
    QString addr;
};
 
QDebug operator<<(QDebug debug, const Customer& customer)
{
    //保存QDebug的状态
    QDebugStateSaver saver(debug);
    debug.nospace() << "("
                    << "name: " << customer.name << ","
                    << "age: " << customer.age  << ","
                    << "clientid: " << customer.clientid  << ","
                    << "addr: " << customer.addr  << ","
                    << ")";
    return debug;
}
 
int main(int argc,char*argv[])
{
    QCoreApplication app(argc, argv);
    Customer customer = { "Liming", 22, "677888", "北京市海淀区"};
    qDebug() << "Customer info" << customer;
    return app.exec();
}

QDebugStateSaver类可以保存QDebug的配置,并在销毁的时候自动恢复。使用它我们可以避免在操作符重载的时候破坏QDebug中的配置。

Chapter4 Qt 自定义日志类($$$)

https://blog.csdn.net/qq_45662588/article/details/116259372

Chapter5 简单易用的Qt日志模块($$$)

原文链接:https://blog.csdn.net/lm409/article/details/74908484

引言

项目中需求一日志模块,主要实现两大功能:1.自动打印信息至日志文件;2.软件意外退出时保留信息以便跟踪问题。
本文结合了 Qt 自定义日志工具 和 让程序在崩溃时体面的退出之CallStack 提供的方法,补充实现了文章中未具体给出的管理日志文件大小和数量的功能。

环境:vs2012+Qt5.2(注:Qt5.5之后引入qInfo(),影响不大)

一、日志实现方法

基本原理是使用 qInstallMessageHandler()接管qDebug(), qWarning()等调试信息,然后将信息流存储至本地日志文件,管理日志文件。
代码在原作者基础上做了部分调整:
1.更改日志存储名称格式,用QDateTime取代QDate,以避免当日记录多条日志时的覆盖问题;
2.增加日志文件个数的判断;
3.增加日志文件大小的检测;
4.屏蔽根据修改日期保存日志机制,以免在不同日期开启软件后冲掉以前的有用log,仅凭文件大小另存log文件,然后控制文件数量。

代码实现

LogHandler.cpp

#include "LogHandler.h"

#include <stdio.h>
#include <stdlib.h>
#include <QDebug>
#include <QDateTime>
#include <QMutexLocker>
#include <QtGlobal>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTimer>
#include <QTextStream>
#include <iostream>

#define LOGLIMIT_NUM 5  //日志文件存档个数
#define LOGLIMIT_SIZE 500   //单个日志文件存档大小限制,单位KB
/************************************************************************************************************
 *                                                                                                          *
 *                                               LogHandlerPrivate                                          *
 *                                                                                                          *
 ***********************************************************************************************************/
struct LogHandlerPrivate {
    LogHandlerPrivate();
    ~LogHandlerPrivate();

    // 打开日志文件 protocal.log,如果日志文件不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd.log,并重新创建一个 protocal.log
    void openAndBackupLogFile();

    // 消息处理函数
    static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);

    // 如果日志所在目录不存在,则创建
    void makeSureLogDirectory() const;

    // 检测当前日志文件大小
    void checkLogFiles();

    QDir   logDir;              // 日志文件夹
    QTimer renameLogFileTimer;  // 重命名日志文件使用的定时器
    QTimer flushLogFileTimer;   // 刷新输出到日志文件的定时器
    QDateTime  logFileCreatedDate;  // 日志文件创建的时间

    static QFile *logFile;      // 日志文件
    static QTextStream *logOut; // 输出日志的 QTextStream,使用静态对象就是为了减少函数调用的开销
    static QMutex logMutex;     // 同步使用的 mutex
};

// 初始化 static 变量
QMutex LogHandlerPrivate::logMutex;
QFile* LogHandlerPrivate::logFile = NULL;
QTextStream* LogHandlerPrivate::logOut = NULL;

LogHandlerPrivate::LogHandlerPrivate() {
    logDir.setPath("Log"); // TODO: 日志文件夹的路径,为 exe 所在目录下的 log 文件夹,可从配置文件读取
    QString logPath = logDir.absoluteFilePath("protocal.log"); // 日志的路径
    // 日志文件创建的时间
    // QFileInfo::created(): On most Unix systems, this function returns the time of the last status change.
    // 所以不能运行时使用这个函数检查创建时间,因为会在运行时变化,所以在程序启动时保存下日志文件创建的时间
    logFileCreatedDate = QFileInfo(logPath).lastModified();
    //QString temp= logFileCreatedDate.toString("yyyy-MM-dd hh:mm:ss");

    // 打开日志文件,如果不是当天创建的,备份已有日志文件
    openAndBackupLogFile();

    // 五分钟检查一次日志文件创建时间
    renameLogFileTimer.setInterval(1000 * 60 * 5); // TODO: 可从配置文件读取
    //renameLogFileTimer.setInterval(1000*60); // 为了快速测试看到日期变化后是否新创建了对应的日志文件,所以 1 分钟检查一次
    renameLogFileTimer.start();
    QObject::connect(&renameLogFileTimer, &QTimer::timeout, [this] {
        QMutexLocker locker(&LogHandlerPrivate::logMutex);
        openAndBackupLogFile();
    });

    // 定时刷新日志输出到文件,尽快的能在日志文件里看到最新的日志
    flushLogFileTimer.setInterval(1000); // TODO: 可从配置文件读取
    flushLogFileTimer.start();
    QObject::connect(&flushLogFileTimer, &QTimer::timeout, [this] {
        // qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); // 测试不停的写入内容到日志文件
        QMutexLocker locker(&LogHandlerPrivate::logMutex);
//         if (NULL != logOut) {
//             logOut->flush();
//         }
        checkLogFiles();//每秒检查一次文件是否超过限制大小
    });
}

LogHandlerPrivate::~LogHandlerPrivate() {
    if (NULL != logFile) {
        logFile->flush();
        logFile->close();
        delete logOut;
        delete logFile;

        // 因为他们是 static 变量
        logOut  = NULL;
        logFile = NULL;
    }
}

// 打开日志文件 protocal.log,如果不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd_hhmmss.log,并重新创建一个 protocal.log
void LogHandlerPrivate::openAndBackupLogFile() {
    // 总体逻辑:
    // 1. 程序启动时 logFile 为 NULL,初始化 logFile,有可能是同一天打开已经存在的 logFile,所以使用 Append 模式
    // 2. logFileCreatedDate is null, 说明日志文件在程序开始时不存在,所以记录下创建时间
    // 3. 程序运行时检查如果 logFile 的创建日期和当前日期不相等,则使用它的创建日期重命名,然后再生成一个新的 protocal.log 文件
    // 4. 检查日志文件超过 LOGLIMIT_NUM 个,删除最早的

    makeSureLogDirectory(); // 如果日志所在目录不存在,则创建
    QString logPath = logDir.absoluteFilePath("protocal.log"); // 日志的路径

    // [[1]] 程序启动时 logFile 为 NULL
    if (NULL == logFile) {
        logFile = new QFile(logPath);
        logOut  = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) ?  new QTextStream(logFile) : NULL;

        if (NULL != logOut) {
            logOut->setCodec("UTF-8");
        }

        // [[2]] 如果文件是第一次创建,则创建日期是无效的,把其设置为当前日期
        if (logFileCreatedDate.isNull()) {
            logFileCreatedDate = QDateTime::currentDateTime();
        }
    }

    // [[3]] 程序运行时如果创建日期不是当前日期,则使用创建日期重命名,并生成一个新的 protocal.log
    //不使用该特性,以免在不同日期开启软件后冲掉以前的有用log,仅凭文件大小另存log文件,见checkLogFiles
//     if (logFileCreatedDate.date() != QDate::currentDate()) {
//         logFile->flush();
//         logFile->close();
//         delete logOut;
//         delete logFile;
// 
//         QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("yyyy-MM-dd_hhmmss.log"));;
//         QFile::copy(logPath, newLogPath); // Bug: 按理说 rename 会更合适,但是 rename 时最后一个文件总是显示不出来,需要 killall Finder 后才出现
//         QFile::remove(logPath); // 删除重新创建,改变创建时间
// 
//         logFile = new QFile(logPath);
//         logOut  = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ?  new QTextStream(logFile) : NULL;
//         logFileCreatedDate = QDateTime::currentDateTime();
// 
//         if (NULL != logOut) {
//             logOut->setCodec("UTF-8");
//         }
//  }

    // [[4]] 检查日志文件超过 LOGLIMIT_NUM 个,删除最早的
    logDir.setFilter(QDir::Files);
    logDir.setNameFilters(QStringList() << "*.log");//根据文件后缀过滤日志文件
    QFileInfoList logFiles = logDir.entryInfoList();
    for (int i = 0; i < logFiles.length() - LOGLIMIT_NUM; ++i)
        QFile::remove(logFiles[i].absoluteFilePath());

    //根据文件名称进一步过滤
    //  QMap<QDateTime, QString> fileDates;
    //  for (int i = 0; i < logFiles.length(); ++i)
    //  {
    //      QString name = logFiles[i].baseName();
    //      QDateTime fileDateTime = QDateTime::fromString(name, "yyyy-MM-dd");
    // 
    //      if (fileDateTime.isValid())
    //          fileDates.insert(fileDateTime, logFiles[i].absoluteFilePath());
    //  }
    //  QList<QString> fileDateNames = fileDates.values();
    //  for (int i = 0; i < fileDateNames.length() - LOGFILESLIMIT; ++i)
    //      QFile::remove(fileDateNames[i]);        
}

// 如果日志所在目录不存在,则创建
void LogHandlerPrivate::makeSureLogDirectory() const {
    if (!logDir.exists()) {
        logDir.mkpath("."); // 可以递归的创建文件夹
    }
}

// 检测当前日志文件大小
void LogHandlerPrivate::checkLogFiles() {
    // 如果 protocal.log 文件大小超过5M,重新创建一个日志文件,原文件存档为yyyy-MM-dd_hhmmss.log
    if (logFile->size() > 1024*LOGLIMIT_SIZE) {
        logFile->flush();
        logFile->close();
        delete logOut;
        delete logFile;

        QString logPath = logDir.absoluteFilePath("protocal.log"); // 日志的路径
        QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("yyyy-MM-dd_hhmmss.log"));;
        QFile::copy(logPath, newLogPath); // Bug: 按理说 rename 会更合适,但是 rename 时最后一个文件总是显示不出来,需要 killall Finder 后才出现
        QFile::remove(logPath); // 删除重新创建,改变创建时间

        logFile = new QFile(logPath);
        logOut  = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ?  new QTextStream(logFile) : NULL;
        logFileCreatedDate = QDateTime::currentDateTime();

        if (NULL != logOut) {
            logOut->setCodec("UTF-8");
        }
    }
}

// 消息处理函数
void LogHandlerPrivate::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
    QMutexLocker locker(&LogHandlerPrivate::logMutex);
    QString level;

    switch (type) {
    case QtDebugMsg:
        level = "Debug";
        break;
//     case QtInfoMsg://This function was introduced in Qt 5.5.
//         level = "Info ";
//         break;
    case QtWarningMsg:
        level = "Warning";
        break;
    case QtCriticalMsg:
        level = "Error";
        break;
    case QtFatalMsg:
        level = "Fatal";
        break;
    default:;
    }

    // 输出到标准输出
    QByteArray localMsg = msg.toLocal8Bit();
    //std::cout << std::string(localMsg) << std::endl;

    if (NULL == LogHandlerPrivate::logOut) {
        return;
    }

    // 输出到日志文件, 格式: 时间 - [Level] (文件名:行数, 函数): 消息
    QString fileName = context.file;
    int index = fileName.lastIndexOf(QDir::separator());
    fileName = fileName.mid(index + 1);

    (*LogHandlerPrivate::logOut) << QString("%1 - [%2] (%3:%4): %5\n")
                                    .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(level)
                                    .arg(fileName).arg(context.line)/*.arg(context.function)*/.arg(msg);
    logOut->flush();//直接刷新到文件
}

/************************************************************************************************************
 *                                                                                                          *
 *                                               LogHandler                                                 *
 *                                                                                                          *
 ***********************************************************************************************************/
LogHandler::LogHandler() : d(NULL) {
}

LogHandler::~LogHandler() {
}

void LogHandler::installMessageHandler() {
    QMutexLocker locker(&LogHandlerPrivate::logMutex);

    if (NULL == d) {
        d = new LogHandlerPrivate();
        qInstallMessageHandler(LogHandlerPrivate::messageHandler); // 给 Qt 安装自定义消息处理函数
    }
}

void LogHandler::release() {
    QMutexLocker locker(&LogHandlerPrivate::logMutex);
    qInstallMessageHandler(0);
    delete d;
    d = NULL;
}

二、崩溃处理

让程序在崩溃时体面的退出之总结博主在系列文章中做了详尽的说明。
我的应用目的是在程序崩溃时能体面退出,然后记录基本的CallStack信息到日志文件,所以只用到了前面两部分内容。在上文的基础上,用qCritical()或其他方法输出Crash信息和CallStack信息即可。

代码实现

LogHandler.cpp

//程式异常捕获
LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException){
    /*
      ***保存数据代码***
    */
    // 创建Dump文件目录
    QDir   DumpDir; 
    DumpDir.setPath("Log");
    LPCWSTR DumpPath = (const wchar_t*) DumpDir.absoluteFilePath("ProtocolTester.dmp").utf16();// Dump文件的路径
    CreateDumpFile(DumpPath, pException);  

    // 确保有足够的栈空间
#ifdef _M_IX86  
    if (pException->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)  
    {  
        static char TempStack[1024 * 128];  
        __asm mov eax,offset TempStack[1024 * 128];  
        __asm mov esp,eax;  
    }  
#endif    

    CrashInfo crashinfo = GetCrashInfo(pException->ExceptionRecord);  

    // 输出Crash信息
    qCritical() << "ErrorCode: " << crashinfo.ErrorCode << endl;  
    qCritical() << "Address: " << crashinfo.Address << endl;  
    qCritical() << "Flags: " << crashinfo.Flags << endl;  

    vector<CallStackInfo> arrCallStackInfo = GetCallStack(pException->ContextRecord);  

    // 输出CallStack
    qCritical() << "CallStack: " << endl;  
    for (vector<CallStackInfo>::iterator i = arrCallStackInfo.begin(); i != arrCallStackInfo.end(); ++i)  
    {  
        CallStackInfo callstackinfo = (*i);  

        qCritical() << callstackinfo.MethodName << "() : [" << callstackinfo.ModuleName << "] (File: " << callstackinfo.FileName << " @Line " << callstackinfo.LineNumber << ")" << endl;  
    }
    //这里弹出一个错误对话框并退出程序
    EXCEPTION_RECORD* record = pException->ExceptionRecord;
    QString errCode(QString::number(record->ExceptionCode,16)),errAdr(QString::number((uint)record->ExceptionAddress,16)),errMod;
    QMessageBox::critical(NULL,QStringLiteral("Error"),QStringLiteral("<FONT size=4><div><b>很抱歉,程序出错了。</b><br/></div>")+
        QStringLiteral("<div>错误代码:%1</div><div>错误地址:%2</div></FONT>").arg(errCode).arg(errAdr),
        QMessageBox::Ok);
    return EXCEPTION_EXECUTE_HANDLER;
}

小结

本文实现了一个轻量的Qt日志模块,功能肯定是没有log4qt或log4cxx等强大,但也基本满足了项目应用需求,想了解log4qt也可以查看DevBean豆子大神的github

让程序在崩溃时体面的退出之CallStack

Chapter6 Qt/C++开源作品39-日志输出增强版V2022($$$)

原文链接:https://blog.csdn.net/feiyangqingyun/article/details/121314920

一、前言

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

用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的,而且提供友好的接口。

二、主要功能

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

三、效果图

在这里插入图片描述

四、开源主页

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

五、核心代码

#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();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1135857.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3 创建项目

1 桌面打开 CMD窗口&#xff0c;使用vue create 项目名创建项目。 2 选择Manually select features 自定义配置 3 选择配置&#xff1a;使用空格确认/取消&#xff0c;回车确定&#xff1a; Babel 降级处理 CSS Pre-processors CSS预处理器 Linter/Formatter 代码检查 4 选择vu…

「滚雪球学Java」:基础程序设计(章节汇总)

&#x1f3c6;本文收录于「滚雪球学Java」专栏&#xff0c;专业攻坚指数级提升&#xff0c;助你一臂之力&#xff0c;带你早日登顶&#x1f680;&#xff0c;欢迎大家关注&&收藏&#xff01;持续更新中&#xff0c;up&#xff01;up&#xff01;up&#xff01;&#xf…

Vue3:将表格数据下载为excel文件

需求 将表格数据或者其他形式的数据下载为excel文件 技术栈 Vue3、ElementPlus、 实现 1、安装相关的库 下载xlsx 和 file-saver 库 npm install -S file-saver npm install -S xlsx引入XLSX库和FileSaver库 import XLSX from xlsx; import FileSaver from file-saver;…

华为OD机考算法题:高效的任务规划

题目部分 题目高效的任务规划难度难题目说明 你有 n 台机器编号为 1 ~ n&#xff0c;每台都需要完成一项工作&#xff0c; 机器经过配置后都能独立完成一项工作。 假设第 i 台机器你需要花 分钟进行设置&#xff0c; 然后开始运行&#xff0c; 分钟后完成任务。 现在&#x…

虹科培训 | 虹科携手PLCopen开展IEC 61131-3国际工程师培训

文章来源&#xff1a;虹科工业控制 阅读原文&#xff1a;https://mp.weixin.qq.com/s/MLYhBWiWx7qQSApx_3xhmA &#xff08;一&#xff09;课程背景 什么是IEC 61131-3&#xff1f; IEC 61131-3 是工业自动化行业唯一得到大量应用的组态编程语言国际标准&#xff1b;主导制定…

安卓端GB28181设备接入模块如何实现实时位置订阅(MobilePosition)

技术背景 实时位置&#xff08;MobilePosition&#xff09;订阅和上报&#xff0c;对GB28281设备接入终端尤其重要&#xff0c;如移动单兵设备、执法记录仪、智能安全帽、车载终端等&#xff0c;Android国标接入设备通过获取到实时经纬度信息&#xff0c;按照一定的间隔上报到…

引入二维码技术,易点易动全员盘点方案助力高效海量资产盘点

固定资产是企业重要的财务资源之一&#xff0c;而高效准确地进行海量固定资产盘点一直是企业管理的重要挑战。为了解决盘点过程中的繁琐和错误问题&#xff0c;易点易动固定资产管理系统引入了先进的二维码技术&#xff0c;并采用全员盘点方案。本文将详细介绍易点易动固定资产…

单目3D目标检测[基于深度辅助篇]

基于深度辅助的方法 1. Pseudo-LiDAR Pseudo-LiDAR from Visual Depth Estimation: Bridging the Gap in 3D Object Detection for Autonomous Driving康奈尔大学https://zhuanlan.zhihu.com/p/52803631 首先利用DRON或PSMNET从单目 (Monocular)或双目 (Stereo)图像获取对应的…

海外公司注册推广的9个实用技巧建议-华媒舍

在全球化的时代背景下&#xff0c;海外市场的开发对于企业来说是非常重要的战略决策。海外公司注册是进入海外市场的第一步&#xff0c;通过注册在海外的公司&#xff0c;企业可以获得更多的商业机会和巨大的价值。本篇文章将为您介绍海外公司注册推广的9个实用建议&#xff0c…

干洗店预约下单管理系统收衣开单拍照必备软件

随着生活水平的提高和节奏的加快&#xff0c;商务人士的衣物越来越多&#xff0c;但精力和时间却越来越少。于是&#xff0c;干洗店应运而生&#xff0c;在中国&#xff0c;几乎所有的中心城市干洗店都门庭若市。若每人每月需要干洗一套服装&#xff0c;一个城市每月则需干洗50…

Postgresql在jdbc处理bit字段的解决方案

问题&#xff1a; bit如果长度为1&#xff0c;则会默认为布尔型&#xff08;1-true 0-false&#xff09;&#xff1b; bit如果长度大于1&#xff0c;则会默认为bit类型&#xff0c;但是代码中以前常用的两种set方式&#xff0c;会报错 第一种方式&#xff1a; ps.setObject(i1,…

vm_flutter

附件地址 https://buuoj.cn/match/matches/195/challenges#vm_flutter 可以在buu下载到。 flutter我也不会&#xff0c;只是这个题目加密算法全部在java层&#xff0c;其实就是一个异或和相加。 反编译 package k;import java.util.Stack;/* loaded from: classes.dex */ pu…

传智教育研究院重磅发布Java学科新研发《智慧养老》项目

在招聘Java开发人才的过程中&#xff0c;企业往往对候选人的项目经验有着严格的要求&#xff0c;项目经验成为顺利就业的重要敲门砖之一。而在数字化技术的学习中&#xff0c;如何让学员通过项目课程有效地积累实战开发经验&#xff0c;就成了数字化技术职业教育的一个重大难点…

EasyRecovery2024破解版数据恢复软件下载

当我们处理重要的文件数据时&#xff0c;遇到突然停电导致数据来不及保存&#xff0c;再次打开电脑后&#xff0c;此前处理的数据可能丢失&#xff0c;这无疑会影响我们的工作进度&#xff0c;数据恢复软件在此时就派上用场&#xff0c;那么下面就来具体介绍EasyRecovery软件的…

EasyRecovery2024破解版激活码

当我们处理重要的文件数据时&#xff0c;遇到突然停电导致数据来不及保存&#xff0c;再次打开电脑后&#xff0c;此前处理的数据可能丢失&#xff0c;这无疑会影响我们的工作进度&#xff0c;数据恢复软件在此时就派上用场&#xff0c;那么下面就来具体介绍EasyRecovery软件的…

5G与医疗:开启医疗技术的新篇章

5G与医疗&#xff1a;开启医疗技术的新篇章 随着5G技术的快速发展和普及&#xff0c;它已经在医疗领域产生了深远的影响。5G技术为医疗行业提供了更高效、更准确、更及时的通信方式&#xff0c;从而改变了医疗服务的模式和患者的体验。本文将探讨5G技术在医疗领域的应用场景、优…

10月《中国数据库行业分析报告》已发布,深度剖析甲骨文大会Oracle技术新趋势

为了帮助大家及时了解中国数据库行业发展现状、梳理当前数据库市场环境和产品生态等情况&#xff0c;从2022年4月起&#xff0c;墨天轮社区行业分析研究团队出品将持续每月为大家推出最新《中国数据库行业分析报告》&#xff0c;持续传播数据技术知识、努力促进技术创新与行业生…

k8s快速部署nacos2.2.0集群

nacos2.2.0集群部署。nacos-headless内部集群端口服务&#xff0c;nacos-service为了方便ingress转发提供给用户web界面操作&#xff0c;requiredDuringSchedulingIgnoredDuringExecution强制反亲和禁止同一个节点部署nacos实列。 1、数据库导入nacos的sql # 创建数据库 crea…

一文详解多模态大模型发展及高频因子计算加速GPU算力 | 英伟达显卡被限,华为如何力挽狂澜?

★深度学习、机器学习、多模态大模型、深度神经网络、高频因子计算、GPT-4、预训练语言模型、Transformer、ChatGPT、GenAI、L40S、A100、H100、A800、H800、华为、GPU、CPU、英伟达、NVIDIA、卷积神经网络、Stable Diffusion、Midjourney、Faster R-CNN、CNN 随着人工智能技术…

Java 音频处理,音频流转音频文件,获取音频播放时长

1.背景 最近对接了一款智能手表&#xff0c;手环&#xff0c;可以应用与老人与儿童监控&#xff0c;环卫工人监控&#xff0c;农场畜牧业监控&#xff0c;宠物监控等&#xff0c;其中用到了音频传输&#xff0c;通过平台下发语音包&#xff0c;发送远程命令录制当前设备音频并…