開發應用時,要記得替程式加上 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: