【Qt 学习之路】C++日志库 - 18种方法选型(读这一篇就够了)

06-08 1452阅读

文章目录

  • 1、前言
  • 2、归纳
  • 3、典型
    • 3.1、qInstallMessageHandler
      • 3.1.1、简介
      • 3.1.2、示例
      • 3.1.3、思路
      • 3.1.4、使用
      • 3.1.5、案例
      • 3.2、Glog
        • 3.2.1、语法
        • 3.2.2、下载
        • 3.2.3、配置
        • 3.2.4、使用
        • 3.2.5、相关
        • 3.3、Easylogging++
          • 3.3.1、要领
          • 3.3.2、兼容
          • 3.3.3、注意
          • 3.3.4、示例

            1、前言

            最近有时间研究下C++ 的记录日志的方法,包括各个方法的简述、使用特点、适用场景、推荐指数等。本文调研常见的 18 种 记录日志方法(包含:qInstallMessageHandler、Glog、Easylogging++、Qslog、Log4qt、Log4cpp、Log4cxx、Log4c、Log4cplus、Pantheios POCO、ACE、Boost.Log、G3log、Plog、spdlog、Rsyslog等),后续添加了笔者最推荐的 3种 典型方法的要领思路、使用方法和示例等,倾注了很多精力。希望此文不要沉底,请多多支持,谢谢~

            2、归纳

            序号名称简述使用特点适用场景官网最新更新推荐指数
            1qInstallMessageHandlerQt 亲儿子,可安装自定义的日志输出处理函数,把日志输出到文件,控制台等,可查看 Qt 的帮助文档。Release 版本默认不包含文件名、函数名和行数信息,需要在 .pro 文件中加入代码。Qt5.0 及以上版本 跨平台开发场景地址随 Qt 不断升级⭐⭐⭐⭐⭐
            2Glog谷歌 亲儿子,一个 C++ 语言的应用级日志记录框架,提供了 C++ 风格的流操作和各种辅助宏常常与Breakpad结合跨平台使用地址2022-04-05⭐⭐⭐⭐⭐
            3Easylogging++巨简单。轻量级高性能 C++ 日志库,只需要一个头文件,无需任何外部依赖。支持文件配置,提供了强大的自定义日志格式的能力,还提供对第三方库,STL容器的支持几乎所有主流平台和编译器都支持地址2023-07-20⭐⭐⭐⭐
            4Log4cplus一个基于 Log4j简单易用的 C++ 日志记录 API,提供了对日志管理和配置的线程安全、灵活和任意粒度控制使用 C++ 跨平台开发场景地址2023-11-17⭐⭐⭐⭐
            5Qslog基于Qt的轻量级开源日志库,使用Qt的QDebug类的易于使用的记录器。QsLog是在麻省理工学院许可下以开源形式发布的适用于Qt的多库集成场景地址2023-06-21⭐⭐⭐⭐
            6Log4cppLGPL的开源项目,是基于 Log4j的 C++ 类库,可以灵活地记录到文件、syslog、IDSA 和其他目的地适用 C++ 跨平台开发场景地址2023-03-12⭐⭐⭐
            7Pantheios号称 C/C++ 领域速度最快的程序诊断日志库,一类型安全、高效、泛型和可扩展性的 C++ 日志 API 库适用于对速度有特别追求的场景地址2020-07-05⭐⭐⭐
            8PlogPlog 是一个 C++ 日志库,其设计尽可能简单、小巧且灵活,非常小。它是作为现有大型库的替代方案而创建的,并提供一些独特的功能,如CSV 日志格式和宽字符串支持适用于对系统空间有要求的场景地址2023-08-20⭐⭐⭐
            9RsyslogLinux原生日志系统Rsyslog,可用于接受来自各种来源的输入,做处理后将结果输出到不同的目的地适用于纯Linux场景地址2023-02-13⭐⭐⭐
            10ACE是一个很强大的跨平台的网络通信中间件,ACE日志输出的本身格式很类似log4cpp的日志格式适用于中间件跨平台场景地址2023-12-14⭐⭐⭐
            11spdlog主打高性能与易用性。一个快速的 C++ 日志库,只包含头文件,是一个高速异步日志库,支持多线程和旋转文件日志适用于高负载的系统地址2023-07-09⭐⭐⭐
            12G3log一个开源、支持跨平台的异步 C++ 日志框架,支持自定义日志格式。基于 g2log 构建,提升了性能,支持自定义格式适用于对日志记录格式有特别要求的场景地址2023-12-07⭐⭐⭐
            13Log4qt用Trolltech Qt Framework的Apache Software Foundation Log4j包的C ++端口,它旨在供开源和商业Qt项目使用适用于老版本Qt4地址2009-03-01⭐⭐
            14Poco.Log提供了好的日志支持文档。Poco库借助了C++库强大的网络和系统支持,易于集成到其他应用程序中,支持多个日志记录器和过滤器适用于服务端和客户端的日志模块地址2023-12-05⭐⭐
            15Log4c基本上都是一些纯c的东西,移植性比Log4cxx、Log4cpp要好使用 C 较多的场景地址2016-01-30⭐⭐
            16log.cC 语言的日志功能模块,代码简洁,就一个 .c 和 .h 文件,一共 200 行,设计优雅使用 C 较多的场景地址2016-01-30⭐⭐
            17Boost.Log由Boost库提供的日志记录工具,支持多个不同的后端日志器,可以定制多种记录格式适用于任何Boost项目地址2023-12-14
            18Log4cxxJava 社区著名的 Log4j 的 C++ 移植版,用于为 C++ 程序提供日志功能,以便开发者对目标程序进行调试和审计,需要依赖于APR,不能和Qt的qDebug和qInfo等联调适用于仅在Linux的Apache环境项目地址2023-05-01

            3、典型

            3.1、qInstallMessageHandler

            3.1.1、简介

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

            【Qt 学习之路】C++日志库 - 18种方法选型(读这一篇就够了)

            QtMessageHandler qInstallMessageHandler(QtMessageHandler handler)
            

            qInstallMessageHandler 的作用 主要是是对 打印消息的控制,能控制打印的输出,调试,信息,警告,严重,致命错误等五个等级。

            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).

            3.1.2、示例

            官方示例

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

            3.1.3、思路

            • 读取日志配置文件,设置文件输出等级,可以用做在正式项目中调试与日常关键信息打印。
            • Log消息输出方法–输出文本。
            • 消息模块注册

              3.1.4、使用

              枚举变量–设置日志等级 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
                  ReadLogInit();//读取日志等级
                  qInstallMessageHandler(message_output);//安装消息处理函数,依靠回调函数,重定向,全局处理
                  QApplication a(argc, argv);
                  qInfo()
                  LogHandlerPrivate();
                  ~LogHandlerPrivate();
                  // 打开日志文件 log.txt,如果日志文件不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd.log,并重新创建一个 log.txt
                  void openAndBackupLogFile();
                  void checkLogFiles(); // 检测当前日志文件大小
                  void autoDeleteLog(); // 自动删除30天前的日志
                  // 消息处理函数
                  static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
                  QDir   logDir;              // 日志文件夹
                  QTimer renameLogFileTimer;  // 重命名日志文件使用的定时器
                  QTimer flushLogFileTimer;   // 刷新输出到日志文件的定时器
                  QDate  logFileCreatedDate;  // 日志文件创建的时间
                  static QFile *logFile;      // 日志文件
                  static QTextStream *logOut; // 输出日志的 QTextStream,使用静态对象就是为了减少函数调用的开销
                  static QMutex logMutex;     // 同步使用的 mutex
              };
              class LogHandler {
              public:
                  void installMessageHandler();   // 给Qt安装消息处理函数
                  void uninstallMessageHandler(); // 取消安装消息处理函数并释放资源
                  static LogHandler& Get() {
                      static LogHandler m_logHandler;
                      return m_logHandler;
                  }
              private:
                  LogHandler();
                  LogHandlerPrivate *d;
              };
              #endif // LOGHANDLER_H
              
                  logDir.setPath("log"); // TODO: 日志文件夹的路径,为 exe 所在目录下的 log 文件夹,可从配置文件读取
                  QString logPath = logDir.absoluteFilePath("today.log"); // 获取日志的路径
                  // ========获取日志文件创建的时间========
                  // QFileInfo::created(): On most Unix systems, this function returns the time of the last status change.
                  // 所以不能运行时使用这个函数检查创建时间,因为会在运行时变化,于是在程序启动时保存下日志文件的最后修改时间,
                  logFileCreatedDate = QFileInfo(logPath).lastModified().date(); // 若日志文件不存在,返回nullptr
                  // 打开日志文件,如果不是当天创建的,备份已有日志文件
                  openAndBackupLogFile();
                  // 十分钟检查一次日志文件创建时间
                  renameLogFileTimer.setInterval(1000 *  2); // TODO: 可从配置文件读取
                  renameLogFileTimer.start();
                  QObject::connect(&renameLogFileTimer, &QTimer::timeout, [this] {
                      QMutexLocker locker(&LogHandlerPrivate::logMutex);
                      openAndBackupLogFile(); // 打开日志文件
                      checkLogFiles(); // 检测当前日志文件大小
                      autoDeleteLog(); // 自动删除30天前的日志
                  });
                  // 定时刷新日志输出到文件,尽快的能在日志文件里看到最新的日志
                  flushLogFileTimer.setInterval(1000); // TODO: 可从配置文件读取
                  flushLogFileTimer.start();
                  QObject::connect(&flushLogFileTimer, &QTimer::timeout, [] {
                      // qDebug() 
                          logOut-flush();
                      }
                  });
              }
              LogHandlerPrivate::~LogHandlerPrivate() {
                  if (nullptr != logFile) {
                      logFile-flush();
                      logFile-close();
                      delete logOut;
                      delete logFile;
                      // 因为他们是 static 变量
                      logOut  = nullptr;
                      logFile = nullptr;
                  }
              }
              // 打开日志文件 log.txt,如果不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd.log,并重新创建一个 log.txt
              void LogHandlerPrivate::openAndBackupLogFile() {
                  // 总体逻辑:
                  // 1. 程序启动时 logFile 为 nullptr,初始化 logFile,有可能是同一天打开已经存在的 logFile,所以使用 Append 模式
                  // 2. logFileCreatedDate is nullptr, 说明日志文件在程序开始时不存在,所以记录下创建时间
                  // 3. 程序运行时检查如果 logFile 的创建日期和当前日期不相等,则使用它的创建日期重命名,然后再生成一个新的 log.txt 文件
                  // 4. 检查日志文件超过 LOGLIMIT_NUM 个,删除最早的
                  // 备注:log.txt 始终为当天的日志文件,当第二天,会执行第3步,将使用 log.txt 的创建日期重命名它
                  // 如果日志所在目录不存在,则创建
                  if (!logDir.exists()) {
                      logDir.mkpath("."); // 可以递归的创建文件夹
                  }
                  QString logPath = logDir.absoluteFilePath("today.log"); // log.txt的路径
                  // [[1]] 程序每次启动时 logFile 为 nullptr
                  if (logFile == nullptr) {
                      logFile = new QFile(logPath);
                      logOut  = (logFile-open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) ?  new QTextStream(logFile) : nullptr;
                      if (logOut != nullptr)
                          logOut-setCodec("UTF-8");
                      // [[2]] 如果文件是第一次创建,则创建日期是无效的,把其设置为当前日期
                      if (logFileCreatedDate.isNull()) {
                          logFileCreatedDate = QDate::currentDate();
                      }
                  }
                  // [[3]] 程序运行时如果创建日期不是当前日期,则使用创建日期重命名,并生成一个新的 log.txt
                  if (logFileCreatedDate != QDate::currentDate()) {
                      logFile-flush();
                      logFile-close();
                      delete logOut;
                      delete logFile;
                      QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("yyyy-MM-dd.log"));;
                      QFile::copy(logPath, newLogPath); // Bug: 按理说 rename 会更合适,但是 rename 时最后一个文件总是显示不出来,需要 killall Finder 后才出现
                      QFile::remove(logPath); // 删除重新创建,改变创建时间
                      // 重新创建 log.txt
                      logFile = new QFile(logPath);
                      logOut  = (logFile-open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ?  new QTextStream(logFile) : nullptr;
                      logFileCreatedDate = QDate::currentDate();
                      if (logOut != nullptr)
                          logOut->setCodec("UTF-8");
                  }
              }
              // 检测当前日志文件大小
              void LogHandlerPrivate::checkLogFiles() {
                  // 如果 protocal.log 文件大小超过5M,重新创建一个日志文件,原文件存档为yyyy-MM-dd_hhmmss.log
                  if (logFile->size() > 1024*g_logLimitSize) {
                      logFile->flush();
                      logFile->close();
                      delete logOut;
                      delete logFile;
                      QString logPath = logDir.absoluteFilePath("today.log"); // 日志的路径
                      QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("yyyy-MM-dd.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 = QDate::currentDate();
                      if (logOut != nullptr)
                          logOut->setCodec("UTF-8");
                  }
              }
              // 自动删除30天前的日志
              void LogHandlerPrivate::autoDeleteLog()
              {
                  QDateTime now = QDateTime::currentDateTime();
                  // 前30天
                  QDateTime dateTime1 = now.addDays(-30);
                  QDateTime dateTime2;
                  QString logPath = logDir.absoluteFilePath("today.log"); // 日志的路径
                  QDir dir(logPath);
                  QFileInfoList fileList = dir.entryInfoList();
                  foreach (QFileInfo f, fileList ) {
                      // "."和".."跳过
                      if (f.baseName() == "")
                          continue;
                      dateTime2 = QDateTime::fromString(f.baseName(), "yyyy-MM-dd");
                      if (dateTime2 fromUnicode(msg); //msg.toLocal8Bit();
              #else
                  QByteArray localMsg = msg.toLocal8Bit();
              #endif
                  std::cout 
                      return;
                  }
                  // 输出到日志文件, 格式: 时间 - [Level] (文件名:行数, 函数): 消息
                  QString fileName = context.file;
                  int index = fileName.lastIndexOf(QDir::separator());
                  fileName = fileName.mid(index + 1);
                  (*LogHandlerPrivate::logOut) 
              }
              // 给Qt安装消息处理函数
              void LogHandler::installMessageHandler() {
                  QMutexLocker locker(&LogHandlerPrivate::logMutex); // 类似C++11的lock_guard,析构时自动解锁
                  if (nullptr == d) {
                      d = new LogHandlerPrivate();
                      qInstallMessageHandler(LogHandlerPrivate::messageHandler); // 给 Qt 安装自定义消息处理函数
                  }
              }
              // 取消安装消息处理函数并释放资源
              void LogHandler::uninstallMessageHandler() {
                  QMutexLocker locker(&LogHandlerPrivate::logMutex);
                  qInstallMessageHandler(nullptr);
                  delete d;
                  d = nullptr;
              }
              
                  QApplication app(argc, argv);
                  // [[1]] 安装消息处理函数
                  LogHandler::Get().installMessageHandler();
                  // [[2]] 输出测试,查看是否写入到文件
                  qDebug() 
                      qDebug() 
                google::INFO = 0,
                google::WARNING = 1,
                google::ERROR = 2,
                google::FATAL = 3,
              };
              
                  FLAGS_logtostderr = true;
                  FLAGS_colorlogtostderr = true;//是否启用不同颜色显示(如果终端支持)
                  google::InitGoogleLogging(argv[0]);//使用glog之前必须先初始化库,仅需执行一次,括号内为程序名
                  //google::SetLogDestination(google::GLOG_INFO, "E:\\logs\\INFO_");//INFO级别的日志都存放到logs目录下且前缀为INFO_
                  LOG(INFO) 

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]