Neko Logging
nlog 是我之前在開發Neko Launcher時所產生的需求而編寫的一個日誌程式庫。
下面就請讓我來講解一下這個用了絕對大吃一驚日誌庫的優點吧:
- 沒有宏
你無需忍受C語言遺留下的糟粕 —— 宏。
- 僅標頭檔 (無需構建/鏈接)
不需要構建或鏈接,新手小白福音。 —— 我太奶來了都會用 XD
- Auto SrcLoc
自動捕獲原始碼位置 —— 阿嬤再也不用擔心報錯找不到位置啦
- 支援多個輸出器(Console, File, Custom)
—— 無論多少輸出都能塞進去(你塞到uint64溢出的話當我沒說)
- 支援自定格式化日誌訊息
——理所當然,你可以很容易的自定格式與輸出器
- 支援異步日誌IO
- 執行緒安全
- 範圍日誌方式記錄(RAII)
Quick Start
- 克隆倉庫到你的主機
git clone https://github.com/moehoshio/nlog.git- 將include資料夾拷貝到你的include目錄
- 在原始碼中引入標頭
#include "neko/log/nlog.hpp"基礎範例
現在你可以開始記錄了,無需太多操作就能夠做到:
#include "neko/log/nlog.hpp"
int main() {
using namespace neko;
log::setCurrentThreadName("Main Thread"); //設定當前線程名稱
log::setLevel(log::Level::Debug); //設定日誌等級
log::info("This is an info message.");
log::debug("This is a debug message.");
log::warn("This is a warning message.");
log::error("This is an error message.");
}output:
[2025-09-16 01:53:58.678] [Info] [Main Thread] [main.cpp:9] This is an info message.
[2025-09-16 01:53:58.679] [Debug] [Main Thread] [main.cpp:10] This is a debug message.
[2025-09-16 01:53:58.679] [Warn] [Main Thread] [main.cpp:11] This is a warning message.
[2025-09-16 01:53:58.679] [Error] [Main Thread] [main.cpp:12] This is an error message.使用
記錄
要記錄日誌很簡單,就和上面的例子一樣,
這些函式都有兩個版本。
單字串:
inline void debug(const std::string &message, const neko::SrcLocInfo &location = {});
debug("msg"); // (基礎格式)... msg和格式化參數(透過std::format):
template <typename... Args>
void debug(const neko::SrcLocInfo &location, std::format_string<Args...> fmt, Args &&...args);
debug( {} , "Hello , {} . 1 + 1 = {}", "World" , 1 + 1); // (基礎格式)... Hello , World . 1 + 1 = 2其他等級的函式同上。
Tips: SrcLoc可以自動原始碼位置,只需要一個默認對象即可, 你可以透過 {} 或型參默認產生它。
Level
你可以設定記錄者的日誌級別,它會控制何等級的日誌該被記錄。
舉例來說,如果你設定setLevel爲 Info,那麼只有Info以上的日誌會被記錄。(此時Debug訊息會被丟棄)。
log::setLevel(log::Level::Info);
log::info("Info"); // 輸出 Info
log::warn("Warn"); // 輸出 Warn
log::debug("Debug"); // 更詳細的訊息將被丟棄如果需要,你可以添加更多的日誌等級並透過log函式記錄。
列如:
// nlog.hpp
enum class Level : neko::uint8 {
Debug = 1, ///< Debug
Info = 2, ///< General information
Warn = 3, ///< Potential issues
Error = 4, ///< Error
lv5 = 5, /// 自定的等級
lv6 = 6,
lv10 = 10,
Off = 255 ///< Logging off
};
// main.cpp
using namespace neko;
log::logger.log(log::Level::lv10,"Hello Lv10");設定執行緒名稱
您可以透過
log::setCurrentThreadName和
log::setThreadName函式來設定不同執行緒在日誌中的名稱。
範例:
using namespace neko;
//設定目前的執行緒
log::setCurrentThreadName("Thread 1");
log::info(""); // ... [Thread 1] ...
//指定id
auto id = std::this_thread::get_id();
logsetThreadName(id, "Thread-1");
log::info(""); // ... [Thread-1] ...Appenders
您可以同時新增多個 appender 來將日誌輸出到不同地方,默認提供了輸出到終端和寫入檔案的appender。
記錄到檔案中
// 新增檔案輸出器並覆寫檔案
addFileAppender("app.log", true); 輸出到終端(默認已啟用)
addConsoleAppender(); //新增終端機輸出器自定輸出器
你可以非常方便的添加自己的輸出器,以輸出到任何地方。
只需要繼承
log::IAppender並覆寫 append 和 flush方法爲你的輸出即可。
範例:
using namespace neko;
class MyAppender : public log::IAppender {
public:
void append(const log::LogRecord &record) override {
std::unique_ptr<log::IFormatter> formatter = std::make_unique<log::DefaultFormatter>(); //構建一個默認日誌格式化器
auto formatted = formatter->format(record); //格式化日誌
// 將字串輸出到你的目標
yourOutput << formatted;
}
void flush() override {
yourOutput.flush();
}
};
// 添加自定的日誌輸出器
log::addAppender(std::make_unique<MyAppender>());格式化日誌
格式化器是輸出器的一個輔助方法,用以格式化日誌。
其與每個appender獨立的,並由appender調用格式化器的方法來格式化日誌。
So,建議自定appender時內置一個格式化器對象。
默認格式化器
使用默認的格式化器格式爲:
[date time] [level] [thread] [file:line] [msg]
在構造時你可以指定要截斷的根路徑和是否使用全路徑,函式定義如下:
explicit DefaultFormatter(const std::string &rootPath = "", bool useFullPath = false)當rootPath爲空字串時(默認), file = main.cpp。
當rootPath爲路徑,如 /to/path/, 且file位於 /to/path/src/main.cpp時,file = /src/main.cpp。
當 useFullPath爲 true時, 將忽略rootPath,始終顯示完全路徑。 file = /to/path/src/main.cpp。
自定格式化器
繼承自
IFormatter並覆寫 format函式。
你需要在format函式實現格式化傳入的記錄。
範例:
using namespace neko;
class MyFormatter : public log::IFormatter {
public:
std::string format(const log::LogRecord &record) override {
//你可以使用任何方式組合record格式,std format ,oss ...
std::ostringstream oss;
oss << "lv: " << log::levelToString(record.level) << " , msg: "<< record.message ;
return oss.str();
}
};
//構建一個終端輸出器並指定格式化器爲MyFormatter
log::ConsoleAppender consoleAppender(std::make_unique<MyFormatter>());
log::info("Hello");output:
lv: Info , msg: Hello非同步日誌
默認情況下,記錄日誌由記錄者執行緒寫入IO。
為了獲得更好的效能,您可以啟用非同步模式。 讓日誌在背景執行緒中處理。
#include "neko/log/nlog.hpp"
#include <thread>
using namespace neko;
int main() {
// 設定為非同步模式
log::setMode(neko::SyncMode::Async);
// 啟動日誌處理迴圈(通常在一個專用的執行緒中)
std::thread logThread([]{ log::runLogLoop(); });
// 主執行緒提交日誌
log::info("This will be logged asynchronously.");
// ... 應用程式執行 ...
// 停止日誌迴圈並刷新剩餘的日誌
log::stopLogLoop();
logThread.join();
}異步時日誌不會實時刷新,可能發生開始前提交了日誌,但來不及記錄便崩潰的情況。
建議Debug時關閉。
Tips: 使用異步模式時,必須有執行緒運行
neko::log::runLogLoop()函式。
否則任何日誌都不會得到處理。
RAII 範圍日誌
使用 autoLog 來自動記錄一個範圍的開始和結束。
void someFunction() {
// 當 someFunction 進入時記錄 "Start",離開時記錄 "End"
log::autoLog log("Start", "End");
// ... 函式內容 ...
}結語
就是醬紫,你已經可以熟練使用 nlog 了,快去試試吧~
如果還有任何問題歡迎留言探討喔!😉
更新:
哇,正文含有命名空間會導致排版解析錯誤QQ, 所以看起來可能會有些奇怪。
對象的完整命名空間參考readme中的說明吧,或者閱讀原始碼~
(這不會很困難,該原始碼是易讀的)
你好呀~ 看到留言訊息裏的 ⌈O2⌋可能是拼寫錯誤,故維持了原始網域並更新了頭像路徑 如有誤可再留言變更~
您好,麻烦更新一下我的站点信息,谢谢~ Name: Ethan Desc: Don’t stay awake for too long. Link: https://hanlifeO2.com Avatar: https://hanlifeO2.com/avatar.svg
嗨~ 朋友你好呀,已經加上咯~