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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯 第 2 场算法双周赛 第3题 摆玩具【算法赛】 c++ 贪心

题目 摆玩具【算法赛】https://www.lanqiao.cn/problems/5888/learning/?contest_id145 问题描述 小蓝是一个热爱收集玩具的小伙子&#xff0c;他拥有 n 个不同的玩具。 这天&#xff0c;他把 n 个玩具按照高度顺序从矮到高摆放在了窗台上&#xff0c;然后&#xff0c;他希…

阿里云对象存储OSS文件无法预览,Bucket设置了Referer

您发起的请求头中没有Referer字段或Referer字段为空&#xff0c;与请求Bucket设置的防盗链策略不相符。 解决方案 您可以选择以下任意方案解决该问题。 在请求中增加Referer请求头。 GET /test.txt HTTP/1.1 Date: Tue, 20 Dec 2022 08:48:18 GMT Host: BucketName.oss-examp…

Docker GitLab-Runner安装

Docker GitLab-Runner安装 GitLab-Runner安装 问题合集GitLab 域名的配置修改Runner容器内注册失败&#xff0c;提示 dial tcp: lookup home.zsl0.com on 192.168.254.2:53: no such host GitLab-Runner 安装 拉去gitlab/gitlab-runner镜像 docker pull gitlab/gitlab-runne…

汽车行驶性能的主观评价方法(1)-底盘校准方法

底盘校准的目的是&#xff0c;从行驶性能和行驶舒适性两个方面进行协调&#xff0c;从而优化行驶动力学特性。为了达到这一目标&#xff0c;工程人员早在设计阶段&#xff0c;就对大多数对行驶动力性有重要意义的部件提出了要求。这些要求不仅与底盘的组件有关&#xff0c;还必…

轻量封装WebGPU渲染系统示例<2>-彩色立方体(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/version-1.01/src/voxgpu/sample/VertColorCube.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 2. 高频调用与低频调用隔离。 3. 面向用户的易用性封装。 4. 渲染数据和渲染机制分离。 5. …

超宽带技术在汽车领域的应用

随着科技的不断发展&#xff0c;超宽带&#xff08;Ultra-Wideband, UWB&#xff09;技术在各个领域展现出了强大的潜力&#xff0c;其中汽车领域更是受益匪浅。UWB技术以其高精度的定位能力、高速的数据传输和低功耗的特点&#xff0c;为汽车行业带来了许多创新。本文将探讨UW…

20.1 OpenSSL 字符BASE64压缩算法

OpenSSL 是一种开源的加密库&#xff0c;提供了一组用于加密和解密数据、验证数字证书以及实现各种安全协议的函数和工具。它可以用于创建和管理公钥和私钥、数字证书和其他安全凭据&#xff0c;还支持SSL/TLS、SSH、S/MIME、PKCS等常见的加密协议和标准。 OpenSSL 的功能非常…

安卓开发实例:方向传感器

调用手机的方向传感器&#xff0c;X轴&#xff0c;Y轴&#xff0c;Z轴的数值 activity_sensor.xml <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayoutxmlns:android"http://schemas.android.c…

Git Gui使用技巧

资料 https://www.runoob.com/w3cnote/git-gui-window.html 操作过程 创建仓库→添加远程仓库→扫描目录→文件移动→提交→上传 注意填注释 文件忽略 创建文件.gitignore→编写内容 *.log #文件 config.ini #文件 temp/ #目录

Linux--安装与配置虚拟机及虚拟机服务器坏境配置与连接---超详细教学

一&#xff0c;操作系统介绍 1.1.什么是操作系统 操作系统&#xff08;Operating System&#xff0c;简称OS&#xff09;是一种系统软件&#xff0c;它是计算机硬件和应用软件之间的桥梁。它管理计算机的硬件和软件资源&#xff0c;为应用程序提供接口和服务&#xff0c;并协调…

spark

spark Spark可以将Hadoop集群中的应用在内存中的运行速度提升100倍&#xff0c;甚至能够将应用在磁盘上的运行速度提升10倍。除了Map和Reduce操作之外&#xff0c;Spark还支持SQL查询&#xff0c;流数据&#xff0c;机器学习和图表数据处理。开发者可以在一个数据管道用例中单独…

面试总结之消息中间件

RabbitMQ的消息如何实现路由 RabbitMQ是一个基于AMQP协议实现的分布式消息中间件&#xff0c;AMQP具体的工作机制是生产者将消息发送到RabbitMQ Broker上的Exchange交换机上&#xff0c;Exchange交换机将收到的消息根据路由规则发给绑定的队列&#xff08;Queue&#xff09;&am…

汇总区间(Java)

大家好我是苏麟 , 这篇文章也是凑数的 ... 描述 : 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 n…

C++之特殊类的设计

目录 一、单例模式 1、设计模式 2、单例模式 1、饿汉模式 2、懒汉模式 3、单例对象的释放问题 二、设计一个不能被拷贝的类 三、设计一个只能在堆上创建对象的类 四、设计一个只能在栈上创建对象的类 五、设计一个不能被继承的类 一、单例模式 1、设计模式 概念&am…

二分归并法将两个数组合并

#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> main() {int a[5] {1,3,4,5,6};int b[4] {2,7,8,9};int c[9];int m0, n0,k0;while (m < 5 && n < 4){if (a[m] < b[n]){c[k] a[m];//谁小谁先进数组m; k;}else{c[k] b[n];k; n;}}while (m <…

【Java从入门到大牛】特殊文本文件和日志技术

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Java从入门到大牛 &#x1f320; 首发时间&#xff1a;2023年10月27日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f4…

【黑马程序员】mysql进阶篇笔记

2023年10月27日17:50:07 58.01. 进阶-课程介绍(Av765670802,P58) 59.02. 进阶-存储引擎-MySQL体系结构(Av765670802,P59) 60.03. 进阶-存储引擎-简介(Av765670802,P60) 61.04. 进阶-存储引擎-InnoDB介绍(Av765670802,P61) 62.05. 进阶-存储引擎-MyISAM和Memory(Av765670802…

【计算机毕设小程序案例】基于微信小程序的图书馆座位预定系统

前言&#xff1a;我是IT源码社&#xff0c;从事计算机开发行业数年&#xff0c;专注Java领域&#xff0c;专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务 &#x1f449;IT源码社-SpringBoot优质案例推荐&#x1f448; &#x1f449;IT源码社-小程序优质案例…

Visual Studio Professional 2019 软件安装教程(附安装包下载)

Microsoft Visual Studio 是一个非常强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;适用于 Windows 上的 .NET 和 C 开发人员。它提供了一系列丰富的工具和功能&#xff0c;可以提升和增强软件开发的每个阶段。 Visual Studio IDE 是一个创意启动板&#xff0c;可…

百度文心一言4.0抢先体验教程!

&#x1f341; 展望&#xff1a;关注我, AI学习之旅上&#xff0c;我与您一同成长&#xff01; 一、 引言 想快速体验文心一言4.0&#xff0c;但又觉得技术难度太高&#xff1f;别担心&#xff0c;我来手把手教你&#xff01; &#x1f680; 10月17日&#xff0c;文心一言4.0…