中文文档
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 Echtzeit-Protokollformat mit hoher Komprimierung
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#, wodurch die ständige Erstellung neuer Objekte während der Laufzeit vermieden wird
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#endif#include #include 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 between) 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, „Stringtext“, 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. Dabei kann es sich um die Konsole (ADB Logcat-Protokolle für Android), Textdateien oder sogar spezielle Formate wie komprimierte Protokolldateien oder reguläre Dateien im Binärprotokollformat handeln.
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 wenn ein von Unity entwickeltes Spiel auf dem Android-System ausgeführt wird, können Sie die Sprachen Java, Kotlin, C# und C++ in derselben App einbeziehen. 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 ////// Erstellen Sie ein Protokollobjekt /// /// 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. /// Protokollkonfigurationszeichenfolge ///Ein Protokollobjekt. Wenn die Erstellung fehlschlägt, gibt die Methode is_valid() des Objekts false zurück. 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 es jedoch sein, dass ein ungültiges Protokollobjekt erstellt wird. 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 ////// Ein Protokollobjekt anhand seines Namens abrufen /// /// Name des Protokollobjekts, das Sie suchen möchten ///Ein Protokollobjekt. Wenn das Protokollobjekt mit einem bestimmten Namen nicht gefunden wurde, gibt die Methode is_valid() davon false zurück 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 templatebq::enable_if_t ::value, bool> verbose(const STR& log_content) const; template bq::enable_if_t ::value, bool> verbose(const STR& log_format_content, const Args&... args) const; template bq::enable_if_t ::value, bool> debug(const STR& log_content) const; template bq::enable_if_t ::value, bool> debug(const STR& log_format_content, const Args&... args) const; template bq::enable_if_t ::value, bool> info(const STR& log_content) const; template bq::enable_if_t ::value, bool> info(const STR& log_format_content, const Args&... args) const; template bq::enable_if_t ::value, bool> warning(const STR& log_content) const; template bq::enable_if_t ::value, bool> warning(const STR& log_format_content, const Args&... args) const; template bq::enable_if_t ::value, bool> error(const STR& log_content) const; template bq::enable_if_t ::value, bool> error(const STR& log_format_content, const Args&... args) const; template bq::enable_if_t ::value, bool> fatal(const STR& log_content) const; template bq::enable_if_t ::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:
////// Deinitialisieren Sie BqLog. Bitte rufen Sie diese Funktion auf, bevor Ihr Programm existiert. /// 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.
////// 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. /// static void enable_auto_crash_handle();
Eine ausführliche Einführung finden Sie unter Datenschutz bei Programmabbruch
////// Den Puffer aller Protokollobjekte synchron leeren /// um sicherzustellen, dass alle Daten im Puffer nach dem Aufruf verarbeitet werden. /// static void force_flush_all_logs(); ////// Leeren Sie den Puffer dieses Protokollobjekts synchron ///, um sicherzustellen, dass alle Daten im Puffer nach dem Aufruf verarbeitet werden. /// 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.
////// 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. /// /// static void register_console_callback(bq::type_func_ptr_console_callback callback); /// /// Registrierung eines Konsolenrückrufs aufheben. /// /// 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.
////// 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. /// /// ///static void set_console_buffer_enable(bool enable); /// /// Einen Protokolleintrag threadsicher 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. /// /// Eine Rückruffunktion, die für den abgerufenen Protokolleintrag aufgerufen werden soll, wenn der Konsolen-Appender-Puffer nicht leer ist ///True, wenn der Der Konsolen-Appender-Puffer ist nicht leer und es wird ein Protokolleintrag abgerufen. andernfalls wird False zurückgegeben. 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.
////// Ändern Sie die Protokollkonfiguration, aber einige Felder, wie zum Beispiel buffer_size, können nicht geändert werden. /// /// ///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
////// Einen bestimmten Appender vorübergehend deaktivieren oder aktivieren. /// /// /// 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.
////// Funktioniert nur, wenn Snapshot konfiguriert ist. /// Es wird den Snapshot-Puffer in Text dekodieren. /// /// ob der Zeitstempel jedes Protokolls GMT-Zeit oder Ortszeit ist ///der dekodierte Snapshot-Puffer 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: ////// Erstellen Sie ein log_decoder-Objekt, wobei jedes log_decoder-Objekt einer binären Protokolldatei entspricht. /// /// der Pfad einer binären Protokolldatei, kann ein relativer oder absoluter Pfad sein log_decoder(const bq::string& log_file_path); ~log_decoder(); ////// Einen Protokolleintrag dekodieren. Jeder Aufruf dieser Funktion dekodiert nur 1 Protokolleintrag /// ///Dekodierergebnis, appender_decode_result::eof bedeutet, dass die gesamte Protokolldatei dekodiert wurde bq::appender_decode_result decode(); ////// das letzte Dekodierungsergebnis abrufen /// ///bq::appender_decode_result get_last_decode_result() const; /// /// den Inhalt des letzten Decodierungsprotokolleintrags abrufen /// ///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 ist, 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 Endausgabe 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 richtet ein Protokollobjekt mit insgesamt 5 Appendern ein, darunter zwei TextFileAppender, die in zwei verschiedene Dateien ausgeben.# Der erste Appender heißt appender_0 und sein Typ ist ConsoleAppenderappenders_config.appender_0.type=console# Die Zeitzone für appender_0 ist Die lokale Zeit des Systemsappenders_config.appender_0.time_zone=lokale Standardzeit# appender_0 gibt alle 6 Protokollebenen aus (Hinweis: Zwischen den Protokollebenen dürfen keine Leerzeichen stehen, sonst schlägt die Analyse fehl)appenders_config.appender_0.levels=[verbose,debug ,info,warning,error,fatal]# Der zweite Appender heißt appender_1 und sein Typ ist TextFileAppenderappenders_config.appender_1.type=text_file# Die Zeitzone für appender_1 ist GMT, also UTC+0appenders_config.appender_1.time_zone=gmt# nur appender_1 gibt Protokolle der Level-Info und höher aus, andere werden ignoriertappenders_config.appender_1.levels=[info,warning,error,fatal]# Der Pfad für appender_1 befindet sich im relativen bqLog-Verzeichnis des Programms, wobei die Dateinamen mit normal beginnen, gefolgt von das Datum und die .log-Erweiterung# Unter iOS wird es in /var/mobile/Containers/Data/Application/[APP]/Library/Caches/bqLog# gespeichert Unter Android wird es in [android.content.Context gespeichert .getExternalFilesDir()]/bqLogappenders_config.appender_1.file_name=bqLog/normal# Die maximale Dateigröße beträgt 10.000.000 Byte; Bei Überschreitung wird eine neue Datei erstelltappenders_config.appender_1.max_file_size=10000000# Dateien, die älter als zehn Tage sind, werden bereinigtappenders_config.appender_1.expire_time_days=10# Wenn die Gesamtgröße der Ausgabe 100.000.000 Bytes überschreitet, werden Dateien ab dem bereinigt ältesteappenders_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 gibt alle Ebenen von logs ausappenders_config.appender_2.levels=[all]# Der Pfad für appender_2 befindet sich im relatives bqLog-Verzeichnis des Programms, mit Dateinamen, die mit new_normal beginnen, gefolgt vom Datum und der Erweiterung .logappenders_config.appender_2.file_name=bqLog/new_normal# Diese Option ist nur auf Android wirksam und speichert Protokolle im internen Speicherverzeichnis, das [android ist .content.Context.getFilesDir()]/bqLogappenders_config.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=[all aus ]# Der Pfad für appender_3 befindet sich im absoluten Pfad ~/bqLog-Verzeichnis des Programms, wobei die Dateinamen mit compress_log beginnen, gefolgt vom Datum und .logcompr extensionappenders_config.appender_3.file_name=~/bqLog/compress_log# Der fünfte Appender wird benannt appender_4 und sein Typ sind RawFileAppenderappenders_config.appender_4.type=raw_file# appender_4 ist standardmäßig deaktiviert und kann später mit set_appenders_enableappenders_config.appender_4.enable=false# aktiviert werden. appender_4 gibt alle Ebenen von logs ausappenders_config.appender_4.levels=[all]# Der Pfad für appender_4 befindet sich im relativen bqLog-Verzeichnis des Programms, wobei die Dateinamen mit raw_log beginnen, gefolgt vom Datum und der Erweiterung .lograwappenders_config.appender_4.file_name=bqLog/raw_log#. Protokolle werden nur verarbeitet, wenn ihre Kategorie mit ModuleA, ModuleB beginnt. SystemC, andernfalls werden alle ignoriert (das Konzept der Kategorie wird später in den Themen zur erweiterten Verwendung ausführlich erläutert)appenders_config.appender_4.categories_mask=[ModuleA,ModuleB.SystemC]# Die gesamte asynchrone Puffergröße beträgt 65535 Bytes; Die spezifische Bedeutung wird später erklärtlog.buffer_size=65535# Die Zuverlässigkeitsstufe des Protokolls ist normal; Die spezifische Bedeutung wird später erklärtlog.reliable_level=normal# Protokolle werden nur verarbeitet, wenn ihre Kategorie mit den folgenden drei Platzhaltern übereinstimmt, andernfalls werden alle ignoriert (das Konzept der Kategorie wird später in den Themen zur erweiterten Verwendung ausführlich erläutert)log.categories_mask= [*default,ModuleA,ModuleB.SystemC]# Dies ist ein asynchrones Protokoll; Asynchrone Protokolle sind die leistungsstärksten und empfohlenen Protokolltypen. log.thread_mode=async# Wenn die Protokollebene Fehler oder schwerwiegend ist, fügen Sie jedem Protokolleintrag Aufrufstapelinformationen hinzu.log.print_stack_levels=[error,fatal]# Aktivieren Sie die Snapshot-Funktionalität. Die Snapshot-Cache-Größe beträgt 64Ksnapshot .buffer_size=65536# Nur Protokolle mit Info- und Fehlerstufen werden im Snapshot aufgezeichnetsnapshot.levels=[info,error]# Nur Protokolle, deren Kategorie mit ModuleA, ModuleB.SystemC beginnt, werden im Snapshot aufgezeichnet, andernfalls werden sie ignoriert .categories_mask=[ModuleA.SystemA.ClassA,ModuleB]
Die appenders_config
ist eine Reihe von Konfigurationen für Appender. Der erste Parameter nach appenders_config
ist der Name des Appenders, und alle Appender mit demselben Namen haben dieselbe Konfiguration.
Name | Erforderlich | Konfigurierbare Werte | Standard | Gilt für ConsoleAppender | Gilt für TextFileAppender | Gilt für CompressedFileAppender | Gilt für RawFileAppender |
---|---|---|---|---|---|---|---|
Typ | ✔ | Konsole, Textdatei, komprimierte_Datei, Rohdatei | ✔ | ✔ | ✔ | ✔ | |
aktivieren | ✘ | Ob der Appender standardmäßig aktiviert ist | WAHR | ✔ | ✔ | ✔ | ✔ |
Ebenen | ✘ | Array von Protokollebenen | [alle] | ✔ | ✔ | ✔ | ✔ |
Zeitzone | ✘ | gmt oder eine andere Zeichenfolge | Ortszeit | ✔ | ✔ | ✔ | ✔ |
Dateiname | ✔ | Relativer oder absoluter Pfad | ✘ | ✔ | ✔ | ✔ | |
is_in_sandbox | ✘ | wahr, falsch | FALSCH | ✘ | ✔ | ✔ | ✔ |
max_file_size | ✘ | Positive ganze Zahl oder 0 | 0 | ✘ | ✔ | ✔ | ✔ |
abgelaufene_zeit_tage | ✘ | Positive ganze Zahl oder 0 | 0 | ✘ | ✔ | ✔ | ✔ |
Kapazitätslimit | ✘ | Positive ganze Zahl oder 0 | 0 | ✘ | ✔ | ✔ | ✔ |
Kategorien_Maske | ✘ | Array von Zeichenfolgen, eingeschlossen in [] | Leer | ✔ | ✔ | ✔ | ✔ |
Gibt den Typ des Appenders an.
console
: Stellt ConsoleAppender dar
text_file
: Stellt TextFileAppender dar
compressed_file
: Stellt CompressedFileAppender dar
raw_file
: Stellt RawFileAppender dar
Der Standardwert ist true
. Wenn auf false
gesetzt, wird der Appender standardmäßig deaktiviert und kann später mit set_appenders_enable
aktiviert werden.
Ein in []
eingeschlossenes Array, das eine beliebige Kombination aus verbose
, debug
, info
, warning
, error
, fatal
oder [all]
enthält, um alle Ebenen zu akzeptieren. Hinweis: Fügen Sie zwischen den Ebenen keine Leerzeichen ein, da die Analyse sonst fehlschlägt.
Gibt die Zeitzone der Protokolle an. gmt
stellt die Greenwich Mean Time (UTC+0) dar, und jede andere Zeichenfolge oder wenn sie leer bleibt, verwendet die lokale Zeitzone. Die Zeitzone beeinflusst zwei Dinge:
Der Zeitstempel formatierter Textprotokolle (gilt für ConsoleAppender und TextFileAppender)
Eine neue Protokolldatei wird erstellt, wenn Mitternacht in der angegebenen Zeitzone überschritten wird (gilt für TextFileAppender, CompressedFileAppender und RawFileAppender).
Der Pfad und das Dateinamenpräfix zum Speichern von Dateien. Der Pfad kann absolut (nicht empfohlen für Android und iOS) oder relativ sein. Der endgültige Name der Ausgabedatei ist dieser Pfad und Name, gefolgt vom Datum, der Dateinummer und der Erweiterung des Appenders.
Nur auf Android sinnvoll:
true
: Dateien werden im internen Speicherverzeichnis gespeichert (android.content.Context.getFilesDir()). Wenn sie nicht verfügbar sind, werden sie im externen Speicherverzeichnis (android.content.Context.getExternalFilesDir()) gespeichert. Wenn auch dieser nicht verfügbar ist, werden sie im Cache-Verzeichnis gespeichert (android.content.Context.getCacheDir()).
false
: Dateien werden standardmäßig im externen Speicherverzeichnis gespeichert. Wenn sie nicht verfügbar sind, werden sie im internen Speicherverzeichnis gespeichert. Ist auch dieser nicht verfügbar, werden sie im Cache-Verzeichnis abgelegt.
Die maximale Dateigröße in Bytes. Wenn die gespeicherte Datei diese Größe überschreitet, wird eine neue Protokolldatei erstellt, wobei die Dateinummern fortlaufend erhöht werden. 0
deaktiviert diese Funktion.
Die maximale Anzahl an Tagen, die Dateien aufbewahrt werden sollen. Ältere Dateien werden automatisch gelöscht. 0
deaktiviert diese Funktion.
Die maximale Gesamtgröße der von diesem Appender im Ausgabeverzeichnis ausgegebenen Dateien. Wenn diese Grenze überschritten wird, werden die Dateien beginnend mit der ältesten gelöscht, bis die Gesamtgröße innerhalb der Grenze liegt. 0
deaktiviert diese Funktion.
Wenn es sich bei dem Protokollobjekt um ein Protokollobjekt handelt, das Kategorien unterstützt, kann dies zum Filtern einer baumartigen Liste von Kategorien verwendet werden. Wenn das Array nicht leer ist, ist diese Funktion aktiv. Beispielsweise bedeutet [*default,ModuleA,ModuleB.SystemC]
dass mit der Standardkategorie protokolliert wird