1. 程式人生 > >C++之日誌列印

C++之日誌列印

一、C++日誌輸出到檔案

        眾所周知,在C++標準庫<iostream>中提供了一個控制檯(console)輸出物件——std::cout,程式設計師可以呼叫該物件將需要檢視的內容輸出到控制檯。除了std::cout外,C++標準庫<fstream>還提供了一個檔案輸出流,程式設計師可以使用它定一個流物件,如“std::ofstream fout”,並關聯一個檔案,然後像使用控制檯輸出一樣將將需要檢視的內容輸出到檔案。參考《C++ Primer Plus》第17章。

        我們在設計程式的時候,可以藉助C++標準庫的檔案流物件,程式執行的日誌輸出到檔案。具體的方法是:1)定義一個全域性的std::ofstream物件,在main()函式中將它初始化,指定關聯的檔案。然後在其他各個檔案使用extern宣告該物件,並在需要的地方使用該物件;2)設計一個全域性函式,使用print_log名稱空間封裝。這個函式接受一個“const char *”的引數,在函式再定義一個臨時的檔案,並將傳遞進來的字串寫入檔案,如下:

<span style="font-size:14px;">void output(const char* log)
{
    std::ofstream fout;
    fout.open("/Log/log.txt", std::out | std::append);
    fout << log;
    fout.flush();
    fout.close();
}</span>

        這樣,一個基本的日誌輸出到檔案功能就具備了。此外,我們也可通過debug巨集(比如VS的前處理器定義中就含有“_DEBUG”巨集),選擇在debug時將日誌輸出到console,在release是將日誌輸出到檔案。

        二、為日誌新增時間戳

        為了方便跟蹤程式執行的狀況,我們需要為每條日誌新增時間戳。具體的方法也有兩種,第一種情況對應上面的全域性檔案流物件,使用LOG列印巨集進行包裝。第二種是在上面的函式中呼叫系統時間,如下:

<span style="font-size:14px;">void output(const char* log)
{
    std::ofstream fout;
    fout.open("/Log/log.txt", std::out | std::append);
    SYSTEMTIME sys; 
    GetLocalTime(&sys); 
    fout << "[Time]:" << sys.wYear << "-" << sys.wMonth  << "-" << sys.wDay  << " "   << sys.wHour << ":" << sys.wMinute << ":" << sys.wSecond << "." << sys.wMilliseconds << log << std::endl; 
    fout.flush();
    fout.close();
}</span>
        此處是通過呼叫windows系統函式,獲得時間戳,也可以應用C++11新增的“chrono庫”來獲取與平臺無關的時間戳或者呼叫C/C++的兩個巨集“__DATE”、“__TIME__”。參考《深入應用C++11》第6章。此外,在QT中,獲取時間戳的程式碼如下:
<span style="font-size:14px;">    QString current_date_time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    int current_time = QTime::currentTime().msec();
    QString current_date = QString("(%1 %2)").arg(current_date_time).arg(current_time);</span>

        三、新增其他的日誌資訊

        除了時間戳,我們可能還關心日誌發生的位置,如檔名、行號、類名和函式名。在此可以參考博文“C/C++語言中特定的巨集”,通過呼叫巨集“__FILE__”、“__LINE__”、“__FUNCTION__”,來自動獲取日誌發生時的位置,而不是採用硬編碼的方式。

        對於類名,在QT中可以通過超程式設計的方式獲得,如下:

<span style="font-size:14px;">	if (row < 0 || column < 0 || row > 1 || column > 1) {
		qWarning("QGridLayout: Cannot add %s/%s to %s/%s at row %d column %d",
			widget->metaObject()->className(), widget->objectName().toLocal8Bit().data(),
			q->metaObject()->className(), q->objectName().toLocal8Bit().data(), row, column);</span>

        參考《An Introduction to Design Patterns in C++ with Qt》的第12章。繼承自QObject的物件,添加了巨集“Q_OBJECT”的類,QT會為它生成一個meta物件,裡面包含了它的類資訊。

        四、跨執行緒的日誌同步

        兩個執行緒同時輸出日誌的時候,如果採用的是方法一(全域性檔案流物件),容易出現日誌錯亂的情況,特別是一條日誌通過包含多個“《”符合的時候。解決的辦法是新增互斥鎖。當然,最好是採用第二中方法(全域性日誌輸出函式),將日誌組好,在傳參給函式,呼叫函式輸出日誌。

        五、改進“一去、二三裡的Qt日誌輸出到檔案”

        參考博文:“Qt之日誌輸出檔案”。該博文對Qt的日誌輸出介紹的比較詳細,其中主要應用Qt的QMessageLogContext。不足的是:1)缺少毫秒級的時間戳;2)沒用列印函式名和類名;

        1,新增毫秒級時間戳,見上文程式碼。

        2,在函式調研的地方,使用巨集“__FUNCTION__”,新增函式名。

        3,考慮使用全域性檔案,而不用每次open和close。