Qt+QtWebApp开发笔记(二):http服务器日志系统介绍、添加日志系统至Demo测试
前言
上一篇使用QtWebApp的基于Qt的轻量级http服务器实现了一个静态网页返回的Demo,网页服务器很重要的就是日志,因为在服务器类上并没有直接返回,所以,本篇先把日志加上。
Demo
下载地址
日志系统
生产环境需要查看旧的日志消息,例如两天前的日志消息。
可以简单地将输出重定向到一个文件(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配置文件的相对路径
解决
Qt+QtWebApp开发笔记(二):http服务器日志系统介绍、添加日志系统至Demo测试的更多相关文章
- Django开发笔记二
Django开发笔记一 Django开发笔记二 Django开发笔记三 Django开发笔记四 Django开发笔记五 Django开发笔记六 1.xadmin添加主题.修改标题页脚和收起左侧菜单 # ...
- SDL开发笔记(二):音频基础介绍、使用SDL播放音频
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- Qt+MPlayer音乐播放器开发笔记(一):ubuntu上编译MPlayer以及Demo演示
前言 在ubuntu上实现MPlayer播放器播放音乐. Demo Mplayer MPlayer是一款开源多媒体播放器,以GNU通用公共许可证发布.此款软件 ...
- 【大型软件开发】浅谈大型Qt软件开发(二)面向未来开发——来自未来的技术:COM组件。我如何做到让我们的教学模块像插件一样即插即用,以及为什么这么做。
前言 最近我们项目部的核心产品正在进行重构,然后又是年底了,除了开发工作之外项目并不紧急,加上加班时间混不够了....所以就忙里偷闲把整个项目的开发思路聊一下,以供参考. 鉴于接下来的一年我要进行这个 ...
- EasyUI 开发笔记(二)
接上篇 :EasyUI 开发笔记(一) (http://www.cnblogs.com/yiayi/p/3485258.html) 这期就简单介绍下, easyui 的 list 展示, 在easy ...
- RocketMQ初探(二)之RocketMQ3.26版本搭建(含简单Demo测试案例)
作为一名程序猿,要敢于直面各种现实,脾气要好,心态要棒,纵使Bug虐我千百遍,我待它如初恋,方法也有千万种,一条路不行,换条路走走,方向对了,只要前行,总会上了罗马的道. Apache4.x最新版本既 ...
- Qt+ECharts开发笔记(二):Qt窗口动态调整大小,使ECharts跟随Qt窗口大小变换而变换大小
前言 上一篇将ECharts嵌入Qt中,在开始ECharts使用之前,还有一个很重要的功能,就是在窗口变换大小的时候,ECharts的图表尺寸也要跟随Qt窗口变换大小而变换大小. Demo演示 ...
- Qt+ECharts开发笔记(三):ECharts的柱状图介绍、基础使用和Qt封装Demo
前言 上一篇成功是EChart随着Qt窗口变化而变化,本篇将开始正式介绍柱状图介绍.基础使用,并将其封装一层Qt. 本篇的demo实现了隐藏js代码的方式,实现了一个条形图的基本交互方式,即Qt ...
- Qt开发技术:Q3D图表开发笔记(一):Q3DScatter三维散点图介绍、Demo以及代码详解
前言 qt提供了q3d进行三维开发,虽然这个框架没有得到大量运用也不是那么成功,性能上也有很大的欠缺,但是普通的点到为止的应用展示还是可以的. 其中就包括华丽绚烂的三维图表,数据量不大的时候是可 ...
- JSP学习笔记(二):Tomcat服务器的安装及配置
一.Tomcat的下载及安装. 前往Tomcat官网下载安装包或者免安装压缩包.链接http://tomcat.apache.org/ 这里,我选择的是Tomcat8.0,而不是最新的Tomcat9. ...
随机推荐
- [转帖]文件系统读写性能fio测试方法及参数详解
简介 Fio 是一个 I/O 工具,用来对硬件进行压力测试和验证,磁盘IO是检查磁盘性能的重要指标,可以按照负载情况分成照顺序读写,随机读写两大类. Fio支持13种不同的I/O引擎,包括:sync, ...
- 01 vue子组件调用父组件中的方法
vue子组件,调用父组件中有三种方法哈!下面我们一起来讲解. 第一种使用 直接在子组件中通过this.$parent.父组件中的方法.来调用父组件的方法 第一种的缺点是不能够传递参数哈.它只能够调用方 ...
- es6数组方法find()、findIndex() filter()的总结
find()查找符合条件数组的元素(只能够找出第一个符合条件的) // 查找出大33的元素. // find查找第一个符合条件的数组元素(只查找出第一个 找不到返回undefined) // 它的参数 ...
- linux时间和当前时间相关8小时问题
依次执行如下的代码: 1.更改时区 cp /usr/share/zoneinfo/GMT /etc/localtime ln -sf /usr/share/zoneinfo/Asia/Shanghai ...
- 【OpenIM原创】简单轻松入门 一文讲解WebRTC实现1对1音视频通信原理
什么是 WebRTC ? WebRTC(Web Real-Time Communication)是 Google于2010以6829万美元从 Global IP Solutions 公司购买,并于20 ...
- 在C#或python中使用xpath解析xml
记几个笔记 文件后缀不一定要.xml,可以是任意后缀,只要内容是符合xml和格式即可解析出来 文件编码需要是utf-8,python和c#都需要,或者xml文件头有这样一句:<?xml vers ...
- 应对数据爆炸时代,揭秘向量数据库如何成为AI开发者的新宠,各数据库差异对比
应对数据爆炸时代,揭秘向量数据库如何成为AI开发者的新宠,各数据库差异对比 随着大模型的爆火,向量数据库也越发成为开发者关注的焦点.为了方便大家更好地了解向量数据库,我们特地推出了<Hello, ...
- Postfix + Extmail 企业邮件服务器搭建
ExtMail套件用于提供从浏览器中登录.使用邮件系统的Web操作界面,而Extman套件用于提供从浏览器中管理邮件系统的Web操作界面.它以GPL版权释出,设计初衷是希望设计一个适应当前高速发展的I ...
- 苹果正在测试新款Mac mini:搭载M3芯片 配备24GB大内存
据悉苹果目前正在测试新的Mac机型,亮点是采用最新的M3芯片. 据报道,首款搭载M3芯片的设备应该是13英寸的MacBook Pro和重新设计的MacBook Air,Mac mini机型并不在名单上 ...
- Docker从认识到实践再到底层原理(一)|技术架构
前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助. 高质量博客汇总 然后就是博主最近最花时间的一 ...