中文文檔
BqLog是一款輕量級、高效能的日誌系統,應用於《王者榮耀》等專案中,目前已成功部署並運作順利。
Windows 64 位元
蘋果系統
Linux
iOS系統
Android(X86_64、arm64-v8a、armeabi-v7a)
Unix(在FreeBSD上通過測試)
C++
爪哇
科特林
C#
與現有的開源日誌記錄庫相比,BqLog 提供了顯著的效能優勢(請參閱基準)。它不僅適用於伺服器和用戶端,而且與行動裝置高度相容。
記憶體消耗低,在10個線程、20,000,000個日誌條目的Benchmark情況下,BqLog本身消耗的記憶體不到1MB。
提供高效能、高壓縮的即時日誌格式
可以在遊戲引擎( Unity
、 Unreal
)中正常使用,支援Unreal提供的常見類型。
支援UTF-8
、 UTF-16
、 UTF-32
字元和字串,以及常見的參數類型,如 bool、float、double 以及各種長度和類型的整數
支援C++20
format specifications
非同步日誌記錄支援崩潰審查以避免資料遺失(受XLog啟發)
體積極小,Android編譯後動態函式庫僅200k左右
在 Java 和 C# 中不會產生額外的堆分配,避免在執行時間不斷建立新對象
僅依賴標準C語言庫和平台API,並且可以在Android的ANDROID_STL = none
模式下編譯
支援C++11
及以後的編譯標準,可以在-Wall -Wextra -pedantic -Werror的嚴格要求下編譯
編譯模組基於CMake
,提供不同平台的編譯腳本,使用方便
支援自訂參數類型
對程式碼建議非常友好
為什麼 BqLog 這麼快 - 高效能即時壓縮日誌格式
為什麼BqLog這麼快-高併發環形緩衝區
將 BqLog 整合到您的專案中
簡單示範
架構概述
主流程API使用說明
1-建立日誌對象
2-檢索日誌對象
3-記錄訊息
4-其他API
同步和非同步日誌記錄
1. 非同步日誌記錄的線程安全
追加器簡介
1. 控制台Appender
2. 文字檔追加器
3. CompressedFileAppender(強烈推薦)
4.RawFileAppender
配置說明
1. 完整範例
2. 詳細說明
二進位格式 Appender 的離線解碼
建構說明
1. 庫構建
2. 演示建置和運行
3. 自動化測試運行說明
4. 基準測試運行說明
進階使用主題
1. 無堆分配
2. 具有類別支援的日誌對象
3.程式異常退出時的資料保護
4.關於NDK和ANDROID_STL=無
5. 自訂參數類型
6. 在虛幻引擎中使用BqLog
基準
1. 基準說明
2. BqLog C++ 基準程式碼
3. BqLog Java 基準程式碼
4.Log4j基準程式碼
5. 基準測試結果
BqLog 可以以多種形式整合到您的專案中。對於C++,它支援動態函式庫、靜態函式庫和原始檔。對於 Java 和 C#,它支援帶有包裝器原始程式碼的動態庫。以下是包含 BqLog 的方法:
程式碼儲存庫包含位於 /dist/dynamic_lib/ 中的預編譯動態庫檔案。要使用庫檔案將 BqLog 整合到您的專案中,您需要執行以下操作:
選擇與您的平台對應的動態庫檔案並將其新增至專案的建置系統。
將 /dist/dynamic_lib/include 目錄複製到您的專案中並將其新增至包含目錄清單。 (如果您使用的是 XCode 的 .framework 程式庫,則可以跳過此步驟,因為 .framework 檔案已包含頭檔)。
程式碼儲存庫包含位於 /dist/static_lib/ 中的預編譯靜態函式庫檔案。要使用庫檔案將 BqLog 整合到您的專案中,您需要執行以下操作:
選擇與您的平台對應的靜態庫檔案並將其新增至專案的建置系統。
將 /dist/static_lib/include 目錄複製到您的專案中並將其新增至包含目錄清單。 (如果您使用的是 XCode 的 .framework 程式庫,則可以跳過此步驟,因為 .framework 檔案已包含頭檔)。
BqLog還支援直接將原始程式碼包含到您的專案中進行編譯。若要使用原始碼整合 BqLog,請依照下列步驟操作:
將 /src 目錄複製到您的專案中作為原始碼參考。
將 /include 目錄複製到您的專案中並將其新增至包含目錄清單。
如果在 Visual Studio 中編譯 Windows 版本,請將 /Zc:__cplusplus 新增至編譯選項,以確保正確確定目前 C++ 編譯器標準支援。
如果使用 Android NDK 中的原始程式碼,請參閱 4. 關於 NDK 和 ANDROID_STL = none 以了解重要注意事項。
在 C# 中,BqLog 可以透過本機動態程式庫和 C# 包裝器使用,支援 Mono、Microsoft CLR 和 Unity 引擎。 Unity 與 Mono 和 IL2CPP 模式相容。若要在 C# 中使用 BqLog,請依照下列步驟操作:
從/dist/dynamic_lib/中選擇與您的平台對應的動態庫檔案並將其新增至您的專案(對於Unity,請參閱Unity匯入和設定外掛程式)。
將 /wrapper/csharp/src 中的原始碼檔案複製到您的專案中。
在Java中,BqLog可以透過本機動態函式庫和Java Wrapper使用,支援常見的JVM環境和Android。若要將 BqLog 整合到 JVM 中,請執行下列步驟:
從/dist/dynamic_lib/中選擇與您的平台對應的動態庫檔案並將其新增至您的專案。
將 /wrapper/java/src 中的原始碼檔案複製到您的專案中。
(可選)如果您打算從 NDK 呼叫 BqLog,請將 /dist/dynamic_lib/include 目錄複製到您的專案中並將其新增至包含目錄清單。
以下程式碼將向您的控制台輸出超過 1000 個日誌(如果在 Android 上,則為 ADB Logcat)
#如果已定義(WIN32) #include#endif#include <字串>#include int main() { #if Defined(WIN32) // 將 Windows 命令列切換為 UTF-8,因為 BqLog 以 UTF-8 編碼輸出所有最終文本,以避免顯示問題 SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); #endif // 該字串是日誌配置。這裡它配置了一個帶有一個名為appender_0的appender(輸出目標)的記錄器,它輸出到控制台。 std::string config = R"( # 該appender的輸出目標是控制台appenders_config.appender_0.type=console # 此appender使用本地時間作為時間戳appenders_config.appender_0.time_zone=預設本地時間# 該appender輸出這6個appenders_config.appender_0.time_zone=預設本地時間# 該appender輸出這6個appenders_config.appender_0.time_zone=預設本地時間# 該appender輸出這6個appenders_config.appender_0.time_zone=預設本地時間# 該appender輸出這6個appender等級的日誌(中間沒有空格)appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal] )"; bq::log log = bq::log::create_log("my_first_log", config); // 使用設定建立日誌物件 for(int i = 0; i < 1024; ++i) { log.info("這是一則訊息測試日誌,格式字串為UTF-8,param int:{}, param bool :{}, param string8:{}, param string16:{}, param string32:{} , param float:{}", i, true, "utf8-string", u"utf16-string", U"utf32-string", 4.3464f); } log.error(U"這是錯誤測試日誌,格式字串為UTF-32"); bq::log::force_flush_all_logs(); // BqLog預設為非同步輸出。為了確保日誌在程式退出之前可見,請強制刷新以同步輸出一次。 返回0; }
使用 System.Text;使用 System;public class demo_main { public static void Main(string[] args) { Console.OutputEncoding = Encoding.UTF8; Console.InputEncoding = Encoding.UTF8; string config = @" # 此appender的輸出目標是控制台appenders_config.appender_0.type=console # 此appender使用本機時間作為時間戳ppenders_config.appender_0.time_zone=預設本機時間# 此appender輸出這6個等級的日誌(中間沒有空格)之間)appenders_config.appender_0.levels=[詳細,調試,信息,警告,錯誤,致命]“; bq.log log = bq.log.create_log("my_first_log", config); // 使用設定建立日誌物件 for (int i = 0; i < 1024; ++i) { log.info("這是一則訊息測試日誌,格式字串為UTF-16,param int:{}, param bool :{}, param string:{}, param float:{}", i, true, "字串文字”,4.3464f); } bq.log.force_flush_all_logs(); Console.ReadKey(); }}
public class demo_main { public static void main(String[] args) { // TODO 自動產生的方法存根String config = """ # 此appender 的輸出目標是控制台appenders_config.appender_0.type=console # 此appender 使用本機時間for timestampsappenders_config.appender_0.time_zone=default local time # 此appender輸出這6個等級的日誌(中間沒有空格)appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal] """; bq.log log = bq.log.create_log("my_first_log", config); // 使用設定建立一個日誌物件 for (int i = 0; i < 1024; ++i) { log.info("這是一則訊息測試日誌,格式字串為 UTF-16,param int:{}, param bool :{}, param string:{}, param float:{}", i, true, “字串文本”,4.3464f); bq.log.force_flush_all_logs(); } }
上圖清楚地說明了BqLog的基本結構。圖的右側是 BqLog 庫的內部實現,左側是您的程式和程式碼。您的程式可以使用提供的包裝器(針對不同語言的物件導向的 API)來呼叫 BqLog。在該圖中,建立了兩個日誌:一個名為“Log A”,另一個名為“Log B”。每個日誌都附加到一個或多個 Appender。 Appender可以理解為日誌內容的輸出目標。這可以是控制台(Android 的 ADB Logcat 日誌)、文字文件,甚至是壓縮日誌檔案或常規二進位日誌格式檔案等特殊格式。
在同一個流程中,不同語言的包裝器可以存取同一個 Log 物件。例如,如果在 Java 中建立了名為 Log A 的 Log 對象,則也可以透過名稱 Log A 從 C++ 端存取和使用它。
在極端情況下,例如在 Android 系統上運行的 Unity 開發的遊戲,您可能會在同一個應用程式中涉及 Java、Kotlin、C# 和 C++ 語言。它們都可以共用同一個 Log 物件。您可以使用create_log在Java端建立Log,然後使用get_log_by_name在其他語言中存取它。
注意:以下 API 在 bq::log(或 bq.log)類別中聲明。為了節省空間,僅列出 C++ API。 Java和C#中的API是相同的,這裡不再贅述。
在 C++ 中, bq::string
是 BqLog 函式庫中的 UTF-8 字串型別。您也可以傳入 c 樣式字串,例如 char 或std::string
或std::string_view
,它們將自動隱式轉換。
可以使用 create_log 靜態函數建立日誌物件。其聲明如下:
//C++ API ////// 建立日誌物件 /// /// 如果日誌名稱為空字串,bqLog 會自動為您指派一個唯一的日誌名稱。如果日誌名稱已經存在,它將返回先前存在的日誌對象,並用新的配置覆蓋先前的配置。一個日誌對象,如果建立失敗,其is_valid() 方法將會傳回false static log create_log(const bq::string& log_name, const bq::string& config_content);
程式碼透過傳入日誌物件的名稱和配置字串來建立日誌物件。日誌配置可以參考配置說明。以下是需要注意的幾個關鍵點:
無論是C#還是Java,傳回的日誌物件永遠不會為null。但是,由於配置錯誤或其他原因,可能會建立無效的日誌物件。因此,您應該使用 is_valid() 函數來檢查傳回的物件。對無效物件執行操作可能會導致程式崩潰。
如果傳遞空字串作為日誌名稱,bqLog 將自動產生唯一的日誌名稱,例如「AutoBqLog_1」。
對已存在的同名日誌對象呼叫 create_log 不會建立新的日誌對象,而是會用新的配置覆寫先前的配置。但此過程中有些參數無法修改;詳細資訊請參閱配置說明。
除了在NDK中使用時(參考4.關於NDK和ANDROID_STL = none),其他情況下都可以使用此API直接在全域或靜態變數中初始化日誌物件。
如果已經在其他地方建立了日誌對象,則可以直接使用 get_log_by_name 函數來取得已建立的日誌對象。
//C++ API ////// 透過名稱取得日誌物件 /// /// 要尋找的日誌物件的名稱 ///一個日誌對象,如果沒有找到指定名稱的日誌對象,則其is_valid() 方法將返回false static log get_log_by_name(const bq::string& log_name);
您也可以使用此函數來初始化全域變數或靜態函數中的日誌物件。但要注意的是,必須確保指定名稱的日誌物件已經存在。否則,傳回的日誌物件將無法使用,其 is_valid() 方法將傳回 false。
///核心日誌功能,有6個日誌等級: ///verbose, debug, info, warning, error, fatal templatebq::enable_if_t ::value, bool> verbose(const STR& log_content) const; 範本<類型名稱 STR, 類型名稱...參數> bq::enable_if_t ::value, bool> verbose(const STR& log_format_content, const Args&... args) const; 模板<類型名稱 STR> bq::enable_if_t ::value, bool> debug(const STR& log_content) const; 範本<類型名稱 STR, 類型名稱...參數> bq::enable_if_t ::value, bool> debug(const STR& log_format_content, const Args&... args) const; 模板<類型名稱 STR> bq::enable_if_t ::value, bool> info(const STR& log_content) const; 範本<類型名稱 STR, 類型名稱...參數> bq::enable_if_t ::value, bool> info(const STR& log_format_content, const Args&... args) const; 模板<類型名稱 STR> bq::enable_if_t ::value, bool> warning(const STR& log_content) const; 範本<類型名稱 STR, 類型名稱...參數> bq::enable_if_t ::value, bool> 警告(const STR& log_format_content, const Args&... args) const; 模板<類型名稱 STR> bq::enable_if_t ::value, bool> error(const STR& log_content) const; 範本<類型名稱 STR, 類型名稱...參數> bq::enable_if_t ::value, bool> error(const STR& log_format_content, const Args&... args) const; 模板<類型名稱 STR> bq::enable_if_t ::value, bool> fatal(const STR& log_content) const; 範本<類型名稱 STR, 類型名稱...參數> bq::enable_if_t ::value, bool> fatal(const STR& log_format_content, const Args&... args) const;
記錄訊息時,要注意三個要點:
可以看到,我們的日誌分為六個等級:verbose、debug、info、warning、error、fatal,與Android一致。它們的重要性依次增加。當輸出到控制台時,它們會以不同的顏色顯示。
STR參數與printf的第一個參數類似,可以是各種常見的字串類型,包括:
Java 的 java.lang.String
C# 的字串
C++ 的 C 風格字串和std::string
的各種編碼( char*
、 char16_t*
、 char32_t*
、 wchar_t*
、 std::string
、 std::u8string
、 std::u16string
、 std::u32string
、 std::wstring
、 std::string_view
、 std::u16string_view
、 std::u32string_view
、 std::wstring_view
甚至自訂字串型別(可參考自訂參數型別 )
您可以在 STR 參數後面新增各種參數。這些參數將被格式化為 STR 中的指定位置,遵循類似於 C++20 的 std::format 的規則(除了缺乏對位置參數和日期時間格式的支援)。例如,使用單一 {} 表示參數的預設格式,而 {:.2f} 指定格式化浮點數的精確度。盡量使用格式化參數輸出日誌,而不是手動拼接字串。此方法對於效能和壓縮格式儲存而言是最佳的。
目前支援的參數類型包括:
空指標(輸出為空)
指標(以 0x 開頭的十六進位位址輸出)
布林值
單字節字元(char)
雙位元組字元(char16_t、wchar_t、C# 的 char、Java 的 char)
四位元組字元(char32_t 或 wchar_t)
8 位元整數
8 位元無符號整數
16 位元整數
16 位元無符號整數
32 位元整數
32 位元無符號整數
64 位元整數
64 位元無符號整數
32 位元浮點數
64 位元浮點數
C++ 中其他未知的 POD 類型(大小限制為 1、2、4 或 8 個位元組,分別視為 int8、int16、int32 和 int64)
字串,包括STR參數中提到的所有字串類型
C# 和 Java 中的任何類別或物件(輸出其 ToString() 字串)
自訂參數類型,詳見自訂參數類型
還有其他常用的 API 可以完成特定的任務。詳細的API說明請參考bq_log/bq_log.h,以及Java和C#中的bq.log類別。以下是一些需要強調的關鍵 API:
////// 取消初始化BqLog,請在程式存在之前呼叫此函數。 /// static void uninit();
建議在退出程式或卸載使用BqLog的自行實作的動態函式庫之前執行uninit()
,否則在某些特定情況下退出時可能會卡住。
////// 如果bqLog是非同步的,程式崩潰可能會導致緩衝區中的日誌無法持久化到磁碟。 /// 如果啟用此功能,bqLog 將在發生崩潰時嘗試強制刷新緩衝區中的日誌。但是, /// 此功能並不保證成功,並且僅支援 POSIX 系統。 /// 靜態無效enable_auto_crash_handle();
詳細介紹請參閱程式異常退出資料保護
////// 同步刷新所有日誌物件的緩衝區 /// 以確保呼叫後處理緩衝區中的所有資料。 /// 靜態無效force_flush_all_logs(); ////// 同步刷新此日誌物件的緩衝區 /// 以確保呼叫後處理緩衝區中的所有資料。 /// 無效force_flush();
由於 bqLog 預設使用非同步日誌記錄,因此有時您可能希望立即同步並輸出所有日誌。在這種情況下,您需要強制呼叫force_flush()。
////// 註冊一個回調,每當輸出控制台日誌訊息時都會呼叫該回調。 /// 這可用於外部系統監控控制台日誌輸出。 /// /// static void register_console_callback(bq::type_func_ptr_console_callback 回呼); ////// 取消註冊控制台回呼。 /// /// static void unregister_console_callback(bq::type_func_ptr_console_callback 回呼);
ConsoleAppender 的輸出會傳送到 Android 上的控制台或 ADB Logcat 日誌,但這可能無法涵蓋所有情況。例如,在自訂遊戲引擎或自訂 IDE 中,提供了一種機制來為每個控制台日誌輸出呼叫回呼函數。這允許您在程式中的任何位置重新處理和輸出控制台日誌。
注意:不要在控制台回呼中輸出任何同步的 BQ 日誌,因為這很容易導致死鎖。
////// 啟用或停用控制台附加器緩衝區。 /// 由於我們的包裝器可以在 C# 和 Java 虛擬機中運行,並且我們不想直接從本機執行緒呼叫回調, /// 我們可以啟用此選項。這樣,所有控制台輸出都將保存在緩衝區中,直到我們取得它們為止。 /// /// ///static void set_console_buffer_enable(bool enable); /// /// 以線程安全的方式從控制台追加器緩衝區中取得和刪除日誌條目。 /// 如果控制台追加器緩衝區不為空,則將為此日誌條目呼叫 on_console_callback 函數。 /// 請確保在回呼函數中不要輸出同步的BQ日誌。 /// /// 如果控制台追加器緩衝區不為空,則為取得的日誌條目呼叫回調函數 ///如果控制台追加器緩衝區不為空且已取得日誌條目; static bool fetch_and_remove_console_buffer(bq::type_func_ptr_console_callback on_console_callback); 否則回傳 False。
除了透過控制台回呼攔截控制台輸出之外,您還可以主動取得控制台日誌輸出。有時,我們可能不希望控制台日誌輸出透過回呼來實現,因為您不知道回呼將來自哪個執行緒(例如,在某些C# 虛擬機器或JVM 中,當控制台日誌輸出時,虛擬機器可能正在執行垃圾收集)呼叫回調,這可能會導致掛起或崩潰)。
這裡使用的方法涉及透過set_console_buffer_enable
啟用控制台緩衝區。這會導致每個控制台日誌輸出都儲存在記憶體中,直到我們主動呼叫fetch_and_remove_console_buffer
來檢索它。因此,如果您選擇使用此方法,請記住及時獲取並清除日誌,以避免記憶體未釋放。
注意:不要在控制台回呼中輸出任何同步的 BQ 日誌,因為這很容易導致死鎖。
其他注意事項:如果您在 IL2CPP 環境中使用此程式碼,請確保 on_console_callback 被標記為靜態不安全,並使用 [MonoPInvokeCallback(typeof(type_console_callback))] 屬性進行修飾。
////// 修改日誌配置,但有些欄位不能修改,如buffer_size。 /// /// ///bool reset_config(const bq::string& config_content);
有時您可能想要修改程式中日誌的設定。除了重新建立日誌物件來覆蓋配置(請參閱建立日誌物件),您還可以使用重置介面。但請注意,並非所有配置項都可以透過這種方式修改。詳細資訊請參閱配置說明
////// 暫時停用或啟用特定的 Appender。 /// /// /// void set_appenders_enable(const bq::string&appender_name, bool enable) ;
預設情況下,配置中的 Appender 是活動的,但這裡提供了一種機制來暫時停用和重新啟用它們。
////// 僅在配置快照時有效。 /// 它將快照緩衝區解碼為文字。 /// /// 每個日誌的時間戳記是GMT時間還是當地時間 ///解碼後的快照緩衝區 bq::string take_snapshot(bool use_gmt_time) const;
有時,某些特殊功能需要輸出日誌的最後部分,這可以使用快照功能來完成。要啟用此功能,您首先需要在日誌配置中啟動快照並設定最大緩衝區大小(以位元組為單位)。此外,您需要指定要為快照過濾的日誌等級和類別(選用)。詳細配置請參閱快照配置。當需要快照時,呼叫 take_snapshot() 將傳回包含儲存在快照緩衝區中的最新日誌條目的格式化字串。在 C++ 中,類型為bq::string
,可以隱式轉換為std::string
。
命名空間bq{命名空間工具{ //這是一個用於解碼二進位日誌格式的實用程式類別。 //使用時,先建立一個log_decoder對象, //然後呼叫其decode函數進行解碼。 //每次成功呼叫後, //您可以使用 get_last_decoded_log_entry() 檢索解碼結果。 //每次呼叫都會解碼一個日誌條目。 結構體log_decoder { 私人的: bq::字串解碼文字_; bq::appender_decode_result result_ = bq::appender_decode_result::成功; uint32_t句柄_ = 0; public: ////// 建立一個log_decoder對象,每個log_decoder物件對應一個二進位日誌檔。 /// /// 二進位日誌檔案的路徑,可以是相對路徑或絕對路徑 log_decoder(const bq::string& log_file_path); 〜log_decoder(); ////// 解碼日誌條目。每次呼叫此函數只會解碼 1 個日誌條目 /// ///解碼結果,appender_decode_result::eof 表示整個日誌檔案已解碼 bq::appender_decode_result 解碼(); ////// 取得最後的解碼結果 /// ///bq::appender_decode_result get_last_decode_result() const; /// /// 取得最後一次解碼日誌條目內容 /// ///const bq::string& get_last_decoded_log_entry() const; }; } }
這是一個實用程式類,可以在運行時解碼二進位類型 Appender 輸出的日誌文件,例如 CompressedFileAppender 和 RawFileAppender。
要使用它,首先建立一個 log_decoder 物件。然後,每次呼叫decode()函數時,它都會按順序解碼一個日誌條目。如果傳回結果為bq::appender_decode_result::success,則可以呼叫get_last_decoded_log_entry()取得最後一條解碼日誌條目的格式化文字內容。如果結果是bq::appender_decode_result::eof,則表示所有日誌都已讀取完畢。
BqLog 允許您透過 thread_mode 設定來設定日誌物件是同步還是非同步。這兩種模式的主要差異如下:
同步記錄 | 非同步日誌記錄 | |
---|---|---|
行為 | 呼叫logging函數後,日誌立即寫入對應的appender。 | 呼叫日誌函數後,日誌並沒有立即寫入;相反,它被移交給工作線程進行定期處理。 |
表現 | 低,因為寫入日誌的執行緒需要阻塞並等待日誌寫入對應的appender,然後才能從日誌記錄函數返回。 | 高,因為寫入日誌的執行緒不需要等待實際輸出,可以在記錄後立即返回。 |
線程安全 | 高,但要求日誌功能執行過程中不修改日誌參數。 | 高,但要求日誌功能執行過程中不修改日誌參數。 |
關於非同步日誌記錄的一個常見誤解是它的執行緒安全性較低,使用者擔心在工作執行緒處理日誌時參數可能會被回收。例如:
{ const char str_array[5] = {'T', 'E', 'S', 'T', '�'}; const char* str_ptr = str_array; log_obj.info("這是測試參數:{},{}",str_array,str_ptr); }
在上面的範例中, str_array
儲存在堆疊上,一旦退出作用域,它的記憶體就不再有效。使用者可能會擔心,如果使用非同步日誌記錄,當工作執行緒處理日誌時, str_array
和str_ptr
將成為無效變數。
但不會發生這種情況,因為BqLog在執行info
函數的過程中將所有參數內容複製到其內部ring_buffer
。一旦info
函數傳回,就不再需要str_array
或str_ptr
等外部變數。此外, ring_buffer
不會儲存const char*
指標地址,而是始終儲存整個字串。
真正的潛在問題出現在以下場景:
靜態 std::string global_str = "你好世界"; // 這是一個被多個執行緒修改的全域變數。 { log_obj.info("這是測試參數:{}", global_str); }
如果在info
函數執行過程中global_str
的內容髮生變化,可能會導致未定義的行為。 BqLog會盡力防止崩潰,但無法保證最終輸出的正確性。
Appender代表日誌輸出目標。 bqLog 中 Appender 的概念基本上與 Log4j 相同。目前bqLog提供了以下幾種Appender:
這個Appender的輸出目標是控制台,包括Android的ADB和iOS上對應的控制台。文本編碼為UTF-8。
此Appender直接以UTF-8文字格式輸出日誌檔。
此 Appender 以壓縮格式輸出日誌文件,這是highly recommended format by bqLog
。它在所有 Appender 中具有最高的性能,並產生最小的輸出檔案。但是,最終文件需要解碼。解碼可以是運行時解碼,也可以是離線解碼。
該Appender將二進位日誌內容從記憶體直接輸出到檔案。它的效能比TextFileAppender高,但消耗更多的儲存空間。最終文件需要解碼。解碼可以是運行時解碼,也可以是離線解碼。
下面對各種Appender進行綜合比較:
姓名 | 輸出目標 | 直接可讀 | 輸出性能 | 輸出尺寸 |
---|---|---|---|---|
控制台附加程式 | 安慰 | ✔ | 低的 | - |
文字檔案附加器 | 文件 | ✔ | 低的 | 大的 |
壓縮檔案附加器 | 文件 | ✘ | 高的 | 小的 |
原始文件附加器 | 文件 | ✘ | 中等的 | 大的 |
配置是指create_log和reset_config函數中的配置字串。字串使用屬性檔案格式並支援 # 註解(但請記住以 # 開始新行作為註解)。
下面是一個完整的例子:
# 此組態設定了一個日誌對象,總共有5個Appender,其中包含兩個TextFileAppender,輸出到兩個不同的檔案。時區為系統本機時間appenders_config.appender_0.time_zone=預設本機時間#appender_0會輸出全部6個等級的日誌(注意:日誌等級之間不能有空格,否則解析失敗)appenders_config.appender_0.levels=[verbose,debug , info,warning,error,fatal]# 第二個Appender名為appender_1,類型為TextFileAppenderappenders_config.appender_1.type=text_file#appender_1的時區為GMT,即UTC+0appenders_config.appender_1.time_zone=gmt#僅適用等級的日誌,其他將被忽略appenders_config.appender_1.levels=[info,warning,error,fatal]#appender_1的路徑將在程式的相對bqLog目錄中,檔案名稱以normal開頭,後面是日期和.log 副檔名# 在iOS 上,它將保存在/var/mobile/Containers/Data/Application/[APP]/Library/Caches/bqLog# 在Android 上,它將保存在[android.content.Context .getExternalFilesDir()]/ bqLogappenders_config.appender_1.file_name=bqLog/normal#最大檔案大小為10,000,000位元組;如果超過,將建立一個新檔案appenders_config.appender_1.max_file_size=10000000# 超過十天的檔案將被清理appenders_config.appender_1.expire_time_days=10# 如果輸出的總大小超過100,000,000位元組,將從下列時間開始清理檔案.appender_1.capacity_limit=100000000# 第三個Appender名稱為appender_2,類型為TextFileAppenderappenders_config.appender_2.type=text_file#appender_2將輸出所有層級的日誌appenders_config.appender_2.levels=[ bqLog目錄,檔案名稱以new_normal開頭,後面跟著日期和.log副檔名appenders_config.appender_2.file_name=bqLog/new_normal#此選項僅在Android上有效,將日誌儲存在內部儲存目錄中,即[android .content.Context .getFilesDir()]/bqLogappenders_config.appender_2.is_in_sandbox=true#第四個Appender名稱為appender_3,類型為CompressedFileAppenderappenders_config.appender_3.type=compressed_file#appender_3將輸出所有層級的日誌」的路徑將在程式的絕對路徑~/bqLog目錄下,檔案名稱以compress_log開頭,後面跟著日期和.logcompr副檔名appenders_config.appender_3.file_name=~/bqLog/compress_log#命名第五個Appender appender_4,其類型為RawFileAppenderappenders_config .appender_4.type=raw_file#appender_4預設為停用,可以稍後使用set_appenders_enable啟用appenders_config.appender_4.enable=false#appender_4將輸出所有層級的日誌appenders_config.appender_4.levels=[all]#forforender_4 路徑位於程式的相對目錄中,檔案名稱以raw_log 開頭,後面跟著日期和.lograw 副檔名appenders_config.appender_4.file_name=bqLog/raw_log# 只有當其類別以ModuleA、ModuleB 開頭時才會處理日誌。概念在後面的進階使用主題中有詳細解釋)appenders_config.appender_4.categories_mask=[ModuleA,ModuleB.SystemC]#非同步緩衝區總大小為65535位元組;具體意義稍後解釋log.buffer_size=65535#日誌的可靠性等級為正常;具體意義稍後解釋log.reliable_level=normal#日誌只有其類別符合以下三個通配符才會被處理,否則全部被忽略(Category的概念在後面的高級使用主題中詳細解釋)log.categories_mask= [*default ,ModuleA,ModuleB.SystemC]# 這是非同步日誌;非同步日誌是效能最高、建議的日誌類型log.thread_mode=async#如果日誌等級為error或fatal,則在每個日誌條目中包含呼叫堆疊資訊log.print_stack_levels=[error,fatal]#啟用快照功能,快照快取大小為64Ksnapshot .buffer_size=65536# 快照中只會記錄info和error等級的日誌snapshot.levels=[info,error]# 會被記錄類別以ModuleA、ModuleB.SystemC開頭的日誌,否則快照會被忽略snapshot .categories_mask=[模組A.系統A.ClassA,模組B]
appenders_config
是Appenders的一組設定。 appenders_config
後面的第一個參數是Appender的名稱,所有同名的Appender共享相同的設定。
姓名 | 必需的 | 可配置值 | 預設 | 適用於ConsoleAppender | 適用於TextFileAppender | 適用於 CompressedFileAppender | 適用於RawFileAppender |
---|---|---|---|---|---|---|---|
類型 | ✔ | 控制台、文字檔案、壓縮檔案、原始文件 | ✔ | ✔ | ✔ | ✔ | |
使能夠 | ✘ | Appender預設是否開啟 | 真的 | ✔ | ✔ | ✔ | ✔ |
等級 | ✘ | 日誌等級數組 | [全部] | ✔ | ✔ | ✔ | ✔ |
時區 | ✘ | gmt 或任何其他字串 | 當地時間 | ✔ | ✔ | ✔ | ✔ |
檔案名稱 | ✔ | 相對或絕對路徑 | ✘ | ✔ | ✔ | ✔ | |
位於沙箱中 | ✘ | 真,假 | 錯誤的 | ✘ | ✔ | ✔ | ✔ |
最大檔案大小 | ✘ | 正整數或 0 | 0 | ✘ | ✔ | ✔ | ✔ |
過期時間天數 | ✘ | 正整數或 0 | 0 | ✘ | ✔ | ✔ | ✔ |
容量限制 | ✘ | 正整數或 0 | 0 | ✘ | ✔ | ✔ | ✔ |
類別_掩碼 | ✘ | [] 中包含的字串數組 | 空的 | ✔ | ✔ | ✔ | ✔ |
指定 Appender 的類型。
console
: 代表ConsoleAppender
text_file
:代表 TextFileAppender
compressed_file
:代表 CompressedFileAppender
raw_file
:代表RawFileAppender
預設為true
。如果設定為false
,Appender 將預設為停用,稍後可以使用set_appenders_enable
啟用。
包含在[]
中的數組,包含verbose
、 debug
、 info
、 warning
、 error
、 fatal
或[all]
的任意組合以接受所有層級。注意:級別之間不要包含空格,否則解析失敗。
指定日誌的時區。 gmt
表示格林威治標準時間 (UTC+0),任何其他字串或留空將使用本地時區。時區影響兩件事:
格式化文字日誌的時間戳記(適用於ConsoleAppender和TextFileAppender)
當超過指定時區的午夜時,將建立新的日誌檔案(適用於 TextFileAppender、CompressedFileAppender 和 RawFileAppender)。
儲存檔案的路徑和檔案名稱前綴。此路徑可以是絕對路徑(不建議用於 Android 和 iOS)或相對路徑。最終的輸出檔名將是此路徑和名稱,後面跟著日期、檔案號和 Appender 的副檔名。
僅在 Android 上有意義:
true
:檔案儲存在內部儲存目錄中(android.content.Context.getFilesDir())。如果不可用,它們將儲存在外部儲存目錄中(android.content.Context.getExternalFilesDir())。如果這也不可用,它們將儲存在快取目錄中(android.content.Context.getCacheDir())。
false
:檔案預設儲存在外部儲存目錄中。如果不可用,它們將儲存在內部儲存目錄中。如果也不可用,它們將儲存在 Cache 目錄中。
最大檔案大小(以位元組為單位)。當已儲存的文件超過此大小時,將建立一個新的日誌文件,文件編號會依序遞增。 0
禁用此功能。
文件保留的最大天數。早於此時間的檔案將會自動刪除。 0
禁用此功能。
此 Appender 在輸出目錄中輸出的檔案的最大總大小。如果超過此限制,將從最舊的檔案開始刪除,直到總大小在限制內。 0
禁用此功能。
如果日誌對像是支援類別的 Log 對象,則可以使用它來篩選樹狀類別清單。當數組不為空時,此功能處於活動狀態。例如, [*default,ModuleA,ModuleB.SystemC]
表示預設類別的日誌