中文文档
BqLog ist ein leichtes, leistungsstarkes Protokollierungssystem, das in Projekten wie „Honor of Kings“ verwendet wird. Es wurde erfolgreich eingesetzt und läuft reibungslos.
Windows 64-Bit
MacOS
Linux
iOS
Android (X86_64, arm64-v8a, armeabi-v7a)
Unix (Bestehen Sie den Test unter FreeBSD)
C++
Java
Kotlin
C#
Im Vergleich zu bestehenden Open-Source-Protokollierungsbibliotheken bietet BqLog erhebliche Leistungsvorteile (siehe Benchmark). Es ist nicht nur für Server und Clients geeignet, sondern auch hochkompatibel mit mobilen Geräten.
Bei geringem Speicherverbrauch verbraucht BqLog selbst im Benchmark-Fall von 10 Threads und 20.000.000 Protokolleinträgen weniger als 1 MB Speicher.
Bietet ein leistungsstarkes, hochkomprimiertes Echtzeit-Protokollformat
Kann normal in Spiel-Engines ( Unity
, Unreal
) verwendet werden, mit Unterstützung für gängige Typen, die für Unreal bereitgestellt werden.
Unterstützt UTF-8
, UTF-16
und UTF-32
Zeichen und -Strings sowie gängige Parametertypen wie Bool, Float, Double und verschiedene Längen und Typen von Ganzzahlen
Unterstützt C++20
format specifications
Asynchrone Protokollierung unterstützt Absturzüberprüfung, um Datenverlust zu vermeiden (inspiriert von XLog)
Extrem kleine Größe, da die dynamische Bibliothek nach der Android-Kompilierung nur etwa 200 KB groß ist
Generiert keine zusätzlichen Heap-Zuweisungen in Java und C# und vermeidet so die ständige Erstellung neuer Objekte während der Laufzeit
Hängt nur von der Standard-C-Sprachbibliothek und den Plattform-APIs ab und kann im Android-Modus ANDROID_STL = none
kompiliert werden
Unterstützt C++11
und spätere Kompilierungsstandards und kann unter den strengen Anforderungen von -Wall -Wextra -pedantic -Werror kompiliert werden
Das Kompilierungsmodul basiert auf CMake
und bietet Kompilierungsskripts für verschiedene Plattformen, wodurch es einfach zu verwenden ist
Unterstützt benutzerdefinierte Parametertypen
Sehr freundlich zu Codevorschlägen
Warum ist BqLog so schnell – Hochleistungs-Echtzeit-komprimiertes Protokollformat
Warum ist BqLog so schnell – Ringpuffer mit hoher Parallelität
Integrieren Sie BqLog in Ihr Projekt
Einfache Demo
Architekturübersicht
Anweisungen zur Verwendung der Hauptprozess-API
1-Erstellen eines Protokollobjekts
2-Abrufen eines Protokollobjekts
3-Protokollierungsnachrichten
4-Andere APIs
Synchrone und asynchrone Protokollierung
1. Thread-Sicherheit der asynchronen Protokollierung
Einführung in Appender
1. ConsoleAppender
2. TextFileAppender
3. CompressedFileAppender (sehr empfehlenswert)
4. RawFileAppender
Konfigurationsanweisungen
1. Vollständiges Beispiel
2. Detaillierte Erläuterung
Offline-Dekodierung von Binärformat-Appendern
Bauanleitung
1. Bibliotheksaufbau
2. Demo erstellen und ausführen
3. Anweisungen zum automatisierten Testlauf
4. Anweisungen zum Benchmark-Lauf
Erweiterte Nutzungsthemen
1. Keine Heap-Zuweisung
2. Protokollieren Sie Objekte mit Kategorieunterstützung
3. Datenschutz bei Programmabbruch
4. Über NDK und ANDROID_STL = keine
5. Benutzerdefinierte Parametertypen
6. Verwendung von BqLog in der Unreal Engine
Benchmark
1. Benchmark-Beschreibung
2. BqLog C++ Benchmark-Code
3. BqLog Java Benchmark-Code
4. Log4j-Benchmark-Code
5. Benchmark-Ergebnisse
BqLog kann in verschiedenen Formen in Ihr Projekt integriert werden. Für C++ werden dynamische Bibliotheken, statische Bibliotheken und Quelldateien unterstützt. Für Java und C# werden dynamische Bibliotheken mit Wrapper-Quellcode unterstützt. Nachfolgend finden Sie die Methoden zum Einbinden von BqLog:
Das Code-Repository enthält vorkompilierte dynamische Bibliotheksdateien, die sich in /dist/dynamic_lib/ befinden. Um BqLog mithilfe der Bibliotheksdateien in Ihr Projekt zu integrieren, müssen Sie Folgendes tun:
Wählen Sie die Ihrer Plattform entsprechende dynamische Bibliotheksdatei aus und fügen Sie sie dem Build-System Ihres Projekts hinzu.
Kopieren Sie das Verzeichnis /dist/dynamic_lib/include in Ihr Projekt und fügen Sie es der Include-Verzeichnisliste hinzu. (Wenn Sie die .framework-Bibliothek von XCode verwenden, können Sie diesen Schritt überspringen, da die .framework-Datei bereits die Header-Dateien enthält.)
Das Code-Repository enthält vorkompilierte statische Bibliotheksdateien, die sich in /dist/static_lib/ befinden. Um BqLog mithilfe der Bibliotheksdateien in Ihr Projekt zu integrieren, müssen Sie Folgendes tun:
Wählen Sie die Ihrer Plattform entsprechende statische Bibliotheksdatei aus und fügen Sie sie dem Build-System Ihres Projekts hinzu.
Kopieren Sie das Verzeichnis /dist/static_lib/include in Ihr Projekt und fügen Sie es der Include-Verzeichnisliste hinzu. (Wenn Sie die .framework-Bibliothek von XCode verwenden, können Sie diesen Schritt überspringen, da die .framework-Datei bereits die Header-Dateien enthält.)
BqLog unterstützt auch die direkte Einbindung von Quellcode in Ihr Projekt zur Kompilierung. Um BqLog mithilfe des Quellcodes zu integrieren, gehen Sie folgendermaßen vor:
Kopieren Sie das Verzeichnis /src als Quellcode-Referenz in Ihr Projekt.
Kopieren Sie das Verzeichnis /include in Ihr Projekt und fügen Sie es der Include-Verzeichnisliste hinzu.
Wenn Sie die Windows-Version in Visual Studio kompilieren, fügen Sie /Zc:__cplusplus zu den Kompilierungsoptionen hinzu, um sicherzustellen, dass die aktuelle C++-Compiler-Standardunterstützung korrekt bestimmt wird.
Wenn Sie den Quellcode im NDK von Android verwenden, lesen Sie bitte 4. Über NDK und ANDROID_STL = none für wichtige Überlegungen.
In C# kann BqLog über eine native dynamische Bibliothek und einen C#-Wrapper verwendet werden und unterstützt Mono-, Microsoft CLR- und Unity-Engines. Unity ist sowohl mit dem Mono- als auch mit dem IL2CPP-Modus kompatibel. Um BqLog in C# zu verwenden, führen Sie die folgenden Schritte aus:
Wählen Sie die Ihrer Plattform entsprechende dynamische Bibliotheksdatei aus /dist/dynamic_lib/ aus und fügen Sie sie Ihrem Projekt hinzu (für Unity siehe Unity-Import und Plug-Ins konfigurieren).
Kopieren Sie die Quellcodedateien von /wrapper/csharp/src in Ihr Projekt.
In Java kann BqLog über eine native dynamische Bibliothek und einen Java Wrapper verwendet werden und unterstützt gängige JVM-Umgebungen und Android. Um BqLog in eine JVM zu integrieren, gehen Sie folgendermaßen vor:
Wählen Sie die Ihrer Plattform entsprechende dynamische Bibliotheksdatei aus /dist/dynamic_lib/ aus und fügen Sie sie Ihrem Projekt hinzu.
Kopieren Sie die Quellcodedateien von /wrapper/java/src in Ihr Projekt.
(Optional) Kopieren Sie das Verzeichnis /dist/dynamic_lib/include in Ihr Projekt und fügen Sie es der Include-Verzeichnisliste hinzu, wenn Sie BqLog vom NDK aus aufrufen möchten.
Der folgende Code gibt über 1000 Protokolle an Ihre Konsole aus (oder ADB Logcat, wenn Sie Android verwenden).
#wenn definiert(WIN32) #include <windows.h>#endif#include <string>#include <bq_log/bq_log.h>int main() { #if define(WIN32) // Windows-Befehlszeile auf UTF-8 umstellen, da BqLog den gesamten endgültigen Text in UTF-8-Codierung ausgibt, um Anzeigeprobleme zu vermeiden SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); #endif // Diese Zeichenfolge ist die Protokollkonfiguration. Hier wird ein Logger mit einem Appender (Ausgabeziel) namens appender_0 konfiguriert, der an die Konsole ausgibt. std::string config = R"( # Das Ausgabeziel dieses Appenders ist die Konsole appenders_config.appender_0.type=console # Dieser Appender verwendet die lokale Zeit für Zeitstempel appenders_config.appender_0.time_zone=default local time # Dieser Appender gibt Protokolle dieser 6 Ebenen aus (keine Leerzeichen dazwischen) appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal] )"; bq::log log = bq::log::create_log("my_first_log", config); // Erstellen Sie ein Protokollobjekt mit der Konfiguration for(int i = 0; i < 1024; ++i) { log.info("Dies ist ein Info-Testprotokoll, die Formatzeichenfolge ist 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"Dies ist ein Fehlertestprotokoll, die Formatzeichenfolge ist UTF-32"); bq::log::force_flush_all_logs(); // BqLog verwendet standardmäßig die asynchrone Ausgabe. Um sicherzustellen, dass die Protokolle vor dem Beenden des Programms sichtbar sind, erzwingen Sie die einmalige Synchronisierung der Ausgabe durch Flush. 0 zurückgeben; }
using System.Text;using System;public class demo_main { public static void Main(string[] args) { Console.OutputEncoding = Encoding.UTF8; Console.InputEncoding = Encoding.UTF8; string config = @" # Das Ausgabeziel dieses Appenders ist die Konsole appenders_config.appender_0.type=console # Dieser Appender verwendet die lokale Zeit für Zeitstempel pppenders_config.appender_0.time_zone=default local time # Dieser Appender gibt Protokolle dieser 6 Ebenen aus (keine Leerzeichen in zwischen) appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal] "; bq.log log = bq.log.create_log("my_first_log", config); // Erstellen Sie ein Protokollobjekt mit der Konfiguration for (int i = 0; i < 1024; ++i) { log.info("Dies ist ein Info-Testprotokoll, die Formatzeichenfolge ist UTF-16, param int:{}, param bool :{}, param string:{}, param float:{}", i, true, " String-Text", 4.3464f); } bq.log.force_flush_all_logs(); Console.ReadKey(); }}
public class demo_main { public static void main(String[] args) { // TODO Automatisch generierter Methodenstub String config = """ # Das Ausgabeziel dieses Appenders ist die Konsole appenders_config.appender_0.type=console # Dieser Appender verwendet die Ortszeit für Zeitstempel appenders_config.appender_0.time_zone=default local time # Dieser Appender gibt Protokolle dieser 6 Ebenen aus (keine Leerzeichen dazwischen) appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal] „““; bq.log log = bq.log.create_log("my_first_log", config); // Erstellen Sie ein Protokollobjekt mit der Konfiguration für (int i = 0; i < 1024; ++i) { log.info("Dies ist ein Info-Testprotokoll, die Formatzeichenfolge ist UTF-16, param int:{}, param bool :{}, param string:{}, param float:{}", i, true, „String-Text“, 4.3464f); } bq.log.force_flush_all_logs(); } }
Das obige Diagramm veranschaulicht deutlich die Grundstruktur von BqLog. Auf der rechten Seite des Diagramms befindet sich die interne Implementierung der BqLog-Bibliothek, während auf der linken Seite Ihr Programm und Ihr Code angezeigt werden. Ihr Programm kann BqLog mithilfe der bereitgestellten Wrapper (objektorientierte APIs für verschiedene Sprachen) aufrufen. Im Diagramm werden zwei Protokolle erstellt: eines mit dem Namen „Protokoll A“ und das andere mit dem Namen „Protokoll B“. Jedes Protokoll ist an einen oder mehrere Appender angehängt. Ein Appender kann als Ausgabeziel des Protokollinhalts verstanden werden. Dies können die Konsole (ADB Logcat-Protokolle für Android), Textdateien oder sogar spezielle Formate wie komprimierte Protokolldateien oder reguläre Dateien im Binärprotokollformat sein.
Innerhalb desselben Prozesses können Wrapper für verschiedene Sprachen auf dasselbe Log-Objekt zugreifen. Wenn beispielsweise ein Log-Objekt mit dem Namen Log A in Java erstellt wird, kann es auch von C++-Seite aus unter dem Namen Log A aufgerufen und verwendet werden.
In extremen Fällen, beispielsweise bei einem von Unity entwickelten Spiel, das auf dem Android-System läuft, könnten Sie Java-, Kotlin-, C#- und C++-Sprachen in derselben App einbinden. Sie können alle dasselbe Protokollobjekt verwenden. Sie können das Protokoll auf der Java-Seite mit create_log erstellen und dann mit get_log_by_name in anderen Sprachen darauf zugreifen.
Hinweis: Die folgenden APIs werden in der Klasse bq::log (oder bq.log) deklariert. Um Platz zu sparen, werden nur die C++-APIs aufgeführt. Die APIs in Java und C# sind identisch und werden hier nicht wiederholt.
In C++ ist bq::string
der UTF-8-String-Typ in der BqLog-Bibliothek. Sie können auch Zeichenfolgen im C-Stil wie char oder std::string
oder std::string_view
übergeben, die automatisch und implizit konvertiert werden.
Ein Protokollobjekt kann mit der statischen Funktion create_log erstellt werden. Seine Erklärung lautet wie folgt:
//C++ API /// <summary> /// Erstellen Sie ein Protokollobjekt /// </summary> /// <param name="log_name">Wenn der Protokollname eine leere Zeichenfolge ist, weist Ihnen bqLog automatisch ein zu eindeutiger Protokollname. Wenn der Protokollname bereits vorhanden ist, wird das zuvor vorhandene Protokollobjekt zurückgegeben und die vorherige Konfiguration mit der neuen Konfiguration überschrieben.</param> /// <param name="config_content">Protokollkonfigurationszeichenfolge</param> /// <returns>Ein Protokollobjekt. Wenn die Erstellung fehlschlägt, gibt die Methode is_valid() des Objekts false zurück.</returns> static log create_log(const bq::string& log_name, const bq::string& config_content);
Der Code erstellt ein Protokollobjekt, indem er den Namen des Protokollobjekts und eine Konfigurationszeichenfolge übergibt. Die Protokollkonfiguration finden Sie in den Konfigurationsanweisungen. Hier sind einige wichtige Punkte, die Sie beachten sollten:
Unabhängig davon, ob es sich um C# oder Java handelt, ist das zurückgegebene Protokollobjekt niemals null. Aufgrund von Konfigurationsfehlern oder aus anderen Gründen kann jedoch ein ungültiges Protokollobjekt erstellt werden. Daher sollten Sie die Funktion is_valid() verwenden, um das zurückgegebene Objekt zu überprüfen. Das Ausführen von Vorgängen an einem ungültigen Objekt kann zum Absturz des Programms führen.
Wenn als Protokollname eine leere Zeichenfolge übergeben wird, generiert bqLog automatisch einen eindeutigen Protokollnamen, z. B. „AutoBqLog_1“.
Wenn Sie „create_log“ für ein bereits vorhandenes Protokollobjekt mit demselben Namen aufrufen, wird kein neues Protokollobjekt erstellt, sondern die vorherige Konfiguration mit der neuen überschrieben. Einige Parameter können dabei jedoch nicht geändert werden; Einzelheiten finden Sie in den Konfigurationsanweisungen.
Außer bei Verwendung im NDK (siehe 4. Über NDK und ANDROID_STL = none) können Sie das Protokollobjekt in anderen Situationen mithilfe dieser API direkt in globalen oder statischen Variablen initialisieren.
Wenn bereits an anderer Stelle ein Protokollobjekt erstellt wurde, können Sie das erstellte Protokollobjekt direkt mit der Funktion get_log_by_name abrufen.
//C++ API /// <summary> /// Ein Protokollobjekt anhand seines Namens abrufen /// </summary> /// <param name="log_name">Name des Protokollobjekts, das Sie suchen möchten</param > /// <returns>Ein Protokollobjekt. Wenn das Protokollobjekt mit einem bestimmten Namen nicht gefunden wurde, gibt die Methode is_valid() davon false zurück</returns> static log get_log_by_name(const bq::string& log_name);
Sie können diese Funktion auch verwenden, um ein Protokollobjekt in globalen Variablen oder statischen Funktionen zu initialisieren. Beachten Sie jedoch, dass Sie sicherstellen müssen, dass das Protokollobjekt mit dem angegebenen Namen bereits vorhanden ist. Andernfalls ist das zurückgegebene Protokollobjekt unbrauchbar und seine Methode is_valid() gibt false zurück.
///Kernprotokollfunktionen, es gibt 6 Protokollebenen: ///verbose, debug, info, warning, error, fatal template<typename STR> bq::enable_if_t<is_bq_log_str<STR>::value, bool> verbose(const STR& log_content) const; template<Typname STR, Typname...Args> bq::enable_if_t<is_bq_log_str<STR>::value, bool> verbose(const STR& log_format_content, const Args&... args) const; template<Typname STR> bq::enable_if_t<is_bq_log_str<STR>::value, bool> debug(const STR& log_content) const; template<Typname STR, Typname...Args> bq::enable_if_t<is_bq_log_str<STR>::value, bool> debug(const STR& log_format_content, const Args&... args) const; template<Typname STR> bq::enable_if_t<is_bq_log_str<STR>::value, bool> info(const STR& log_content) const; template<Typname STR, Typname...Args> bq::enable_if_t<is_bq_log_str<STR>::value, bool> info(const STR& log_format_content, const Args&... args) const; template<Typname STR> bq::enable_if_t<is_bq_log_str<STR>::value, bool> warning(const STR& log_content) const; template<Typname STR, Typname...Args> bq::enable_if_t<is_bq_log_str<STR>::value, bool> warning(const STR& log_format_content, const Args&... args) const; template<Typname STR> bq::enable_if_t<is_bq_log_str<STR>::value, bool> error(const STR& log_content) const; template<Typname STR, Typname...Args> bq::enable_if_t<is_bq_log_str<STR>::value, bool> error(const STR& log_format_content, const Args&... args) const; template<Typname STR> bq::enable_if_t<is_bq_log_str<STR>::value, bool> fatal(const STR& log_content) const; template<Typname STR, Typname...Args> bq::enable_if_t<is_bq_log_str<STR>::value, bool> fatal(const STR& log_format_content, const Args&... args) const;
Achten Sie beim Protokollieren von Nachrichten auf drei wichtige Punkte:
Wie Sie sehen können, sind unsere Protokolle in sechs Ebenen unterteilt: ausführlich, Debug, Info, Warnung, Fehler und schwerwiegend, konsistent mit Android. Ihre Bedeutung nimmt sequentiell zu. Bei der Ausgabe auf der Konsole erscheinen sie in unterschiedlichen Farben.
Der STR-Parameter ähnelt dem ersten Parameter von printf und kann verschiedene gängige Zeichenfolgentypen haben, darunter:
Javas java.lang.String
C#-String
Verschiedene Codierungen von C++-Zeichenfolgen im C-Stil und 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
und sogar benutzerdefinierte Zeichenfolgentypen, auf die Sie unter „Benutzerdefinierte Parametertypen“ verweisen können.
Sie können nach dem STR-Parameter verschiedene Parameter hinzufügen. Diese Parameter werden an den angegebenen Stellen in der STR formatiert und folgen dabei ähnlichen Regeln wie std::format von C++20 (mit Ausnahme der fehlenden Unterstützung für Positionsargumente und Datum-Uhrzeit-Format). Beispielsweise stellt die Verwendung eines einzelnen {} eine Standardformatierung eines Parameters dar, und {:.2f} gibt die Genauigkeit für die Formatierung einer Gleitkommazahl an. Versuchen Sie, formatierte Parameter zur Ausgabe von Protokollen zu verwenden, anstatt Zeichenfolgen manuell zu verketten. Dieser Ansatz ist optimal für Leistung und Speicherung im komprimierten Format.
Zu den derzeit unterstützten Parametertypen gehören:
Nullzeiger (Ausgabe als Null)
Zeiger (Ausgabe als hexadezimale Adresse beginnend mit 0x)
bool
Einzelbyte-Zeichen (char)
Doppelbyte-Zeichen (char16_t, wchar_t, C#-Zeichen, Java-Zeichen)
Vier-Byte-Zeichen (char32_t oder wchar_t)
8-Bit-Ganzzahlen
8-Bit-Ganzzahlen ohne Vorzeichen
16-Bit-Ganzzahlen
16-Bit-Ganzzahlen ohne Vorzeichen
32-Bit-Ganzzahlen
32-Bit-Ganzzahlen ohne Vorzeichen
64-Bit-Ganzzahlen
64-Bit-Ganzzahlen ohne Vorzeichen
32-Bit-Gleitkommazahlen
64-Bit-Gleitkommazahlen
Andere unbekannte POD-Typen in C++ (beschränkt auf Größen von 1, 2, 4 oder 8 Byte, behandelt als int8, int16, int32 bzw. int64)
Zeichenfolgen, einschließlich aller im STR-Parameter genannten Zeichenfolgentypen
Jede Klasse oder jedes Objekt in C# und Java (gibt ihren ToString()-String aus)
Benutzerdefinierte Parametertypen, wie unter Benutzerdefinierte Parametertypen beschrieben
Es gibt weitere häufig verwendete APIs, die bestimmte Aufgaben erfüllen können. Ausführliche API-Beschreibungen finden Sie unter bq_log/bq_log.h sowie in der bq.log-Klasse in Java und C#. Hier sind einige wichtige APIs, die hervorgehoben werden müssen:
/// <summary> /// Deinitialisieren Sie BqLog. Bitte rufen Sie diese Funktion auf, bevor Ihr Programm existiert. /// </summary> static void uninit();
Es wird empfohlen, uninit()
auszuführen, bevor Sie das Programm beenden oder die selbst implementierte dynamische Bibliothek, die BqLog verwendet, deinstallieren. Andernfalls kann das Programm unter bestimmten Umständen beim Beenden hängen bleiben.
/// <Zusammenfassung> /// Wenn bqLog asynchron ist, kann ein Absturz im Programm dazu führen, dass die Protokolle im Puffer nicht auf der Festplatte gespeichert werden. /// Wenn diese Funktion aktiviert ist, versucht bqLog im Falle eines Absturzes, eine erzwungene Leerung der Protokolle im Puffer durchzuführen. /// Diese Funktionalität garantiert jedoch keinen Erfolg und unterstützt nur POSIX-Systeme. /// </summary> static void enable_auto_crash_handle();
Eine ausführliche Einführung finden Sie unter Datenschutz bei Programmabnormalem Beenden
/// <summary> /// Den Puffer aller Protokollobjekte synchron leeren /// um sicherzustellen, dass alle Daten im Puffer nach dem Aufruf verarbeitet werden. /// </summary> static void force_flush_all_logs(); /// <Zusammenfassung> /// Leeren Sie den Puffer dieses Protokollobjekts synchron ///, um sicherzustellen, dass alle Daten im Puffer nach dem Aufruf verarbeitet werden. /// </summary> void force_flush();
Da bqLog standardmäßig die asynchrone Protokollierung verwendet, kann es vorkommen, dass Sie alle Protokolle sofort synchronisieren und ausgeben möchten. In solchen Fällen müssen Sie den Aufruf von force_flush() erzwingen.
/// <summary> /// Registrieren Sie einen Rückruf, der immer dann aufgerufen wird, wenn eine Konsolenprotokollmeldung ausgegeben wird. /// Dies kann für ein externes System verwendet werden, um die Konsolenprotokollausgabe zu überwachen. /// </summary> /// <param name="callback"></param> static void register_console_callback(bq::type_func_ptr_console_callback callback); /// <summary> /// Registrierung eines Konsolenrückrufs aufheben. /// </summary> /// <param name="callback"></param> static void unregister_console_callback(bq::type_func_ptr_console_callback callback);
Die Ausgabe von ConsoleAppender geht an die Konsole oder ADB Logcat-Protokolle unter Android, aber dies deckt möglicherweise nicht alle Situationen ab. In benutzerdefinierten Spiele-Engines oder benutzerdefinierten IDEs wird beispielsweise ein Mechanismus bereitgestellt, um eine Rückruffunktion für jede Konsolenprotokollausgabe aufzurufen. Dadurch können Sie das Konsolenprotokoll an einer beliebigen Stelle in Ihrem Programm erneut verarbeiten und ausgeben.
Zusätzliche Vorsicht: Geben Sie keine synchronisierten BQ-Protokolle innerhalb des Konsolenrückrufs aus, da dies leicht zu Deadlocks führen kann.
/// <summary> /// Aktivieren oder deaktivieren Sie den Konsolen-Appender-Puffer. /// Da unser Wrapper sowohl in virtuellen C#- als auch in Java-Maschinen ausgeführt werden kann und wir Rückrufe nicht direkt von einem nativen Thread aufrufen möchten, /// können wir diese Option aktivieren. Auf diese Weise werden alle Konsolenausgaben im Puffer gespeichert, bis wir sie abrufen. /// </summary> /// <param name="enable"></param> /// <returns></returns> static void set_console_buffer_enable(bool enable); /// <summary> /// Threadsicher einen Protokolleintrag aus dem Konsolen-Appender-Puffer abrufen und entfernen. /// Wenn der Konsolen-Appender-Puffer nicht leer ist, wird die Funktion on_console_callback für diesen Protokolleintrag aufgerufen. /// Bitte stellen Sie sicher, dass innerhalb der Callback-Funktion keine synchronisierten BQ-Protokolle ausgegeben werden. /// </summary> /// <param name="on_console_callback">Eine Rückruffunktion, die für den abgerufenen Protokolleintrag aufgerufen werden soll, wenn der Konsolen-Appender-Puffer nicht leer ist</param> /// <returns>True, wenn der Der Konsolen-Appender-Puffer ist nicht leer und es wird ein Protokolleintrag abgerufen. andernfalls wird False zurückgegeben.</returns> static bool fetch_and_remove_console_buffer(bq::type_func_ptr_console_callback on_console_callback);
Zusätzlich zum Abfangen der Konsolenausgabe über einen Konsolenrückruf können Sie Konsolenprotokollausgaben aktiv abrufen. Manchmal möchten wir möglicherweise nicht, dass die Konsolenprotokollausgabe über einen Rückruf erfolgt, weil Sie nicht wissen, von welchem Thread der Rückruf kommt (z. B. führt die VM in einigen virtuellen C#-Maschinen oder JVMs möglicherweise eine Garbage Collection durch, wenn die Konsole Rückruf aufgerufen wird, was möglicherweise zu Hängen oder Abstürzen führen kann).
Die hier verwendete Methode beinhaltet die Aktivierung des Konsolenpuffers über set_console_buffer_enable
. Dadurch wird jede Konsolenprotokollausgabe im Speicher gespeichert, bis wir fetch_and_remove_console_buffer
aktiv aufrufen, um sie abzurufen. Wenn Sie sich für diese Methode entscheiden, denken Sie daher daran, Protokolle umgehend abzurufen und zu löschen, um nicht freigegebenen Speicher zu vermeiden.
Zusätzliche Vorsicht: Geben Sie keine synchronisierten BQ-Protokolle innerhalb des Konsolenrückrufs aus, da dies leicht zu Deadlocks führen kann.
Zusätzliche Vorsicht: Wenn Sie diesen Code in einer IL2CPP-Umgebung verwenden, stellen Sie bitte sicher, dass on_console_callback als statisch unsicher markiert und mit dem Attribut [MonoPInvokeCallback(typeof(type_console_callback))] versehen ist.
/// <summary> /// Ändern Sie die Protokollkonfiguration, aber einige Felder, wie zum Beispiel buffer_size, können nicht geändert werden. /// </summary> /// <param name="config_content"></param> /// <returns></returns> bool reset_config(const bq::string& config_content);
Manchmal möchten Sie möglicherweise die Konfiguration eines Protokolls in Ihrem Programm ändern. Zusätzlich zur Neuerstellung des Protokollobjekts zum Überschreiben der Konfiguration (siehe Erstellen eines Protokollobjekts) können Sie auch die Reset-Schnittstelle verwenden. Beachten Sie jedoch, dass nicht alle Konfigurationselemente auf diese Weise geändert werden können. Einzelheiten finden Sie in den Konfigurationsanweisungen
/// <Zusammenfassung> /// Einen bestimmten Appender vorübergehend deaktivieren oder aktivieren. /// </summary> /// <param name="appender_name"></param> /// <param name="enable"></param> void set_appenders_enable(const bq::string& appender_name, bool enable) ;
Standardmäßig sind die Appender in der Konfiguration aktiv, hier wird jedoch ein Mechanismus bereitgestellt, um sie vorübergehend zu deaktivieren und wieder zu aktivieren.
/// <summary> /// Funktioniert nur, wenn Snapshot konfiguriert ist. /// Es wird den Snapshot-Puffer in Text dekodieren. /// </summary> /// <param name="use_gmt_time">ob der Zeitstempel jedes Protokolls GMT-Zeit oder Ortszeit ist</param> /// <returns>der dekodierte Snapshot-Puffer</returns> bq::string take_snapshot(bool use_gmt_time) const;
Manchmal erfordern bestimmte Sonderfunktionen die Ausgabe des letzten Teils der Protokolle, was mithilfe der Snapshot-Funktion erfolgen kann. Um diese Funktion zu aktivieren, müssen Sie zunächst den Snapshot in der Protokollkonfiguration aktivieren und die maximale Puffergröße in Bytes festlegen. Darüber hinaus müssen Sie die Protokollebenen und Kategorien angeben, die für den Snapshot gefiltert werden sollen (optional). Eine detaillierte Konfiguration finden Sie unter Snapshot-Konfiguration. Wenn ein Snapshot benötigt wird, gibt der Aufruf von take_snapshot() die formatierte Zeichenfolge zurück, die die neuesten im Snapshot-Puffer gespeicherten Protokolleinträge enthält. In C++ ist der Typ bq::string
, der implizit in std::string
konvertiert werden kann.
namespace bq{ namespace tools { //Dies ist eine Dienstprogrammklasse zum Dekodieren binärer Protokollformate. //Um es zu verwenden, erstellen Sie zuerst ein log_decoder-Objekt und //rufen Sie dann seine Dekodierfunktion zum Dekodieren auf. //Nach jedem erfolgreichen Aufruf //können Sie get_last_decoded_log_entry() verwenden, um das dekodierte Ergebnis abzurufen. //Jeder Aufruf dekodiert einen Protokolleintrag. struct log_decoder { Privat: bq::string decode_text_; bq::appender_decode_result result_ = bq::appender_decode_result::success; uint32_t handle_ = 0; public: /// <summary> /// Erstellen Sie ein log_decoder-Objekt, wobei jedes log_decoder-Objekt einer binären Protokolldatei entspricht. /// </summary> /// <param name="log_file_path">der Pfad einer binären Protokolldatei, kann ein relativer oder absoluter Pfad sein</param> log_decoder(const bq::string& log_file_path); ~log_decoder(); /// <summary> /// Einen Protokolleintrag dekodieren. Jeder Aufruf dieser Funktion dekodiert nur 1 Protokolleintrag /// </summary> /// <returns>Dekodierergebnis, appender_decode_result::eof bedeutet, dass die gesamte Protokolldatei dekodiert wurde</returns> bq::appender_decode_result decode(); /// <summary> /// das letzte Dekodierungsergebnis abrufen /// </summary> /// <returns></returns> bq::appender_decode_result get_last_decode_result() const; /// <summary> /// den Inhalt des letzten Decodierungsprotokolleintrags abrufen /// </summary> /// <returns></returns> const bq::string& get_last_decoded_log_entry() const; }; } }
Dies ist eine Dienstprogrammklasse, die zur Laufzeit Protokolldateien dekodieren kann, die von binären Appendern wie CompressedFileAppender und RawFileAppender ausgegeben werden.
Um es zu verwenden, erstellen Sie zunächst ein log_decoder-Objekt. Dann wird jedes Mal, wenn Sie die Funktion decode() aufrufen, nacheinander ein Protokolleintrag dekodiert. Wenn das zurückgegebene Ergebnis bq::appender_decode_result::success ist, können Sie get_last_decoded_log_entry() aufrufen, um den formatierten Textinhalt des letzten dekodierten Protokolleintrags abzurufen. Wenn das Ergebnis bq::appender_decode_result::eof lautet, bedeutet dies, dass alle Protokolle vollständig gelesen wurden.
Mit BqLog können Sie über die Einstellung „thread_mode“ konfigurieren, ob ein Protokollobjekt synchron oder asynchron ist. Die Hauptunterschiede zwischen diesen beiden Modi sind folgende:
Synchrone Protokollierung | Asynchrone Protokollierung | |
---|---|---|
Verhalten | Nach Aufruf der Logging-Funktion wird das Log sofort in den entsprechenden Appender geschrieben. | Nach dem Aufruf der Protokollierungsfunktion wird das Protokoll nicht sofort geschrieben; Stattdessen wird es zur regelmäßigen Verarbeitung an einen Arbeitsthread übergeben. |
Leistung | Niedrig, da der Thread, der das Protokoll schreibt, blockieren und darauf warten muss, dass das Protokoll an den entsprechenden Appender geschrieben wird, bevor er von der Protokollierungsfunktion zurückkehrt. | Hoch, da der Thread, der das Protokoll schreibt, nicht auf die tatsächliche Ausgabe warten muss und sofort nach der Protokollierung zurückkehren kann. |
Thread-Sicherheit | Hoch, erfordert jedoch, dass die Protokollparameter während der Ausführung der Protokollierungsfunktion nicht geändert werden. | Hoch, erfordert jedoch, dass die Protokollparameter während der Ausführung der Protokollierungsfunktion nicht geändert werden. |
Ein häufiges Missverständnis über die asynchrone Protokollierung besteht darin, dass sie weniger Thread-sicher ist, da Benutzer befürchten, dass die Parameter möglicherweise zurückgefordert werden, wenn der Arbeitsthread das Protokoll verarbeitet. Zum Beispiel:
{ const char str_array[5] = {'T', 'E', 'S', 'T', '�'}; const char* str_ptr = str_array; log_obj.info("Dies ist der Testparameter: {}, {}", str_array, str_ptr); }
Im obigen Beispiel wird str_array
auf dem Stapel gespeichert und sobald der Bereich verlassen wird, ist sein Speicher nicht mehr gültig. Benutzer befürchten möglicherweise, dass str_array
und str_ptr
bei Verwendung der asynchronen Protokollierung ungültige Variablen sind, wenn der Arbeitsthread das Protokoll verarbeitet.
Eine solche Situation wird jedoch nicht eintreten, da BqLog während der Ausführung der info
-Funktion alle Parameterinhalte in seinen internen ring_buffer
kopiert. Sobald die info
-Funktion zurückkehrt, werden die externen Variablen wie str_array
oder str_ptr
nicht mehr benötigt. Darüber hinaus speichert der ring_buffer
keine const char*
-Zeigeradresse, sondern immer die gesamte Zeichenfolge.
Das tatsächliche potenzielle Problem entsteht im folgenden Szenario:
static std::string global_str = "Hallo Welt"; // Dies ist eine globale Variable, die von mehreren Threads geändert wurde.void thread_a() { log_obj.info("Dies ist Testparameter: {}", global_str); }
Wenn sich der Inhalt von global_str
während der Ausführung der info
-Funktion ändert, kann es zu undefiniertem Verhalten kommen. BqLog wird sein Bestes tun, um einen Absturz zu verhindern, die Richtigkeit der endgültigen Ausgabe kann jedoch nicht garantiert werden.
Ein Appender stellt das Protokollausgabeziel dar. Das Konzept der Appender in bqLog ist grundsätzlich dasselbe wie in Log4j. Derzeit bietet bqLog die folgenden Arten von Appendern:
Das Ausgabeziel dieses Appenders ist die Konsole, einschließlich Androids ADB und der entsprechenden Konsole auf iOS. Die Textkodierung ist UTF-8.
Dieser Appender gibt Protokolldateien direkt im UTF-8-Textformat aus.
Dieser Appender gibt Protokolldateien in einem komprimierten Format aus, dem highly recommended format by bqLog
. Es hat die höchste Leistung unter allen Appendern und erzeugt die kleinste Ausgabedatei. Die endgültige Datei muss jedoch dekodiert werden. Die Dekodierung kann zur Laufzeit oder Offline-Dekodierung erfolgen.
Dieser Appender gibt den binären Protokollinhalt aus dem Speicher direkt in eine Datei aus. Seine Leistung ist höher als die von TextFileAppender, verbraucht jedoch mehr Speicherplatz. Die endgültige Datei muss dekodiert werden. Die Dekodierung kann zur Laufzeit oder Offline-Dekodierung erfolgen. Die Verwendung dieses Appenders wird nicht empfohlen.
Nachfolgend finden Sie einen umfassenden Vergleich der verschiedenen Appender:
Name | Ausgabeziel | Direkt lesbar | Ausgabeleistung | Ausgabegröße |
---|---|---|---|---|
ConsoleAppender | Konsole | ✔ | Niedrig | - |
TextFileAppender | Datei | ✔ | Niedrig | Groß |
CompressedFileAppender | Datei | ✘ | Hoch | Klein |
RawFileAppender | Datei | ✘ | Medium | Groß |
„Konfiguration“ bezieht sich auf die Konfigurationszeichenfolge in den Funktionen „create_log“ und „reset_config“. Diese Zeichenfolge verwendet das Eigenschaftendateiformat und unterstützt #-Kommentare (denken Sie jedoch daran, eine neue Zeile für Kommentare mit # zu beginnen).
Nachfolgend finden Sie ein vollständiges Beispiel:
# Diese Konfiguration legt ein Protokollobjekt mit insgesamt 5 Appendern ein, darunter zwei TextfileAppenders, die auf zwei verschiedene Dateien ausgeben.# Der erste Appender heißt Appender_0, und sein Typ ist consoleAppenderAppeders_config.appender_0.type = console# Die Zeitzone für Appender_0 ist Is0 Is0 is0 ist Die lokalen TimeAppenders_Config.Appender_0 des Systems .Time_Zone = Default Local Time# Appender_0 gibt alle 6 Protokollebenen aus (Hinweis: Es sollte keine Leerzeichen zwischen Protokollebenen geben, oder es wird nicht analysiert) appenders_config.appender_0.levels = [wörtlich, Debug, Info, Warnung, Fehler, fatal]# Der zweite Appender heißt Appender_1 und sein Typ ist TextFileAppenderers_Config. .Appender_1.type = text_file# Die Zeitzone für Appender_1 ist GMT, nämlich UTC+0Appenders_config.Appender_1.time_zone = gmt# Appender_1 Ausgibt nur Protokolle von Level -Info, und andere werden ignoriert, dass Anpenders_config.appender_1.levels = [Info, Warnung, Fehler, Fatal]# der Pfad für Appender_1 in der Relativ -BQlog -Direktorie sein wird in der Relativ -BQlog -Direktorie. des Programms, bei dem Dateinamen mit normalem, gefolgt vom Datum und der .log -Erweiterung# auf iOS, wird es in gespeichert /var/mobile/container/data/application/[app]/bibliothek/caches/bqlog# auf Android wird es in [android.content.context.getExternalFilesDir ()]/bqlogAppenders_config.appender_1.File_name = bqlog/normal gespeichert/normal/normal/normal/normal # Die maximale Dateigröße beträgt 10.000.000 Bytes; Wenn es überschritten wird, wird eine neue Datei erstelltAppenders_config.appender_1.max_file_size = 10000000# Dateien älter als zehn Tage werden gereinigt upAppenders_config.appender_1.expire_time_days = 10# if die Gesamtgröße der Ausgabe überschreitet überschreitend 100.000.000 bytes, Dateien werden angefangen, beginnend mit der von der Ausgabe, von der von der Abgabe abgereinigt, von der von der Ausgabe werden von den von der abgereinigten Dateien überschritten. oldestAppenders_config.appender_1.capacity_limit = 100000000# Der dritte Appender heißt Appender_2 und sein Typ ist TextFileAppenderAppenders_config.appender_2.type = text_file# Appender_2 Ausgabe aller Ebenen von logsAppentern_config.appender_Appender_2.Appender_2. Relatives BQlog -Verzeichnis des Programms, wobei Dateinamen beginnt mit new_normal, gefolgt vom Datum und dem .log -Erweiterungsanpendler_Config.Appender_2.File_Name = Bqlog/new_normal# Diese Option ist nur auf Android wirksam, speichern Protokolle im internen Speicherverzeichnis, nämlich [Android.content.context.getFilesDir ()/BQlogappenders_Configentenders_Configentendier ()/BQlogAnders_Configentenders. .Appender_2.is_in_sandbox = true# Der vierte Appender heißt Appender_3 und sein Typ ist CompressedFileAppenderAppenders_config.Appender_3.type = compressed_file# Appender_3 gibt alle Ebenen von logsAppenders_config.appender_3.levels = [alle]# Der Pfad für Appender_3 aus dem Absolute Pfad ~/bqlog -Verzeichnis des Programms mit Filennamen mit Compress_log aus. das Datum und das .logCompr ErweiterungAppenders_config.Appender_3.File_name = ~/bqlog/compress_log# Der fünfte Appender heißt Appender_4 und sein Typ ist RawFileAppenderAppenders_Config.Appender_4.type = raw_file# Appender_4, Default. wird alle Ebenen von ausgeben logsAppenders_config.appender_4.levels = [alle]# Der Pfad für Appender_4 befindet werden nur verarbeitet, wenn ihre Kategorie mit Modulea, Moduleb.Systemc beginnt, ansonsten alle ignoriert werden (das Konzept der Kategorie wird ausführlich erläutert In den erweiterten Nutzungsthemen später) appenders_config.appender_4.categories_mask = [modulea, moduleb.systemc]# Die Gesamtgröße Asynchroner Puffer beträgt 65535 Bytes; Die spezifische Bedeutung wird laterlog erklärt. Die spezifische Bedeutung wird laterlog erläutert. [*Standard, Modulea, Moduleb.Systemc]# Dies ist ein asynchrones Protokoll; Asynchronische Protokolle sind die höchsten Leistung und empfohlenen Protokoll -Type -Type.thread_mode = async# Wenn die Protokollebene Fehler oder tödlich ist, geben Sie mit jedem Protokoll -Eintragslog.print_stack_levels = [fehler, fatal]# Snapshot -Funktionalität aktivieren, Snapshot -Cache -Größe ist 64KsnapShot .Buffer_size = 65536# Es werden nur Protokolle mit Informationen und Fehlerstufen im Snapshotsnapshot.levels = [info, Fehler]# Nur Protokolle aufgezeichnet Wessen Kategorie mit Modulea beginnt, wird Moduleb.Systemc im Snapshot aufgezeichnet. Andernfalls werden sie ignoriert.
Das appenders_config
ist eine Reihe von Konfigurationen für Appender. Der erste Parameter nach appenders_config
ist der Name des Appender, und alle Appender mit demselben Namen teilen dieselbe Konfiguration.
Name | Erforderlich | Konfigurierbare Werte | Standard | Anwendbar für KonsoleAppender | Anwendbar für TextFileAppender | Anwendbar auf Compressed fileAppender | Anwendbar auf RawFileAppender |
---|---|---|---|---|---|---|---|
Typ | ✔ | Konsole, text_file, compressed_file, raw_file | ✔ | ✔ | ✔ | ✔ | |
aktivieren | ✘ | Ob der Appender standardmäßig aktiviert ist | WAHR | ✔ | ✔ | ✔ | ✔ |
Ebenen | ✘ | Auswahl an Protokollebenen | [alle] | ✔ | ✔ | ✔ | ✔ |
TIME_ZONE | ✘ | GMT oder eine andere Zeichenfolge | Ortszeit | ✔ | ✔ | ✔ | ✔ |
Dateiname | ✔ | Relativer oder absoluter Weg | ✘ | ✔ | ✔ | ✔ | |
is_in_sandbox | ✘ | Richtig, falsch | FALSCH | ✘ | ✔ | ✔ | ✔ |
MAX_FILE_SIZE | ✘ | Positive Ganzzahl oder 0 | 0 | ✘ | ✔ | ✔ | ✔ |
expire_time_days | ✘ | Positive Ganzzahl oder 0 | 0 | ✘ | ✔ | ✔ | ✔ |
Kapazität_Limit | ✘ | Positive Ganzzahl oder 0 | 0 | ✘ | ✔ | ✔ | ✔ |
categories_mask | ✘ | Reihe von Saiten in [] eingeschlossen in [] | Leer | ✔ | ✔ | ✔ | ✔ |
Gibt den Typ des Appender an.
console
: Repräsentiert ConsoleAppender
text_file
: repräsentiert textFileAppender
compressed_file
: repräsentiert Compressed fileAppender
raw_file
: Repräsentiert RawFileAppender
Standardmäßig true
. Wenn der Appender auf false
festgelegt wird, wird standardmäßig deaktiviert und kann später mit set_appenders_enable
aktiviert werden.
Ein Array, das in []
eingeschlossen ist, das eine beliebige Kombination aus verbose
, debug
, info
, warning
, error
, fatal
oder [all]
enthält, um alle Stufen zu akzeptieren. HINWEIS: Fügen Sie keine Leerzeichen zwischen den Ebenen ein, sonst wird es nicht analysiert.
Gibt die Zeitzone der Protokolle an. gmt
repräsentiert Greenwich Mean Time (UTC+0), und jede andere Zeichenfolge oder leer wird die lokale Zeitzone verwendet. Die Zeitzone betrifft zwei Dinge:
Der Zeitstempel von formatierten Textprotokollen (anwendbar für KonsoleAppender und TextFileAppender)
Eine neue Protokolldatei wird erstellt, wenn Mitternacht in der angegebenen Zeitzone gekreuzt wird (gilt für TextFileAppender, CompressedFileAppender und RawFileAppender).
Das Präfix des Pfades und des Dateinamens zum Speichern von Dateien. Der Pfad kann absolut (nicht für Android und iOS) oder Verwandte empfohlen werden. Der endgültige Ausgabedateiname ist dieser Pfad und diesen Namen, gefolgt von Datum, Dateinummer und Appender -Erweiterung.
Nur bedeutungsvoll auf Android:
true
: Dateien werden im internen Speicherverzeichnis gespeichert (android.content.context.getFilesDir ()). Wenn nicht verfügbar, werden sie im externen Speicherverzeichnis (android.content.context.getExternalFilesDir ()) gespeichert. Wenn dies auch nicht verfügbar ist, werden sie im Cache -Verzeichnis (android.content.context.getCachedir ()) gespeichert.
false
: Dateien werden standardmäßig im externen Speicherverzeichnis gespeichert. Wenn nicht verfügbar, werden sie im internen Speicherverzeichnis gespeichert. Wenn dies auch nicht verfügbar ist, werden sie im Cache -Verzeichnis gespeichert.
Die maximale Dateigröße in Bytes. Wenn die gespeicherte Datei diese Größe überschreitet, wird eine neue Protokolldatei erstellt, wobei die Dateinummern nacheinander inkrementieren. 0
deaktiviert diese Funktion.
Die maximale Anzahl von Tagen, um Dateien aufzubewahren. Dateien älter als diese werden automatisch gelöscht. 0
deaktiviert diese Funktion.
Die maximale Gesamtgröße der Dateien, die von diesem Appender im Ausgabeverzeichnis ausgegeben werden. Wenn diese Grenze überschritten wird, werden die Dateien ab dem ältesten gelöscht, bis die Gesamtgröße innerhalb der Grenze liegt. 0
deaktiviert diese Funktion.
Wenn das Protokollobjekt ein Protokollobjekt ist, das Kategorien unterstützt, kann dies verwendet werden, um eine baumartige Liste von Kategorien zu filtern. Wenn das Array nicht leer ist, ist diese Funktion aktiv. [*default,ModuleA,ModuleB.SystemC]