Use POCO Logger to Generate Log File

Ken Chen
10 min readFeb 1, 2020

--

開發應用時,要記得替程式加上 Log。因為程式可能會因各種原因出問題,像是權限啦、記憶體存取啦、遠端資源啦,這些外部依賴跟程式本身沒什麼關係,但如果沒有加上 Log,你會連怎麼出問題都不知道,容易出現「我這邊測起來明明沒問題啊」的悲劇。而且當軟體佈署在客戶端時,如果沒有 Log,要再現客戶問題也會變得非常困難。人生寶貴,為節省開發時間,請加上 Log。

POCO 這套 C++ 的網路應用開發函式庫有 Log 相關函式,雖然用 POCO 來寫 Log 未免有些殺雞用牛刀,但對原本就用 POCO 進行應用開發的人,例如我,則可以省去整合其他函式庫的麻煩。本文會講解 POCO 的 Log 機制,讓大家輕輕鬆鬆加上自己的除錯資訊。

Step 0: Setup Env

記得安裝 POCO,安裝方式不贅述,可以參考前篇,使用 conan 安裝。

建立專案目錄,例如

project/
├── build/
├── src/
| ├── CMakeLists.txt
| └── main.cpp
├── CMakeLists.txt
├── conanfile.txt
└── README

main 是主要的程式碼。

Step 1: Output to Console

稍微講些概念,POCO 的 Logger 由幾個 module 組成,參考下圖

當 Log 消息產生後,會被轉成 POCO 的 Message,交由 Logger 處理。Logger 是框架入口,每個 Logger 可以對應到好幾個 Channel,Channel 會負責將 Message 送到它們的目的地。

以最單純的 Console Log 舉例,假設我們希望程式執行時,將 Log 直接印出,可以用

#include "Poco/Logger.h"
#include "Poco/ConsoleChannel.h"
#include "Poco/AutoPtr.h"
using Poco::Logger;
using Poco::ConsoleChannel;
using Poco::AutoPtr;
int main(int argc, char** argv)
{
AutoPtr<ConsoleChannel> pChannel(new ConsoleChannel);
Logger::root().setChannel(pChannel);
Logger &logger = Logger::root(); // inherits root channel
logger.information("Hello");
}

在範例中,AutoPtr 是 POCO 內的 Smart Poiner,用來指向 Channel。

建立 Channel 後,用 setChannel 將它 attach 到 Logger,再使用 information() 輸出訊息。編譯後得到

ken@DESKTOP-2R08VK6:~/git/PocoExercise/build$ cmake --build .
ken@DESKTOP-2R08VK6:~/git/PocoExercise/build$ ./bin/main
Hello

如執行結果,Log 的訊息會顯示於 Console 上。

Step 2: Output to File

實務上,不僅僅操作時要看到 Log,也希望把 Log 存在 Storage 中,方便以後除錯跟監控。POCO 有提供 SimpleFileChannel 跟 SplitterChannel 這兩個工具,可以幫助我們達到目的。

SimpleFileChannel 跟 ConsoleChannel 類似,但輸出目的地是檔案;SplitterChannel 則是分流用的 Channel,類似 Y 型接頭,開發者可以將其它的 Channel 接到 SplitterChannel,再將 Logger 對應到 SplitterChannel,就能一次輸出訊息到多個 Channel。

依照這概念進行修改後,程式碼變成

#include "Poco/Logger.h"
#include "Poco/ConsoleChannel.h"
#include "Poco/SimpleFileChannel.h"
#include "Poco/SplitterChannel.h"
#include "Poco/AutoPtr.h"
using Poco::Logger;
using Poco::ConsoleChannel;
using Poco::SimpleFileChannel;
using Poco::SplitterChannel;
using Poco::AutoPtr;
int main(int argc, char** argv)
{
AutoPtr<ConsoleChannel> pChannel(new ConsoleChannel);
AutoPtr<SimpleFileChannel> pChannel2(new SimpleFileChannel);
AutoPtr<SplitterChannel> pSC(new SplitterChannel);
pChannel2->setProperty("path", "sample.log");
pChannel2->setProperty("rotation", "2 K");
pSC->addChannel(pChannel);
pSC->addChannel(pChannel2);
Logger::root().setChannel(pSC);
Logger &logger = Logger::root(); // inherits root channel
logger.information("Hello");
}

其中 pChannel2 是 SimpleFileChannel,設定輸出檔案為 sample.log。rotation 的大小為 2KB,當 log 檔案超過 2 K 時,會自動重頭寫起,這麼做是避免 Log 無限增長。

同樣,編譯並執行

ken@DESKTOP-2R08VK6:~/git/PocoExercise/build$ cmake --build .
ken@DESKTOP-2R08VK6:~/git/PocoExercise/build$ ./bin/main
Hello
ken@DESKTOP-2R08VK6:~/git/PocoExercise/build$ cat sample.log
Hello

可以看到在執行目錄下,產出 sample.log,內容是 “Hello”。

Step 3: Add Formatting

除了 Log 本身的資訊外,最好能加上訊息嚴重性、執行時間等資訊。這時候就要用到 FormattingChannel 跟 PatternFormatter。它們跟原本架構的關係如下圖

當 Logger 收到訊息後,會先傳給 FormattingChannel,FormattingChannel 將訊息傳給 PatternFormatter 改造,加上需要的資訊。PatternFormatter 將改過後的訊息交還給 FormattingChannel,FormattingChannel 再將訊息 Pass 給下游的 Channel。

概念很簡單,修改程式碼為

#include "Poco/Logger.h"
#include "Poco/ConsoleChannel.h"
#include "Poco/SimpleFileChannel.h"
#include "Poco/SplitterChannel.h"
#include "Poco/FormattingChannel.h"
#include "Poco/PatternFormatter.h"
#include "Poco/AutoPtr.h"
using Poco::Logger;
using Poco::ConsoleChannel;
using Poco::SimpleFileChannel;
using Poco::SplitterChannel;
using Poco::FormattingChannel;
using Poco::PatternFormatter;
using Poco::AutoPtr;
int main(int argc, char** argv)
{
AutoPtr<ConsoleChannel> pChannel(new ConsoleChannel);
AutoPtr<SimpleFileChannel> pChannel2(new SimpleFileChannel);
AutoPtr<SplitterChannel> pSC(new SplitterChannel);
AutoPtr<PatternFormatter> pPF(new PatternFormatter);
pPF->setProperty("pattern", "[%p] %Y-%m-%d %H:%M:%S: %t");
AutoPtr<FormattingChannel> pFC(new FormattingChannel(pPF, pSC));
pChannel2->setProperty("path", "sample.log");
pChannel2->setProperty("rotation", "2 K");
pSC->addChannel(pChannel);
pSC->addChannel(pChannel2);
Logger::root().setChannel(pFC);
Logger &logger = Logger::root(); // inherits root channel
logger.information("Hello");
}

執行,並觀察結果

ken@DESKTOP-2R08VK6:~/git/PocoExercise/build$ cmake --build .
ken@DESKTOP-2R08VK6:~/git/PocoExercise/build$ ./bin/main
[Information] 2020-02-01 05:11:23: Hello
ken@DESKTOP-2R08VK6:~/git/PocoExercise/build$ cat sample.log
[Information] 2020-02-01 05:11:23: Hello

不錯吧,現在我們知道 Hello 是 Information 類的資訊,於 2020–02–01 05:11:23 執行。

Conclusion:

透過使用 POCO Logger,簡單記錄程式運行的狀況。嚴格來說,C++ 的 Logger 使用還是有點麻煩,沒有像高階語言一樣直接整合。但有函式勝於無函式,還是遠比手工打造開檔讀檔格式化來得輕鬆。

POCO 似乎還有許多可以玩的地方,就留到以後慢慢探索吧,好了,我要繼續 Debug 了。

Reference:

POCO Logging Slide
POCO Logging

--

--

Ken Chen
Ken Chen

Written by Ken Chen

台北人。現職軟體開發者。主要領域為後端開發。喜歡電影藝術文學,偶爾寫點別的。想看更多的文章,可以到我的個人 Blog https://blog.kenwsc.com

No responses yet