Qt+QtWebApp开发笔记(二):http服务器日志系统介绍、添加日志系统至Demo测试

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 上一篇使用QtWebApp的基于Qt的轻量级http服务器实现了一个静态网页返回的Demo,网页服务器很重要的就是日志,因为在服务器类上并没有直接返回,所以,本篇先把日志加上。

前言

  上一篇使用QtWebApp的基于Qt的轻量级http服务器实现了一个静态网页返回的Demo,网页服务器很重要的就是日志,因为在服务器类上并没有直接返回,所以,本篇先把日志加上。


Demo

  

下载地址

  链接:https://pan.baidu.com/s/1BPVRLS07qk-WPi-txERKbg?pwd=1234


日志系统

生产环境需要查看旧的日志消息,例如两天前的日志消息。

  可以简单地将输出重定向到一个文件(MyFirstWebApp>logfile.txt),但这有两个问题:

  • 在许多系统上,输出重定向有些慢。
  • 日志文件将变得无限大,如果不短时间停止web服务器,就无法防止这种情况发生。

  因此,最好让web服务器自己将所有消息写入文件。这就是记录器模块的作用。

  要将日志模块的源代码包括到项目中,请在项目文件中添加一行:

include(../QtWebApp/QtWebApp/logging/logging.pri)

  这个而模块也是QtWebApp的logging模块,如下:

  

  然后在程序的*.ini文件中添加另一个部分:

[logging]
minLevel=WARNING
bufferSize=100
fileName=../logs/webapp1.log
maxSize=1000000
maxBackups=2
timestampFormat=dd.MM.yyyy hh:mm:ss.zzz
msgFormat={timestamp} {typeNr} {type} {thread} {msg}

  日志级别有:DEBUG(别名ALL)、INFO、WARN或WARNING、CRITICAL(别名ERROR)、FATAL。信息级别由Qt 5.5引入。

  上面的示例配置启用线程本地缓冲区,**这些缓冲区将不太重要的消息保留在内存中,直到出现警告或严重错误。**然后,错误消息与收集到的低级消息一起写入日志文件。只要一切正常,使用缓冲区可以大大减少日志文件的大小。像这样的系统操作员。

  但是,缓冲区的内存和性能成本都很高。收益通常大于成本。要禁用缓冲区,请将bufferSize设置为0。在这种情况下,只有配置了minLevel及以上级别的消息才会写入日志文件。

  如果没有指定文件名,则记录器会写入控制台。日志文件的路径可以是绝对路径,也可以是相对于配置文件的文件夹的路径。maxSize参数限制日志文件的大小(以字节为单位)。当超过此限制时,记录器将启动一个新文件。设置maxBackups指定磁盘上应保留多少旧日志文件。

  时间戳格式设置的作用。QDateTime::toString()的文档以获得对字符的解释,还有更多可用的内容。msgFormat设置指定每条消息的格式。以下字段可用:

  • {timestamp}:创建日期和时间
  • {typeNr}:数字格式的消息类型或级别(0=DEBUG, 4=INFO, 1=WARNING, 2=CRITICAL, 3=FATAL)
  • {type}:字符串格式的消息类型或级别(DEBUG, INFO, WARNING, CRITICAL, FATAL)
  • {thread}:线程的ID号
  • {msg}:消息文本
  • {xxx}:可以自己定义的任何记录器变量QT 5.0及更新版本在调试模式下有一些附加变量:
{file}:Filename of source code where the message was generated
{function}:Function where the message was generated
{line}:Line number where the message was generated

  Qt开发人员将这三个字段添加到他们的框架中。也可以使用\n在消息格式中插入换行符和插入  制表符。上述所有变量也可以在日志消息中使用,例如:

qCritical("An error occured in {file}: out of disk space");

  需要一个指向FileLogger实例的全局指针,以便整个程序都可以访问它。首先添加到global.h:

#include "httpsessionstore.h"
#include "staticfilecontroller.h"
#include "templatecache.h"
#include "filelogger.h"
using namespace stefanfrings;
/** Storage for session cookies */
extern HttpSessionStore* sessionStore;
/** Controller for static files */
extern StaticFileController* staticFileController;
/** Cache for template files */
extern TemplateCache* templateCache;
/** Redirects log messages to a file */
extern FileLogger* logger;
#endif // GLOBAL_H

  global.cpp:

#include "global.h"
HttpSessionStore* sessionStore;
StaticFileController* staticFileController;
TemplateCache* templateCache;
FileLogger* logger;

  在main.cpp中,配置FileLogger的实例:

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    QString configFileName=searchConfigFile();
    // Configure logging
    QSettings* logSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    logSettings->beginGroup("logging");
    logger=new FileLogger(logSettings,10000,&app);
    logger->installMsgHandler();
    // Log the library version
    qDebug("QtWebApp has version %s",getQtWebAppLibVersion());
    // Session store
    QSettings* sessionSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    sessionSettings->beginGroup("sessions");
    sessionStore=new HttpSessionStore(sessionSettings,&app);
    // Static file controller
    QSettings* fileSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    fileSettings->beginGroup("files");
    staticFileController=new StaticFileController(fileSettings,&app);
    // Configure template cache
    QSettings* templateSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    templateSettings->beginGroup("templates");
    templateCache=new TemplateCache(templateSettings,&app);
    // HTTP server
    QSettings* listenerSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    listenerSettings->beginGroup("listener");
    new HttpListener(listenerSettings,new RequestMapper(&app),&app);
    return app.exec();
}

  数字10000是以毫秒为单位的刷新间隔,记录器使用它来重新加载配置文件。因此,可以在程序运行时编辑任何记录器设置,并且更改在几秒钟后生效,而无需重新启动服务器。如果不希望自动重新加载,请使用值0。

  给了一个示例代码,用于查询和记录库的版本号。一些人要求添加该功能。

不要忘记创建一个空文件夹MyFirstWebApp/logs。记录器本身不会创建文件夹。

  现在可以启动应用程序并查看会发生什么。因为程序没有错误,所以日志文件保持为空。但  可以看到控制台窗口中的输出已降至最低:

  

  让在logincontroller.cpp中插入一条qCritical()消息,然后可以看到日志缓冲区工作:

  然后打开URLhttp://localhost:8080/login?username=test&password=wrong.

  再次查看日志文件,它就在那里:

  

  现在通过将min Level降低到DEBUG来进行另一个测试。保存ini文件,等待10秒,然后打开URLhttp://localhost:8080/hello.再次检查日志文件。可以看到,尽管没有发生错误,但现在所有的调试消息都已写入。因此,在不重新启动程序的情况下更改日志级别可以很好地工作。

  

  其实这个很容易看出来,是直接对qt的几个日志等级进行了(PS:这个日志库还不错,installMsgHandler可以截断qDebug等相关的错误信息,可以直接无缝使用到每一个qt项目中,有这个兴趣可以试一试)。

  


记录器变量

  写到记录器支持用户定义的变量。这些变量是线程本地的,在清除它们之前一直保留在内存中。对于web应用程序,在每条消息中记录当前用户的名称可能很有用。向requestmapper.cpp添加代码以设置记录器变量:

void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
    QByteArray path=request.getPath();
    qDebug("RequestMapper: path=%s",path.data());
    HttpSession session=sessionStore->getSession(request,response,false);
    QString username=session.get("username").toString();
    logger->set("currentUser",username);
    ...
}

  通过这种方式,请求映射器在将请求传递给控制器类之前,为所有传入的HTTP请求查询调用用户的名称。

  现在可以修改ini文件以使用该变量:

msgFormat={timestamp} {typeNr} {type} {thread} User:{currentUser} {msg}

  运行程序并打开URLhttp://localhost:8080/login?username=test&password=hello两次。然后再次检查日志文件:

  

  在用户登录之前,可以看到变量{currentUser}为空。然后,所有以下请求都会以该用户的名称记录。

  注意:在RequestMapper类中放置了许多静态资源(logger、sessionStore、staticFileController、templateCache)。在实际应用程序中,建议创建一个单独的类,例如名称为“Globals”的类,这样每个人都知道在哪里可以找到这样的资源。或者按照在Demo1项目中的例子,将它们放在任何类之外的cpp源文件中。

日志缓冲区和线程池

  由于线程被重新用于后续的HTTP请求,记录器可能会输出更多的细节。例如,假设第一个成功的HTTP请求会产生一些隐藏的调试消息,然后由同一线程处理的第二个请求会产生错误。然后,日志文件将包含错误消息以及所有缓冲的调试消息。但其中一些来自以前的HTTP请求,并不需要它。

  要清除两个HTTP请求之间的缓冲区,请添加到requestmapper.cpp:

void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
    ...
    else {
        response.setStatus(404,"Not found");
        response.write("The URL is wrong, no such document.");
    }
    qDebug("RequestMapper: finished request");
    logger->clear(true,true);
}

  因此,每当HTTP请求的处理完成时,都要清理记录器的内存。当同一个线程处理下一个请求时,它将以空缓冲区开始。(碰到错误则会输出到文件,所以一个http请求完成了,就是其前面的日志都是无错误,所以可以清空了)。

双文件记录器

  该项目还包含一个DualFileLogger类,可用于并行生成两个日志文件。这可能对以下设置组合有用:

  • 主记录日志文件
minLevel=INFO
bufferSize=0
  • 第二日志文件
minLevel=ERROR (or WARNING)
bufferSize=100

  这样,主日志文件就不包含调试消息。但是,当发生错误时,辅助日志文件会包含该错误以及多达100条相关的调试消息。如果错误消息本身无法识别问题原因,则此文件特别有用。

总结

  这个日志logging模块起到的最大作用,是因为在QtWebApp三方源码中的qDebug,qWarn,QFatal等相关系统直接输出到控制台的,使用该日志则截断才可以获取httpservice模块以及其他模块中的打印调试信息,而这些信息是在函数返回值中没有体现的。

  为了能查看到三方模块日志,则必须要使用logging模块,或者自己写一个模块去截断,或者直接修改三方源码中的调试信息的代码。

  使用httpservice肯定是最好使用logging模块了。


Demo增量:添加logging日志模块

步骤一:准备代码模板

  准备之前的demo模板:

  

步骤二:拷贝logging模块

  将QtWebApp中的logging,符合模块化设计准则,如下图:

  

  拷贝到的Demo

  

  添加模块进入工程:

# logging模块,QtWebApp自带的三方模块
include ($$PWD/modules/logging/logging.pri)

  

  第三方的模块。

步骤三:添加配置logging的配置文件

  先把上一篇的Demo配置文件加了listener之后就读不出的问题解决了,其实区别关键在下面:

  

  beginGroup就是进入了这一组,这一组拿到key就可以不带前缀。

  

  然后开始添加日志配置,也在httpServerManager,因为配置文件beginGroup之后就是操作单独一组了,这里从第三方源码中也可以看出来:

  

  本次加入logging,也要进行配置文件分组的区分,原来的_pSettings改成_pHttpListenerSettings,然后新增_pLoggingListenerSettings用于配置logging模块的配置实例:

步骤四:新增logging日志代码

  

  

  

步骤五:运行结果

  

  至此,日志加入成功

步骤六:日志配置调整

  

  修改下日志时间:

  

  记录日志则是:

  


Demo源码

HttpServerManager.h

#ifndef HTTPSERVERMANAGER_H
#define HTTPSERVERMANAGER_H
#include <QObject>
#include <QMutex>
#include "httplistener.h"
#include "filelogger.h"
#include "HelloWorldRequestHandler.h"
class HttpServerManager : public QObject
{
    Q_OBJECT
private:
    explicit HttpServerManager(QObject *parent = 0);
public:
    static HttpServerManager *getInstance();
public slots:
    void slot_start();                              // 开启线程
    void slot_stop();                               // 停止线程
private:
    static HttpServerManager *_pInstance;
    static QMutex _mutex;
private:
    bool _running;                                  // 运行状态
private:
    HttpListener *_pHttpListener;                   // http服务监听器
    QSettings *_pHttpListenerSettings;              // http服务器配置文件
    FileLogger *_pFileLogger;                       // 日志记录
    QSettings *_pFileLoggerSettings;                // 日志配置文件
private:
    QString _ip;                // 服务器监听ip(若为空,则表示监听所有ip)
    quint16 _port;              // 服务器监听端口
    int _minThreads;            // 空闲最小线程数
    int _maxThreads;            // 负载最大线程数
    int _cleanupInterval;       // 空线程清空间隔(单位:毫秒)
    int _readTimeout;           // 保持连接空载超时时间(单位:毫秒)
    int _maxRequestSize;        // 最大请求数
    int _maxMultiPartSize;      // 上载文件最大数(单位:字节)
};
#endif // HTTPSERVERMANAGER_H

HttpServerManager.cpp

#include "HttpServerManager.h"
#include <QApplication>
#include <QDir>
#include <QDebug>
#include <QDateTime>
//#define LOG qDebug()<<__FILE__<<__LINE__
//#define LOG qDebug()<<__FILE__<<__LINE__<<__FUNCTION__
//#define LOG qDebug()<<__FILE__<<__LINE__<<QThread()::currentThread()
//#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd")
#define LOG qDebug()<<__FILE__<<__LINE__<<QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz")
HttpServerManager *HttpServerManager::_pInstance = 0;
QMutex HttpServerManager::_mutex;
HttpServerManager::HttpServerManager(QObject *parent)
    : QObject(parent),
      _pHttpListener(0),
      _pHttpListenerSettings(0),
      _pFileLogger(0),
      _pFileLoggerSettings(0),
      _running(false),
      _port(8088),
      _minThreads(2),
      _maxThreads(10),
      _cleanupInterval(60000),
      _readTimeout(60000),
      _maxRequestSize(100),
      _maxMultiPartSize(1024*1024*1024)
{
}
HttpServerManager *HttpServerManager::getInstance()
{
    if(!_pInstance)
    {
        QMutexLocker lock(&_mutex);
        if(!_pInstance)
        {
            _pInstance = new HttpServerManager();
        }
    }
    return _pInstance;
}
void HttpServerManager::slot_start()
{
    if(_running)
    {
        LOG << "It's running!!!";
        return;
    }
    _running = true;
    LOG << "Succeed to run";
    QString httpServerPath = QString("%1/etc/httpServer.ini").arg(qApp->applicationDirPath());
    LOG << httpServerPath << "exit:" << QFile::exists(httpServerPath);
    // 启动日志几里路
    {
        if(!_pFileLoggerSettings)
        {
            _pFileLoggerSettings = new QSettings(httpServerPath, QSettings::IniFormat);
        }
        _pFileLoggerSettings->beginGroup("logging");
        // 日志不会主动创建文件夹,这里需要补全
        {
            QFileInfo fileInfo(httpServerPath);
            QString dirPath = fileInfo.dir().absolutePath();
            dirPath = QString("%1/%2")
                    .arg(dirPath)
                    .arg(_pFileLoggerSettings->value("fileName").toString());
            dirPath = dirPath.mid(0, dirPath.lastIndexOf("/"));
            QDir dir;
            dir.mkpath(dirPath);
        }
        _pFileLogger = new FileLogger(_pFileLoggerSettings);
        _pFileLogger->installMsgHandler();
    }
    // 启动http的监听
    {
        if(!_pHttpListenerSettings)
        {
            _pHttpListenerSettings = new QSettings(httpServerPath, QSettings::IniFormat);
        }
        _pHttpListenerSettings->beginGroup("listener");
        _pHttpListener = new HttpListener(_pHttpListenerSettings, new HelloWorldRequestHandler);
    }
    LOG;
}
void HttpServerManager::slot_stop()
{
    if(!_running)
    {
        LOG <<"It's not running!!!";
        return;
    }
    _running = false;
    LOG << "Succeed to stop";
}


工程模板v1.1.0

  


入坑

入坑一:日志一直不出来

问题

  日志一直不出来

原因

  

  日志log文件的路径是基于ini配置文件的相对路径

解决

  

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
21天前
|
JSON 移动开发 监控
快速上手|HTTP 接口功能自动化测试
HTTP接口功能测试对于确保Web应用和H5应用的数据正确性至关重要。这类测试主要针对后台HTTP接口,通过构造不同参数输入值并获取JSON格式的输出结果来进行验证。HTTP协议基于TCP连接,包括请求与响应模式。请求由请求行、消息报头和请求正文组成,响应则包含状态行、消息报头及响应正文。常用的请求方法有GET、POST等,而响应状态码如2xx代表成功。测试过程使用Python语言和pycurl模块调用接口,并通过断言机制比对实际与预期结果,确保功能正确性。
96 3
快速上手|HTTP 接口功能自动化测试
|
8天前
|
测试技术 持续交付 UED
软件测试的艺术与科学:平衡创新与质量的探索在软件开发的波澜壮阔中,软件测试如同灯塔,指引着产品质量的方向。本文旨在深入探讨软件测试的核心价值,通过分析其在现代软件工程中的应用,揭示其背后的艺术性与科学性,并探讨如何在追求技术创新的同时确保产品的高质量标准。
软件测试不仅仅是技术活动,它融合了创造力和方法论,是软件开发过程中不可或缺的一环。本文首先概述了软件测试的重要性及其在项目生命周期中的角色,随后详细讨论了测试用例设计的创新方法、自动化测试的策略与挑战,以及如何通过持续集成/持续部署(CI/CD)流程优化产品质量。最后,文章强调了团队间沟通在确保测试有效性中的关键作用,并通过案例分析展示了这些原则在实践中的应用。
25 1
|
6天前
|
测试技术 UED 开发者
软件测试的艺术:从代码审查到用户反馈的全景探索在软件开发的宇宙中,测试是那颗确保星系正常运转的暗物质。它或许不总是站在聚光灯下,但无疑是支撑整个系统稳定性与可靠性的基石。《软件测试的艺术:从代码审查到用户反馈的全景探索》一文,旨在揭开软件测试这一神秘面纱,通过深入浅出的方式,引领读者穿梭于测试的各个环节,从细微处着眼,至宏观视角俯瞰,全方位解析如何打造无懈可击的软件产品。
本文以“软件测试的艺术”为核心,创新性地将技术深度与通俗易懂的语言风格相结合,绘制了一幅从代码审查到用户反馈全过程的测试蓝图。不同于常规摘要的枯燥概述,这里更像是一段旅程的预告片,承诺带领读者经历一场从微观世界到宏观视野的探索之旅,揭示每一个测试环节背后的哲学与实践智慧,让即便是非专业人士也能领略到软件测试的魅力所在,并从中获取实用的启示。
|
2月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
30 0
|
23天前
|
Cloud Native Java 编译器
将基于x86架构平台的应用迁移到阿里云倚天实例云服务器参考
随着云计算技术的不断发展,云服务商们不断推出高性能、高可用的云服务器实例,以满足企业日益增长的计算需求。阿里云推出的倚天实例,凭借其基于ARM架构的倚天710处理器,提供了卓越的计算能力和能效比,特别适用于云原生、高性能计算等场景。然而,有的用户需要将传统基于x86平台的应用迁移到倚天实例上,本文将介绍如何将基于x86架构平台的应用迁移到阿里云倚天实例的服务器上,帮助开发者和企业用户顺利完成迁移工作,享受更高效、更经济的云服务。
将基于x86架构平台的应用迁移到阿里云倚天实例云服务器参考
|
21天前
|
编解码 前端开发 安全
通过阿里云的活动购买云服务器时如何选择实例、带宽、云盘
在我们选购阿里云服务器的过程中,不管是新用户还是老用户通常都是通过阿里云的活动去买了,一是价格更加实惠,二是活动中的云服务器配置比较丰富,足可以满足大部分用户的需求,但是面对琳琅满目的云服务器实例、带宽和云盘选项,如何选择更适合自己,成为许多用户比较关注的问题。本文将介绍如何在阿里云的活动中选择合适的云服务器实例、带宽和云盘,以供参考和选择。
通过阿里云的活动购买云服务器时如何选择实例、带宽、云盘
|
19天前
|
弹性计算 运维 安全
阿里云轻量应用服务器和经济型e实例区别及选择参考
目前在阿里云的活动中,轻量应用服务器2核2G3M带宽价格为82元1年,2核2G3M带宽的经济型e实例云服务器价格99元1年,对于云服务器配置和性能要求不是很高的阿里云用户来说,这两款服务器配置和价格都差不多,阿里云轻量应用服务器和ECS云服务器让用户二选一,很多用户不清楚如何选择,本文来说说轻量应用服务器和经济型e实例的区别及选择参考。
阿里云轻量应用服务器和经济型e实例区别及选择参考
|
20天前
|
机器学习/深度学习 存储 人工智能
阿里云GPU云服务器实例规格gn6v、gn7i、gn6i实例性能及区别和选择参考
阿里云的GPU云服务器产品线在深度学习、科学计算、图形渲染等多个领域展现出强大的计算能力和广泛的应用价值。本文将详细介绍阿里云GPU云服务器中的gn6v、gn7i、gn6i三个实例规格族的性能特点、区别及选择参考,帮助用户根据自身需求选择合适的GPU云服务器实例。
阿里云GPU云服务器实例规格gn6v、gn7i、gn6i实例性能及区别和选择参考
|
13天前
|
弹性计算 人工智能 安全
阿里云推出第九代ECS实例,最高提升30%性能
阿里云推出第九代ECS实例,最高提升30%性能
119 14
|
1月前
|
存储 弹性计算 运维
阿里云日常运维-购买服务器
这篇文章是关于如何在阿里云购买和配置云服务器ECS的教程。
60 6
阿里云日常运维-购买服务器
下一篇
无影云桌面