Qt+QtWebApp開發筆記(二):http伺服器日誌系統介紹、新增日誌系統至Demo測試

21497936發表於2023-05-19

前言

  上一篇使用QtWebApp的基於Qt的輕量級http伺服器實現了一個靜態網頁返回的Demo,網頁伺服器很重要的就是日誌,因為在伺服器類上並沒有直接返回,所以,本篇先把日誌加上。

Demo

   在這裡插入圖片描述

下載地址

  連結:

日誌系統

生產環境需要檢視舊的日誌訊息,例如兩天前的日誌訊息。

  可以簡單地將輸出重定向到一個檔案(MyFirstWebApp>logfile.txt),但這有兩個問題:

  • 在許多系統上,輸出重定向有些慢。
  • 日誌檔案將變得無限大,如果不短時間停止web伺服器,就無法防止這種情況發生。

  因此,最好讓web伺服器自己將所有訊息寫入檔案。這就是記錄器模組的作用。
  要將日誌模組的原始碼包括到專案中,請在專案檔案中新增一行:

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

  這個而模組也是QtWebApp的logging模組,如下:
   在這裡插入圖片描述
  然後在程式的*.ini檔案中新增另一個部分:

[logging]minLevel=WARNING
bufferSize=100fileName=../logs/webapp1.log
maxSize=1000000maxBackups=2timestampFormat=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()訊息,然後可以看到日誌緩衝區工作:
  然後開啟URL
  再次檢視日誌檔案,它就在那裡:
   在這裡插入圖片描述

  現在透過將min Level降低到DEBUG來進行另一個測試。儲存ini檔案,等待10秒,然後開啟URL再次檢查日誌檔案。可以看到,儘管沒有發生錯誤,但現在所有的除錯訊息都已寫入。因此,在不重新啟動程式的情況下更改日誌級別可以很好地工作。
   在這裡插入圖片描述

  其實這個很容易看出來,是直接對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}

  執行程式並開啟URL兩次。然後再次檢查日誌檔案:
   在這裡插入圖片描述

  在使用者登入之前,可以看到變數{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=INFObufferSize=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_OBJECTprivate:
    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配置檔案的相對路徑

解決

   在這裡插入圖片描述



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70010283/viewspace-2953389/,如需轉載,請註明出處,否則將追究法律責任。