中文文档
BqLog es un sistema de registro liviano y de alto rendimiento utilizado en proyectos como "Honor of Kings", que se ha implementado con éxito y funciona sin problemas.
ventanas de 64 bits
Mac OS
linux
iOS
Android (X86_64, arm64-v8a, armeabi-v7a)
Unix (Pase la prueba en FreeBSD)
C++
Java
Kotlin
DO#
En comparación con las bibliotecas de registro de código abierto existentes, BqLog ofrece importantes ventajas de rendimiento (consulte Benchmark). No sólo es adecuado para servidores y clientes sino también altamente compatible con dispositivos móviles.
Con un bajo consumo de memoria, en el caso de referencia de 10 subprocesos y 20.000.000 de entradas de registro, BqLog consume menos de 1 MB de memoria.
Proporciona un formato de registro en tiempo real de alto rendimiento y alta compresión.
Se puede usar normalmente en motores de juegos ( Unity
, Unreal
), con soporte para tipos comunes proporcionados por Unreal.
Admite cadenas y caracteres UTF-8
, UTF-16
, UTF-32
, así como tipos de parámetros comunes como bool, flotante, doble y varias longitudes y tipos de enteros
Admite format specifications
C++20
El registro asincrónico admite la revisión de fallas para evitar la pérdida de datos (inspirado en XLog)
Tamaño extremadamente pequeño, con una biblioteca dinámica de solo 200k después de la compilación de Android.
No genera asignaciones de montón adicionales en Java y C#, evitando la creación constante de nuevos objetos durante el tiempo de ejecución.
Solo depende de la biblioteca de lenguaje C estándar y de las API de la plataforma, y se puede compilar en el modo ANDROID_STL = none
de Android.
Admite estándares de compilación C++11
y posteriores y se puede compilar bajo estrictos requisitos de -Wall -Wextra -pedantic -Werror
El módulo de compilación está basado en CMake
y proporciona scripts de compilación para diferentes plataformas, lo que facilita su uso.
Admite tipos de parámetros personalizados
Muy amigable con las sugerencias de código.
¿Por qué BqLog es tan rápido? Formato de registro comprimido en tiempo real de alto rendimiento
¿Por qué BqLog es tan rápido? Búfer de anillo de alta concurrencia
Integrando BqLog en tu proyecto
Demostración sencilla
Descripción general de la arquitectura
Instrucciones de uso de la API del proceso principal
1-Crear un objeto de registro
2-Recuperar un objeto de registro
3-Mensajes de registro
4-Otras API
Registro sincrónico y asincrónico
1. Seguridad de subprocesos del registro asincrónico
Introducción a los anexos
1. ConsolaAppender
2. Aplicador de archivos de texto
3. CompressedFileAppender (muy recomendado)
4. Aplicador de archivos sin formato
Instrucciones de configuración
1. Ejemplo completo
2. Explicación detallada
Decodificación sin conexión de anexos de formato binario
Instrucciones de construcción
1. Construcción de biblioteca
2. Demostración de compilación y ejecución
3. Instrucciones de ejecución de prueba automatizada
4. Instrucciones de ejecución de referencia
Temas de uso avanzado
1. Sin asignación de montón
2. Registrar objetos con soporte de categorías
3. Protección de datos en caso de salida anormal del programa
4. Acerca de NDK y ANDROID_STL = ninguno
5. Tipos de parámetros personalizados
6. Usando BqLog en Unreal Engine
Punto de referencia
1. Descripción del punto de referencia
2. Código de referencia de BqLog C++
3. Código de referencia de BqLog Java
4. Código de referencia de Log4j
5. Resultados de referencia
BqLog se puede integrar en su proyecto de varias formas. Para C++, admite bibliotecas dinámicas, bibliotecas estáticas y archivos fuente. Para Java y C#, admite bibliotecas dinámicas con código fuente contenedor. A continuación se detallan los métodos para incluir BqLog:
El repositorio de código incluye archivos de biblioteca dinámica precompilados ubicados en /dist/dynamic_lib/. Para integrar BqLog en su proyecto utilizando los archivos de la biblioteca, debe hacer lo siguiente:
Seleccione el archivo de biblioteca dinámica correspondiente a su plataforma y agréguelo al sistema de compilación de su proyecto.
Copie el directorio /dist/dynamic_lib/include en su proyecto y agréguelo a la lista de directorios de inclusión. (Si está utilizando la biblioteca .framework de XCode, puede omitir este paso ya que el archivo .framework ya incluye los archivos de encabezado).
El repositorio de código incluye archivos de biblioteca estática precompilados ubicados en /dist/static_lib/. Para integrar BqLog en su proyecto utilizando los archivos de la biblioteca, debe hacer lo siguiente:
Seleccione el archivo de biblioteca estática correspondiente a su plataforma y agréguelo al sistema de compilación de su proyecto.
Copie el directorio /dist/static_lib/include en su proyecto y agréguelo a la lista de directorios de inclusión. (Si está utilizando la biblioteca .framework de XCode, puede omitir este paso ya que el archivo .framework ya incluye los archivos de encabezado).
BqLog también admite la inclusión directa de código fuente en su proyecto para su compilación. Para integrar BqLog usando el código fuente, sigue estos pasos:
Copie el directorio /src en su proyecto como referencia del código fuente.
Copie el directorio /include en su proyecto y agréguelo a la lista de directorios de inclusión.
Si compila la versión de Windows en Visual Studio, agregue /Zc:__cplusplus a las opciones de compilación para garantizar que la compatibilidad estándar del compilador C++ actual esté determinada correctamente.
Si utiliza el código fuente en el NDK de Android, consulte 4. Acerca del NDK y ANDROID_STL = ninguno para conocer consideraciones importantes.
En C#, BqLog se puede utilizar a través de una biblioteca dinámica nativa y un C# Wrapper, compatible con motores Mono, Microsoft CLR y Unity. Unity es compatible con los modos Mono e IL2CPP. Para usar BqLog en C#, siga estos pasos:
Seleccione el archivo de biblioteca dinámica correspondiente a su plataforma en /dist/dynamic_lib/ y agréguelo a su proyecto (para Unity, consulte Importación de Unity y configuración de complementos).
Copie los archivos de código fuente de /wrapper/csharp/src en su proyecto.
En Java, BqLog se puede utilizar a través de una biblioteca dinámica nativa y un Java Wrapper, compatible con entornos JVM comunes y Android. Para integrar BqLog en una JVM, siga estos pasos:
Seleccione el archivo de biblioteca dinámica correspondiente a su plataforma en /dist/dynamic_lib/ y agréguelo a su proyecto.
Copie los archivos de código fuente de /wrapper/java/src en su proyecto.
(Opcional) Copie el directorio /dist/dynamic_lib/include en su proyecto y agréguelo a la lista de directorios de inclusión si desea llamar a BqLog desde el NDK.
El siguiente código generará más de 1000 registros en su consola (o ADB Logcat si está en Android)
#si está definido (WIN32) #include#endif#include #include int main() { #if definido (WIN32) // Cambia la línea de comando de Windows a UTF-8 porque BqLog genera todo el texto final en codificación UTF-8 para evitar problemas de visualización SetConsoleOutputCP(CP_UTF8); EstablecerConsoleCP(CP_UTF8); #endif // Esta cadena es la configuración del registro. Aquí configura un registrador con un agregador (destino de salida) llamado appender_0, que genera salida a la consola. std::string config = R"( # El destino de salida de este appender es la consola appenders_config.appender_0.type=console # Este appender usa la hora local para las marcas de tiempo appenders_config.appender_0.time_zone=default local time # Este appender genera registros de estos 6 niveles (sin espacios entre ellos) appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal] )"; bq::log log = bq::log::create_log("mi_primer_log", config); // Crea un objeto de registro usando la configuración for(int i = 0; i < 1024; ++i) { log.info("Este es un registro de prueba de información, la cadena de formato es 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"Este es un registro de prueba de errores, la cadena de formato es UTF-32"); bq::log::force_flush_all_logs(); // BqLog tiene por defecto salida asincrónica. Para garantizar que los registros sean visibles antes de salir del programa, fuerce el vaciado para sincronizar la salida una vez. devolver 0; }
usando System.Text; usando System; clase pública demo_main {public static void Main(string[] args) { Console.OutputEncoding = Codificación.UTF8; Console.InputEncoding = Codificación.UTF8; string config = @" # El destino de salida de este appender es la consola appenders_config.appender_0.type=console # Este appender usa la hora local para las marcas de tiempo ppenders_config.appender_0.time_zone=default local time # Este appender genera registros de estos 6 niveles (sin espacios en entre) appenders_config.appender_0.levels=[detallado,depuración,info,advertencia,error,fatal] "; bq.log log = bq.log.create_log("mi_primer_log", config); // Crea un objeto de registro usando la configuración para (int i = 0; i < 1024; ++i) { log.info("Este es un registro de prueba de información, la cadena de formato es UTF-16, param int:{}, param bool :{}, param string:{}, param float:{}", i, true, " Texto de cadena", 4.3464f); } bq.log.force_flush_all_logs(); Consola.ReadKey(); }}
public class demo_main { public static void main(String[] args) { // TODO Stub de método generado automáticamente String config = """ # El destino de salida de este appender es la consola appenders_config.appender_0.type=console # Este appender usa la hora local para marcas de tiempo appenders_config.appender_0.time_zone=hora local predeterminada # Este agregador genera registros de estos 6 niveles (sin espacios entre ellos) appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal] """; bq.log log = bq.log.create_log("mi_primer_log", config); // Crea un objeto de registro usando la configuración para (int i = 0; i < 1024; ++i) { log.info("Este es un registro de prueba de información, la cadena de formato es UTF-16, param int:{}, param bool :{}, param string:{}, param float:{}", i, true, "Texto de cadena", 4.3464f); } bq.log.force_flush_all_logs(); } }
El diagrama anterior ilustra claramente la estructura básica de BqLog. En el lado derecho del diagrama está la implementación interna de la biblioteca BqLog, mientras que en el lado izquierdo está su programa y código. Su programa puede llamar a BqLog utilizando los contenedores proporcionados (API orientadas a objetos para diferentes idiomas). En el diagrama, se crean dos registros: uno llamado "Registro A" y el otro llamado "Registro B". Cada registro se adjunta a uno o más anexos. Un Appender puede entenderse como el destino de salida del contenido del registro. Puede ser la consola (registros ADB Logcat para Android), archivos de texto o incluso formatos especializados como archivos de registro comprimidos o archivos de formato de registro binario normal.
Dentro del mismo proceso, los contenedores de diferentes idiomas pueden acceder al mismo objeto de registro. Por ejemplo, si se crea un objeto Log llamado Log A en Java, también se puede acceder a él y utilizarlo desde el lado C++ con el nombre Log A.
En casos extremos, como un juego desarrollado por Unity que se ejecuta en el sistema Android, es posible involucrar los lenguajes Java, Kotlin, C# y C++ dentro de la misma aplicación. Todos pueden compartir el mismo objeto Log. Puede crear el registro en el lado de Java usando create_log y luego acceder a él en otros idiomas usando get_log_by_name.
Nota: Las siguientes API se declaran en la clase bq::log (o bq.log). Para ahorrar espacio, solo se enumeran las API de C++. Las API en Java y C# son idénticas y no se repetirán aquí.
En C++, bq::string
es el tipo de cadena UTF-8 en la biblioteca BqLog. También puede pasar cadenas de estilo C como char o std::string
o std::string_view
, que se convertirán automática e implícitamente.
Se puede crear un objeto de registro utilizando la función estática create_log. Su declaración es la siguiente:
//API de C++ ////// Crear un objeto de registro /// /// Si el nombre del registro es una cadena vacía, bqLog le asignará automáticamente un nombre de registro único. Si el nombre del registro ya existe, devolverá el objeto de registro previamente existente y sobrescribirá la configuración anterior con la nueva configuración. /// Cadena de configuración de registro ///Un objeto de registro, si la creación falla, su método is_valid() devolverá false static log create_log(const bq::string& log_name, const bq::string& config_content);
El código crea un objeto de registro pasando el nombre del objeto de registro y una cadena de configuración. Se puede hacer referencia a la configuración del registro en las Instrucciones de configuración. Aquí hay algunos puntos clave a tener en cuenta:
Independientemente de si es C# o Java, el objeto de registro devuelto nunca será nulo. Sin embargo, debido a errores de configuración u otros motivos, es posible que se cree un objeto de registro no válido. Por lo tanto, debes usar la función is_valid() para verificar el objeto devuelto. Realizar operaciones en un objeto no válido puede provocar que el programa falle.
Si se pasa una cadena vacía como nombre de registro, bqLog generará automáticamente un nombre de registro único, como "AutoBqLog_1".
Llamar a create_log en un objeto de registro ya existente con el mismo nombre no creará un nuevo objeto de registro, pero sobrescribirá la configuración anterior con la nueva. Sin embargo, algunos parámetros no se pueden modificar en este proceso; consulte las Instrucciones de configuración para obtener más detalles.
Excepto cuando se usa en el NDK (consulte 4. Acerca de NDK y ANDROID_STL = ninguno), puede inicializar el objeto de registro directamente en variables globales o estáticas usando esta API en otras situaciones.
Si ya se ha creado un objeto de registro en otro lugar, puede obtener el objeto de registro creado directamente utilizando la función get_log_by_name.
//API de C++ ////// Obtener un objeto de registro por su nombre /// /// Nombre del objeto de registro que desea encontrar ///Un objeto de registro, si no se encontró el objeto de registro con un nombre específico, su método is_valid() devolverá false static log get_log_by_name(const bq::string& log_name);
También puede utilizar esta función para inicializar un objeto de registro en variables globales o funciones estáticas. Sin embargo, tenga en cuenta que debe asegurarse de que el objeto de registro con el nombre especificado ya exista. De lo contrario, el objeto de registro devuelto quedará inutilizable y su método is_valid() devolverá falso.
/// Funciones de registro principales, hay 6 niveles de registro: /// detallado, depuración, información, advertencia, error, plantilla fatalbq::enable_if_t ::valor, bool> detallado(const STR& log_content) const; plantilla bq::enable_if_t ::valor, bool> detallado(const STR& log_format_content, const Args&... args) const; plantilla bq::enable_if_t ::valor, bool> debug(const STR& log_content) const; plantilla bq::enable_if_t ::value, bool> debug(const STR& log_format_content, const Args&... args) const; plantilla bq::enable_if_t ::valor, bool> info(const STR& log_content) const; plantilla bq::enable_if_t ::valor, bool> info(const STR& log_format_content, const Args&... args) const; plantilla bq::enable_if_t ::valor, bool> advertencia(const STR& log_content) const; plantilla bq::enable_if_t ::valor, bool> advertencia(const STR& log_format_content, const Args&... args) const; plantilla bq::enable_if_t ::valor, bool> error(const STR& log_content) const; plantilla bq::enable_if_t ::valor, bool> error(const STR& log_format_content, const Args&... args) const; plantilla bq::enable_if_t ::valor, bool> fatal(const STR& log_content) const; plantilla bq::enable_if_t ::value, bool> fatal(const STR& log_format_content, const Args&... args) const;
Al registrar mensajes, preste atención a tres puntos clave:
Como puede ver, nuestros registros se dividen en seis niveles: detallado, depuración, información, advertencia, error y fatal, de acuerdo con Android. Su importancia aumenta secuencialmente. Cuando se envíen a la consola, aparecerán en diferentes colores.
El parámetro STR es similar al primer parámetro de printf y puede ser de varios tipos de cadenas comunes, que incluyen:
java.lang.String de Java
cadena de C#
Varias codificaciones de cadenas estilo C de C++ y 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
e incluso tipos de cadenas personalizados, a los que puede consultar en Tipos de parámetros personalizados )
Puede agregar varios parámetros después del parámetro STR. Estos parámetros se formatearán en los lugares especificados en STR, siguiendo reglas similares al formato std:: de C++20 (excepto por la falta de soporte para argumentos posicionales y formato de fecha y hora). Por ejemplo, el uso de un solo {} representa un formato predeterminado de un parámetro y {:.2f} especifica la precisión para formatear un número de punto flotante. Intente utilizar parámetros formateados para generar registros en lugar de concatenar cadenas manualmente. Este enfoque es óptimo para el rendimiento y el almacenamiento en formato comprimido.
Los tipos de parámetros admitidos actualmente incluyen:
Punteros nulos (salida como nula)
Punteros (salida como dirección hexadecimal que comienza con 0x)
booleano
Caracteres de un solo byte (char)
Caracteres de doble byte (char16_t, wchar_t, char de C#, char de Java)
Caracteres de cuatro bytes (char32_t o wchar_t)
enteros de 8 bits
Enteros sin signo de 8 bits
enteros de 16 bits
Enteros sin signo de 16 bits
enteros de 32 bits
Enteros sin signo de 32 bits
enteros de 64 bits
Enteros sin signo de 64 bits
números de punto flotante de 32 bits
números de punto flotante de 64 bits
Otros tipos de POD desconocidos en C++ (limitados a tamaños de 1, 2, 4 u 8 bytes, tratados como int8, int16, int32 e int64 respectivamente)
Cadenas, incluidos todos los tipos de cadenas mencionados en el parámetro STR
Cualquier clase u objeto en C# y Java (generando su cadena ToString())
Tipos de parámetros personalizados, como se detalla en Tipos de parámetros personalizados
Existen API adicionales de uso común que pueden realizar tareas específicas. Para obtener descripciones detalladas de la API, consulte bq_log/bq_log.h, así como la clase bq.log en Java y C#. A continuación se muestran algunas API clave que deben destacarse:
////// Desinicializa BqLog, invoca esta función antes de que exista tu programa. /// static void uninit();
Se recomienda ejecutar uninit()
antes de salir del programa o desinstalar la biblioteca dinámica autoimplementada que usa BqLog; de lo contrario, el programa puede bloquearse al salir bajo ciertas circunstancias específicas.
////// Si bqLog es asincrónico, una falla en el programa puede causar que los registros en el búfer no se conserven en el disco. /// Si esta función está habilitada, bqLog intentará realizar un vaciado forzado de los registros en el búfer en caso de falla. Sin embargo, /// esta funcionalidad no garantiza el éxito y solo admite sistemas POSIX. /// static void enable_auto_crash_handle();
Para obtener una introducción detallada, consulte Protección de datos en salidas anormales del programa.
////// Vacíe sincrónicamente el búfer de todos los objetos de registro /// para garantizar que todos los datos en el búfer se procesen después de la llamada. /// static void force_flush_all_logs(); ////// Vacíe sincrónicamente el búfer de este objeto de registro /// para garantizar que todos los datos en el búfer se procesen después de la llamada. /// void force_flush();
Dado que bqLog utiliza el registro asincrónico de forma predeterminada, hay ocasiones en las que es posible que desee sincronizar y generar todos los registros inmediatamente. En tales casos, es necesario llamar con fuerza a force_flush().
////// Registre una devolución de llamada que se invocará cada vez que se genere un mensaje de registro de la consola. /// Esto se puede utilizar para que un sistema externo monitoree la salida del registro de la consola. /// /// static void Register_console_callback(bq::type_func_ptr_console_callback callback); ////// Anular el registro de una devolución de llamada de consola. /// /// static void unregister_console_callback(bq::type_func_ptr_console_callback callback);
La salida de ConsoleAppender va a la consola o a los registros de ADB Logcat en Android, pero es posible que esto no cubra todas las situaciones. Por ejemplo, en motores de juegos personalizados o IDE personalizados, se proporciona un mecanismo para llamar a una función de devolución de llamada para cada salida de registro de la consola. Esto le permite reprocesar y generar el registro de la consola en cualquier parte de su programa.
Precaución adicional: No genere ningún registro BQ sincronizado dentro de la devolución de llamada de la consola, ya que puede provocar fácilmente interbloqueos.
////// Habilita o deshabilita el búfer del agregador de la consola. /// Dado que nuestro contenedor puede ejecutarse en máquinas virtuales C# y Java, y no queremos invocar directamente devoluciones de llamada desde un subproceso nativo, /// podemos habilitar esta opción. De esta manera, todas las salidas de la consola se guardarán en el búfer hasta que las recuperemos. /// /// ///static void set_console_buffer_enable(bool enable); /// /// Recupera y elimina una entrada de registro del búfer del agregador de la consola de forma segura para subprocesos. /// Si el búfer del agregador de la consola no está vacío, se invocará la función on_console_callback para esta entrada de registro. /// Asegúrese de no generar registros BQ sincronizados dentro de la función de devolución de llamada. /// /// Una función de devolución de llamada que se invocará para la entrada de registro recuperada si el búfer del agregador de la consola no está vacío ///Verdadero si el el búfer del agregador de la consola no está vacío y se recupera una entrada de registro; de lo contrario, se devuelve False. static bool fetch_and_remove_console_buffer(bq::type_func_ptr_console_callback on_console_callback);
Además de interceptar la salida de la consola a través de una devolución de llamada de la consola, puede recuperar activamente las salidas del registro de la consola. A veces, es posible que no queramos que la salida del registro de la consola llegue a través de una devolución de llamada porque no se sabe de qué subproceso provendrá la devolución de llamada (por ejemplo, en algunas máquinas virtuales C# o JVM, la VM podría estar realizando una recolección de basura cuando la consola se llama a una devolución de llamada, lo que podría provocar bloqueos o bloqueos).
El método utilizado aquí implica habilitar el búfer de la consola a través de set_console_buffer_enable
. Esto hace que cada salida del registro de la consola se almacene en la memoria hasta que llamemos activamente a fetch_and_remove_console_buffer
para recuperarla. Por lo tanto, si elige utilizar este método, recuerde buscar y borrar registros de inmediato para evitar memoria no liberada.
Precaución adicional: No genere ningún registro BQ sincronizado dentro de la devolución de llamada de la consola, ya que puede provocar fácilmente interbloqueos.
Precaución adicional: si está utilizando este código en un entorno IL2CPP, asegúrese de que on_console_callback esté marcado como estático no seguro y esté decorado con el atributo [MonoPInvokeCallback(typeof(type_console_callback))].
////// Modifica la configuración del registro, pero algunos campos, como buffer_size, no se pueden modificar. /// /// ///bool reset_config(const bq::string& config_content);
A veces es posible que desee modificar la configuración de un registro dentro de su programa. Además de recrear el objeto de registro para sobrescribir la configuración (consulte Creación de un objeto de registro), también puede utilizar la interfaz de reinicio. Sin embargo, tenga en cuenta que no todos los elementos de configuración se pueden modificar de esta manera. Para obtener más información, consulte las Instrucciones de configuración.
////// Deshabilita o habilita temporalmente un Appender específico. /// /// /// void set_appenders_enable(const bq::string& appender_name, bool enable) ;
De forma predeterminada, los Appenders en la configuración están activos, pero aquí se proporciona un mecanismo para deshabilitarlos y volver a habilitarlos temporalmente.
////// Funciona solo cuando se configura la instantánea. /// Decodificará el búfer de instantáneas en texto. /// /// si la marca de tiempo de cada registro es la hora GMT o la hora local ///el búfer de instantáneas decodificada bq::string take_snapshot(bool use_gmt_time) const;
A veces, ciertas características especiales requieren generar la última parte de los registros, lo que se puede hacer usando la función de instantánea. Para habilitar esta función, primero debe activar la instantánea en la configuración del registro y establecer el tamaño máximo del búfer, en bytes. Además, debe especificar los niveles de registro y las categorías que se filtrarán para la instantánea (opcional). Para una configuración detallada, consulte Configuración de instantáneas. Cuando se necesita una instantánea, llamar a take_snapshot() devolverá la cadena formateada que contiene las entradas de registro más recientes almacenadas en el búfer de instantáneas. En C++, el tipo es bq::string
, que se puede convertir implícitamente a std::string
.
namespace bq{ namespace tools { //Esta es una clase de utilidad para decodificar formatos de registro binario. //Para usarlo, primero cree un objeto log_decoder, //luego llame a su función decodificación para decodificar. //Después de cada llamada exitosa, //puedes usar get_last_decoded_log_entry() para recuperar el resultado decodificado. //Cada llamada decodifica una entrada de registro. estructura log_decoder { privado: bq::cadena decode_text_; bq::appender_decode_result resultado_ = bq::appender_decode_result::éxito; uint32_t manejar_ = 0; public: ////// Crea un objeto log_decoder, y cada objeto log_decoder corresponde a un archivo de registro binario. /// /// la ruta de un archivo de registro binario, puede ser una ruta relativa o una ruta absoluta log_decoder(const bq::string& log_file_path); ~log_decodificador(); ////// Decodifica una entrada de registro. cada llamada de esta función decodificará solo 1 entrada de registro /// ///resultado de decodificación, appender_decode_result::eof significa que se decodificó todo el archivo de registro bq::appender_decode_result decodificar(); ////// obtiene el último resultado de decodificación /// ///bq::appender_decode_result get_last_decode_result() const; /// /// obtiene el contenido de la última entrada del registro de decodificación /// ///const bq::string& get_last_decoded_log_entry() const; }; } }
Esta es una clase de utilidad que puede decodificar archivos de registro generados por Appenders de tipo binario en tiempo de ejecución, como CompressedFileAppender y RawFileAppender.
Para usarlo, primero cree un objeto log_decoder. Luego, cada vez que llama a la función decode(), decodifica una entrada de registro en secuencia. Si el resultado devuelto es bq::appender_decode_result::success, puede llamar a get_last_decoded_log_entry() para obtener el contenido de texto formateado de la última entrada de registro decodificada. Si el resultado es bq::appender_decode_result::eof, significa que todos los registros se han leído por completo.
BqLog le permite configurar si un objeto de registro es sincrónico o asincrónico a través de la configuración thread_mode. Las diferencias clave entre estos dos modos son las siguientes:
Registro sincrónico | Registro asincrónico | |
---|---|---|
Comportamiento | Después de llamar a la función de registro, el registro se escribe inmediatamente en el anexo correspondiente. | Después de llamar a la función de registro, el registro no se escribe inmediatamente; en cambio, se entrega a un subproceso de trabajo para su procesamiento periódico. |
Actuación | Bajo, ya que el hilo que escribe el registro debe bloquearse y esperar a que el registro se escriba en el agregador correspondiente antes de regresar de la función de registro. | Alto, ya que el hilo que escribe el registro no necesita esperar la salida real y puede regresar inmediatamente después del registro. |
Seguridad del hilo | Alto, pero requiere que los parámetros de registro no se modifiquen durante la ejecución de la función de registro. | Alto, pero requiere que los parámetros de registro no se modifiquen durante la ejecución de la función de registro. |
Un error común sobre el registro asincrónico es que es menos seguro para los subprocesos, ya que a los usuarios les preocupa que los parámetros puedan recuperarse cuando el subproceso de trabajo procese el registro. Por ejemplo:
{ const char str_array[5] = {'T', 'E', 'S', 'T', '�'}; const char* str_ptr = str_array; log_obj.info("Este es el parámetro de prueba: {}, {}", str_array, str_ptr); }
En el ejemplo anterior, str_array
se almacena en la pila y, una vez que se sale del alcance, su memoria ya no es válida. Los usuarios pueden preocuparse de que si se utiliza el registro asincrónico, cuando el subproceso de trabajo procese el registro, str_array
y str_ptr
serán variables no válidas.
Sin embargo, tal situación no ocurrirá porque BqLog copia todos los contenidos de los parámetros en su ring_buffer
interno durante la ejecución de la función info
. Una vez que regresa la función info
, las variables externas como str_array
o str_ptr
ya no son necesarias. Además, ring_buffer
no almacenará una dirección de puntero const char*
sino que siempre almacenará la cadena completa.
El problema potencial real surge en el siguiente escenario:
static std::string global_str = "hola mundo"; // Esta es una variable global modificada por múltiples subprocesos.void thread_a() { log_obj.info("Este es el parámetro de prueba: {}", global_str); }
Si el contenido de global_str
cambia durante la ejecución de la función info
, puede provocar un comportamiento indefinido. BqLog hará todo lo posible para evitar un fallo, pero no se puede garantizar la exactitud del resultado final.
Un Appender representa el destino de salida del registro. El concepto de Appenders en bqLog es básicamente el mismo que en Log4j. Actualmente, bqLog proporciona los siguientes tipos de Appenders:
El destino de salida de este Appender es la consola, incluido el ADB de Android y la consola correspondiente en iOS. La codificación del texto es UTF-8.
Este Appender genera archivos de registro directamente en formato de texto UTF-8.
Este Appender genera archivos de registro en un formato comprimido, que es el highly recommended format by bqLog
. Tiene el rendimiento más alto entre todos los Appenders y produce el archivo de salida más pequeño. Sin embargo, es necesario decodificar el archivo final. La decodificación se puede realizar durante la decodificación en tiempo de ejecución o durante la decodificación fuera de línea.
Este Appender genera el contenido del registro binario desde la memoria directamente a un archivo. Su rendimiento es superior al de TextFileAppender, pero consume más espacio de almacenamiento. Es necesario decodificar el archivo final. La decodificación se puede realizar durante la decodificación en tiempo de ejecución o sin conexión. No se recomienda el uso de este Appender.
A continuación se muestra una comparación completa de los distintos anexos:
Nombre | Objetivo de salida | Legible directamente | Rendimiento de salida | Tamaño de salida |
---|---|---|---|---|
ConsolaAppender | Consola | ✔ | Bajo | - |
Aplicador de archivo de texto | Archivo | ✔ | Bajo | Grande |
Aplicador de archivos comprimidos | Archivo | ✘ | Alto | Pequeño |
Aplicador de archivos sin procesar | Archivo | ✘ | Medio | Grande |
La configuración se refiere a la cadena de configuración en las funciones create_log y reset_config. Esta cadena utiliza el formato del archivo de propiedades y admite # comentarios (pero recuerde comenzar una nueva línea con # para comentarios).
A continuación se muestra un ejemplo completo:
# Esta configuración configura un objeto de registro con un total de 5 Appenders, incluidos dos TextFileAppenders que generan dos archivos diferentes.# El primer Appender se llama appender_0 y su tipo es ConsoleAppenderappenders_config.appender_0.type=console# La zona horaria para appender_0 es la hora local del sistemaappenders_config.appender_0.time_zone=hora local predeterminada# appender_0 generará los 6 niveles de registros (nota: no debe haber espacios entre los niveles de registro, o no se podrá analizar)appenders_config.appender_0.levels=[verbose,debug ,info,warning,error,fatal]# El segundo Appender se llama appender_1 y su tipo es TextFileAppenderappenders_config.appender_1.type=text_file# La zona horaria para appender_1 es GMT, que es UTC+0appenders_config.appender_1.time_zone=gmt# appender_1 solamente genera registros de nivel de información y superiores, otros serán ignoradosappenders_config.appender_1.levels=[info,warning,error,fatal]# La ruta para appender_1 estará en el directorio bqLog relativo del programa, con nombres de archivos que comienzan con normal, seguido de la fecha y la extensión .log# En iOS, se guardará en /var/mobile/Containers/Data/Application/[APP]/Library/Caches/bqLog# En Android, se guardará en [android.content.Context .getExternalFilesDir()]/bqLogappenders_config.appender_1.file_name=bqLog/normal# El tamaño máximo de archivo es 10.000.000 bytes; si se excede, se creará un nuevo archivoappenders_config.appender_1.max_file_size=10000000# Los archivos con más de diez días se limpiaránappenders_config.appender_1.expire_time_days=10# Si el tamaño total de salida excede los 100.000.000 bytes, los archivos se limpiarán comenzando desde el oldappenders_config.appender_1.capacity_limit=100000000# El tercer Appender se llama appender_2 y su tipo es TextFileAppenderappenders_config.appender_2.type=text_file# appender_2 generará todos los niveles de logsappenders_config.appender_2.levels=[all]# La ruta para appender_2 estará en el Directorio bqLog relativo del programa, con nombres de archivos que comienzan con new_normal, seguido de la fecha y la extensión .log appenders_config.appender_2.file_name=bqLog/new_normal# Esta opción solo es efectiva en Android, guardando registros en el directorio de almacenamiento interno, que es [android .content.Context.getFilesDir()]/bqLogappenders_config.appender_2.is_in_sandbox=true# El cuarto Appender se llama appender_3 y su tipo es CompressedFileAppenderappenders_config.appender_3.type=compressed_file# appender_3 generará todos los niveles de logsappenders_config.appender_3.levels=[all ]# La ruta para appender_3 estará en la ruta absoluta ~/bqLog directorio del programa, con nombres de archivos que comienzan con compress_log, seguido de la fecha y la extensión .logcomprappenders_config.appender_3.file_name=~/bqLog/compress_log# El quinto Appender se llama appender_4 y su tipo es RawFileAppenderappenders_config.appender_4.type=raw_file# appender_4 está deshabilitado de forma predeterminada y se puede habilitar más tarde usando set_appenders_enableappenders_config.appender_4.enable=false# appender_4 generará todos los niveles de logsappenders_config.appender_4.levels=[all]# La ruta para appender_4 estará en el directorio bqLog relativo del programa, con nombres de archivos que comienzan con raw_log, seguido de la fecha y la extensión .lograwappenders_config.appender_4.file_name=bqLog/raw_log# Los registros solo se procesarán si su categoría comienza con ModuleA, ModuleB. SystemC; de lo contrario, se ignorará todo (el concepto de Categoría se explica en detalle en los temas de uso avanzado más adelante)appenders_config.appender_4.categories_mask=[ModuleA,ModuleB.SystemC]# El tamaño total del búfer asíncrono es 65535 bytes; el significado específico se explica más adelantelog.buffer_size=65535# El nivel de confiabilidad del registro es normal; el significado específico se explica más adelantelog.reliable_level=normal# Los registros solo se procesarán si su categoría coincide con los siguientes tres comodines; de lo contrario, se ignorarán todos (el concepto de Categoría se explica en detalle en los temas de uso avanzado más adelante)log.categories_mask= [*default,ModuleA,ModuleB.SystemC]# Este es un registro asincrónico; los registros asincrónicos son los de mayor rendimiento y los recomendados typelog.thread_mode=async# Si el nivel de registro es de error o fatal, incluya información de la pila de llamadas con cada entrada de registrolog.print_stack_levels=[error,fatal]# Habilite la funcionalidad de instantáneas, el tamaño de la caché de instantáneas es 64Ksnapshot .buffer_size=65536# Solo los registros con información y niveles de error se registrarán en la instantánea.levels=[info,error]# Solo los registros cuya categoría comience con ModuleA, ModuleB.SystemC se registrarán en la instantánea; de lo contrario, se ignorarán. .categories_mask=[MóduloA.SistemaA.ClaseA,MóduloB]
appenders_config
es un conjunto de configuraciones para Appenders. El primer parámetro que sigue appenders_config
es el nombre del Appender y todos los Appenders con el mismo nombre comparten la misma configuración.
Nombre | Requerido | Valores configurables | Por defecto | Aplicable a ConsoleAppender | Aplicable a TextFileAppender | Aplicable a CompressedFileAppender | Aplicable a RawFileAppender |
---|---|---|---|---|---|---|---|
tipo | ✔ | consola, archivo_texto, archivo_comprimido, archivo_sin formato | ✔ | ✔ | ✔ | ✔ | |
permitir | ✘ | Si el Appender está habilitado de forma predeterminada | verdadero | ✔ | ✔ | ✔ | ✔ |
niveles | ✘ | Matriz de niveles de registro | [todo] | ✔ | ✔ | ✔ | ✔ |
zona_horaria | ✘ | gmt o cualquier otra cadena | Hora local | ✔ | ✔ | ✔ | ✔ |
Nombre del archivo | ✔ | Ruta relativa o absoluta | ✘ | ✔ | ✔ | ✔ | |
está_en_sandbox | ✘ | verdadero, falso | FALSO | ✘ | ✔ | ✔ | ✔ |
tamaño_archivo_max | ✘ | Entero positivo o 0 | 0 | ✘ | ✔ | ✔ | ✔ |
expira_time_days | ✘ | Entero positivo o 0 | 0 | ✘ | ✔ | ✔ | ✔ |
límite_capacidad | ✘ | Entero positivo o 0 | 0 | ✘ | ✔ | ✔ | ✔ |
categorías_mascarilla | ✘ | Matriz de cadenas encerradas en [] | Vacío | ✔ | ✔ | ✔ | ✔ |
Especifica el tipo de Appender.
console
: Representa ConsoleAppender
text_file
: representa TextFileAppender
compressed_file
: representa CompressedFileAppender
raw_file
: Representa RawFileAppender
El valor predeterminado es true
. Si se establece en false
, el Appender se deshabilitará de forma predeterminada y se podrá habilitar más tarde usando set_appenders_enable
.
Una matriz encerrada en []
, que contiene cualquier combinación de verbose
, debug
, info
, warning
, error
, fatal
o [all]
para aceptar todos los niveles. Nota: No incluya espacios entre niveles o no se podrá analizar.
Especifica la zona horaria de los registros. gmt
representa la hora media de Greenwich (UTC+0), y cualquier otra cadena o dejarla vacía utilizará la zona horaria local. La zona horaria afecta a dos cosas:
La marca de tiempo de los registros de texto formateados (aplicable a ConsoleAppender y TextFileAppender)
Se creará un nuevo archivo de registro cuando cruce la medianoche en la zona horaria especificada (aplicable a TextFileAppender, CompressedFileAppender y RawFileAppender).
La ruta y el prefijo del nombre de archivo para guardar archivos. La ruta puede ser absoluta (no recomendada para Android e iOS) o relativa. El nombre del archivo de salida final será esta ruta y nombre, seguidos de la fecha, el número de archivo y la extensión del Appender.
Sólo significativo en Android:
true
: los archivos se almacenan en el directorio de almacenamiento interno (android.content.Context.getFilesDir()). Si no están disponibles, se almacenan en el directorio de almacenamiento externo (android.content.Context.getExternalFilesDir()). Si tampoco está disponible, se almacenan en el directorio de caché (android.content.Context.getCacheDir()).
false
: los archivos se almacenan en el directorio de almacenamiento externo de forma predeterminada. Si no están disponibles, se almacenan en el directorio de almacenamiento interno. Si tampoco está disponible, se almacenan en el directorio de caché.
El tamaño máximo de archivo en bytes. Cuando el archivo guardado excede este tamaño, se crea un nuevo archivo de registro y los números de archivo aumentan secuencialmente. 0
desactiva esta función.
El número máximo de días para conservar los archivos. Los archivos más antiguos se eliminarán automáticamente. 0
desactiva esta función.
El tamaño total máximo de los archivos generados por este Appender en el directorio de salida. Si se excede este límite, los archivos se eliminan comenzando desde el más antiguo hasta que el tamaño total esté dentro del límite. 0
desactiva esta función.
Si el objeto de registro es un objeto de registro que admite categorías, esto se puede utilizar para filtrar una lista de categorías en forma de árbol. Cuando la matriz no está vacía, esta función está activa. Por ejemplo, [*default,ModuleA,ModuleB.SystemC]
significa que los registros con la categoría predeterminada