中文文档
BqLog é um sistema de registro leve e de alto desempenho usado em projetos como “Honor of Kings”, e foi implantado com sucesso e está funcionando perfeitamente.
Windows 64 bits
Mac OS
Linux
iOS
Android(X86_64, arm64-v8a、armeabi-v7a)
Unix (Passe no teste no FreeBSD)
C++
Java
Kotlin
C#
Comparado às bibliotecas de log de código aberto existentes, o BqLog oferece vantagens significativas de desempenho (consulte Benchmark). Não é apenas adequado para servidores e clientes, mas também altamente compatível com dispositivos móveis.
Com baixo consumo de memória, no caso do Benchmark de 10 threads e 20.000.000 de entradas de log, o próprio BqLog consome menos de 1 MB de memória.
Fornece um formato de log em tempo real de alto desempenho e alta compactação
Pode ser usado normalmente em motores de jogos ( Unity
, Unreal
), com suporte a tipos comuns fornecidos para Unreal.
Suporta caracteres e strings UTF-8
, UTF-16
, UTF-32
, bem como tipos de parâmetros comuns como bool, float, double e vários comprimentos e tipos de números inteiros
Suporta format specifications
C++20
O registro assíncrono oferece suporte à revisão de falhas para evitar perda de dados (inspirado no XLog)
Tamanho extremamente pequeno, com a biblioteca dinâmica tendo apenas cerca de 200k após a compilação do Android
Não gera alocações de heap adicionais em Java e C#, evitando a criação constante de novos objetos durante o tempo de execução
Depende apenas da biblioteca de linguagem C padrão e das APIs da plataforma e pode ser compilado no modo ANDROID_STL = none
do Android
Suporta padrões de compilação C++11
e posteriores e pode ser compilado sob requisitos estritos de -Wall -Wextra -pedantic -Werror
O módulo de compilação é baseado no CMake
e fornece scripts de compilação para diferentes plataformas, facilitando o uso
Suporta tipos de parâmetros personalizados
Muito amigável para sugestões de código
Por que o BqLog é tão rápido - Formato de log compactado em tempo real de alto desempenho
Por que o BqLog é tão rápido - Buffer de anel de alta simultaneidade
Integrando BqLog em seu projeto
Demonstração simples
Visão geral da arquitetura
Instruções de uso da API do processo principal
1-Criando um objeto de log
2-Recuperando um objeto de log
3-Registro de mensagens
4-Outras APIs
Registro síncrono e assíncrono
1. Segurança de thread de registro assíncrono
Introdução aos anexadores
1. ConsoleAppender
2. TextFileAppender
3. CompressedFileAppender (altamente recomendado)
4. RawFileAppender
Instruções de configuração
1. Exemplo completo
2. Explicação detalhada
Decodificação offline de anexadores de formato binário
Instruções de construção
1. Construção de biblioteca
2. Construção e execução de demonstração
3. Instruções de execução de teste automatizado
4. Instruções de execução de benchmark
Tópicos de uso avançado
1. Sem alocação de heap
2. Objetos de log com suporte de categoria
3. Proteção de dados em caso de saída anormal do programa
4. Sobre NDK e ANDROID_STL = nenhum
5. Tipos de parâmetros personalizados
6. Usando BqLog no Unreal Engine
Referência
1. Descrição do benchmark
2. Código de referência BqLog C++
3. Código de referência Java BqLog
4. Código de referência Log4j
5. Resultados de referência
O BqLog pode ser integrado ao seu projeto de diversas formas. Para C++, ele oferece suporte a bibliotecas dinâmicas, bibliotecas estáticas e arquivos de origem. Para Java e C#, ele oferece suporte a bibliotecas dinâmicas com código-fonte wrapper. Abaixo estão os métodos para incluir BqLog:
O repositório de código inclui arquivos de biblioteca dinâmica pré-compilados localizados em /dist/dynamic_lib/. Para integrar o BqLog ao seu projeto usando os arquivos da biblioteca, você precisa fazer o seguinte:
Selecione o arquivo de biblioteca dinâmica correspondente à sua plataforma e adicione-o ao sistema de compilação do seu projeto.
Copie o diretório /dist/dynamic_lib/include em seu projeto e adicione-o à lista de diretórios de inclusão. (Se você estiver usando a biblioteca .framework do XCode, poderá pular esta etapa, pois o arquivo .framework já inclui os arquivos de cabeçalho).
O repositório de código inclui arquivos de biblioteca estática pré-compilados localizados em /dist/static_lib/. Para integrar o BqLog ao seu projeto usando os arquivos da biblioteca, você precisa fazer o seguinte:
Selecione o arquivo da biblioteca estática correspondente à sua plataforma e adicione-o ao sistema de compilação do seu projeto.
Copie o diretório /dist/static_lib/include em seu projeto e adicione-o à lista de diretórios de inclusão. (Se você estiver usando a biblioteca .framework do XCode, poderá pular esta etapa, pois o arquivo .framework já inclui os arquivos de cabeçalho).
BqLog também suporta inclusão direta de código-fonte em seu projeto para compilação. Para integrar o BqLog usando o código-fonte, siga estas etapas:
Copie o diretório /src em seu projeto como referência de código-fonte.
Copie o diretório /include em seu projeto e adicione-o à lista de diretórios de inclusão.
Se estiver compilando a versão do Windows no Visual Studio, adicione /Zc:__cplusplus às opções de compilação para garantir que o suporte padrão atual do compilador C++ seja determinado corretamente.
Se estiver usando o código-fonte no NDK do Android, consulte 4. Sobre NDK e ANDROID_STL = nenhum para considerações importantes.
Em C#, o BqLog pode ser usado por meio de uma biblioteca dinâmica nativa e um C# Wrapper, com suporte a mecanismos Mono, Microsoft CLR e Unity. Unity é compatível com os modos Mono e IL2CPP. Para usar BqLog em C#, siga estas etapas:
Selecione o arquivo de biblioteca dinâmica correspondente à sua plataforma em /dist/dynamic_lib/ e adicione-o ao seu projeto (para Unity, consulte Unity Import e configure plug-ins).
Copie os arquivos de código-fonte de /wrapper/csharp/src para o seu projeto.
Em Java, o BqLog pode ser usado por meio de uma biblioteca dinâmica nativa e um Java Wrapper, suportando ambientes JVM comuns e Android. Para integrar o BqLog em uma JVM, siga estas etapas:
Selecione o arquivo de biblioteca dinâmica correspondente à sua plataforma em /dist/dynamic_lib/ e adicione-o ao seu projeto.
Copie os arquivos de código-fonte de /wrapper/java/src para o seu projeto.
(Opcional) Copie o diretório /dist/dynamic_lib/include em seu projeto e adicione-o à lista de diretórios de inclusão se você pretende chamar BqLog do NDK.
O código a seguir gerará mais de 1.000 logs em seu console (ou ADB Logcat se estiver no Android)
#se definido(WIN32) #include#endif#include #include int main() { #if Defined(WIN32) // Muda a linha de comando do Windows para UTF-8 porque BqLog gera todo o texto final na codificação UTF-8 para evitar problemas de exibição SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); #endif // Esta string é a configuração do log. Aqui ele configura um registrador com um anexador (destino de saída) chamado appender_0, que envia para o console. std::string config = R"( # O destino de saída deste appender é o console appenders_config.appender_0.type=console # Este appender usa a hora local para carimbos de data e hora appenders_config.appender_0.time_zone=default local time # Este appender gera logs desses 6 níveis (sem espaços entre)appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal] )"; bq::log log = bq::log::create_log("meu_primeiro_log", config); // Cria um objeto de log usando a configuração for(int i = 0; i < 1024; ++i) { log.info("Este é um log de teste de informações, a string de formato é UTF-8, param int:{}, param bool :{}, param string8:{}, param string16:{}, param string32:{} , parâmetro float:{}", i, true, "utf8-string", u"utf16-string", U"utf32-string", 4.3464f); } log.error(U"Este é um log de teste de erro, a string de formato é UTF-32"); bq::log::force_flush_all_logs(); // O padrão do BqLog é saída assíncrona. Para garantir que os logs estejam visíveis antes da saída do programa, force a liberação para sincronizar a saída uma vez. retornar 0; }
usando System.Text;usando System;public class demo_main { public static void Main(string[] args) { Console.OutputEncoding = Encoding.UTF8; Console.InputEncoding = Encoding.UTF8; string config = @" # O destino de saída deste appender é o console appenders_config.appender_0.type=console # Este appender usa a hora local para carimbos de data e hora ppenders_config.appender_0.time_zone=default local time # Este appender gera logs desses 6 níveis (sem espaços em entre)appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal] "; bq.log log = bq.log.create_log("meu_primeiro_log",config); // Cria um objeto de log usando a configuração for (int i = 0; i < 1024; ++i) { log.info("Este é um log de teste de informações, a string de formato é UTF-16, param int:{}, param bool :{}, param string:{}, param float:{}", i, true, " String Texto", 4.3464f); } bq.log.force_flush_all_logs(); Console.ReadKey(); }}
public class demo_main { public static void main(String[] args) { // TODO Método gerado automaticamente stub String config = """ # O destino de saída deste appender é o console appenders_config.appender_0.type=console # Este appender usa a hora local para carimbos de data e hora appenders_config.appender_0.time_zone=default local time # Este appender gera logs desses 6 níveis (sem espaços entre eles) appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal] """; bq.log log = bq.log.create_log("meu_primeiro_log",config); // Cria um objeto de log usando a configuração for (int i = 0; i < 1024; ++i) { log.info("Este é um log de teste de informações, a string de formato é UTF-16, param int:{}, param bool :{}, param string:{}, param float:{}", i, true, "String Texto", 4.3464f); } bq.log.force_flush_all_logs(); } }
O diagrama acima ilustra claramente a estrutura básica do BqLog. No lado direito do diagrama está a implementação interna da biblioteca BqLog, enquanto no lado esquerdo está o seu programa e código. Seu programa pode chamar BqLog usando os wrappers fornecidos (APIs orientadas a objetos para diferentes linguagens). No diagrama, dois Logs são criados: um denominado “Log A” e outro denominado “Log B”. Cada Log é anexado a um ou mais Appenders. Um Appender pode ser entendido como o destino de saída do conteúdo do log. Pode ser o console (logs ADB Logcat para Android), arquivos de texto ou até mesmo formatos especializados, como arquivos de log compactados ou arquivos de formato de log binário regular.
Dentro do mesmo processo, wrappers para diferentes idiomas podem acessar o mesmo objeto Log. Por exemplo, se um objeto Log denominado Log A for criado em Java, ele também poderá ser acessado e usado no lado C++ pelo nome Log A.
Em casos extremos, como um jogo desenvolvido em Unity rodando no sistema Android, você pode envolver as linguagens Java, Kotlin, C# e C++ no mesmo aplicativo. Todos eles podem compartilhar o mesmo objeto Log. Você pode criar o log no lado Java usando create_log e acessá-lo em outras linguagens usando get_log_by_name.
Nota: As APIs a seguir são declaradas na classe bq::log (ou bq.log). Para economizar espaço, apenas as APIs C++ são listadas. As APIs em Java e C# são idênticas e não serão repetidas aqui.
Em C++, bq::string
é o tipo de string UTF-8 na biblioteca BqLog. Você também pode passar strings no estilo C como char ou std::string
ou std::string_view
, que serão convertidas automática e implicitamente.
Um objeto de log pode ser criado usando a função estática create_log. Sua declaração é a seguinte:
//API C++ ////// Crie um objeto de log /// /// Se o nome do log for uma string vazia, bqLog atribuirá automaticamente a você um nome de log exclusivo. Se o nome do log já existir, ele retornará o objeto de log existente anteriormente e substituirá a configuração anterior pela nova configuração. /// Log config string ///Um objeto de log, se a criação falhar, o método is_valid() dele retornará false log estático create_log(const bq::string& log_name, const bq::string& config_content);
O código cria um objeto de log passando o nome do objeto de log e uma string de configuração. A configuração do log pode ser referenciada nas Instruções de Configuração. Aqui estão alguns pontos-chave a serem observados:
Independentemente de ser C# ou Java, o objeto de log retornado nunca será nulo. Entretanto, devido a erros de configuração ou outros motivos, um objeto de log inválido poderá ser criado. Portanto, você deve usar a função is_valid() para verificar o objeto retornado. Executar operações em um objeto inválido pode causar falha no programa.
Se uma string vazia for passada como nome do log, o bqLog gerará automaticamente um nome de log exclusivo, como "AutoBqLog_1".
Chamar create_log em um objeto de log já existente com o mesmo nome não criará um novo objeto de log, mas substituirá a configuração anterior pela nova. Porém, alguns parâmetros não podem ser modificados neste processo; consulte Instruções de configuração para obter detalhes.
Exceto ao usar no NDK (consulte 4. Sobre NDK e ANDROID_STL = none), você pode inicializar o objeto de log diretamente em variáveis globais ou estáticas usando esta API em outras situações.
Se um objeto de log já tiver sido criado em outro lugar, você poderá obter o objeto de log criado diretamente usando a função get_log_by_name.
//API C++ ////// Obtenha um objeto de log pelo seu nome /// /// Nome do objeto de log que você deseja encontrar ///Um objeto de log, se o objeto de log com nome específico não for encontrado, o método is_valid() dele retornará false log estático get_log_by_name(const bq::string& log_name);
Você também pode usar esta função para inicializar um objeto de log em variáveis globais ou funções estáticas. No entanto, observe que você deve garantir que o objeto de log com o nome especificado já exista. Caso contrário, o objeto de log retornado ficará inutilizável e seu método is_valid() retornará falso.
///Funções de log principais, existem 6 níveis de log: ///verbose, debug, info, warning, error, fatal templatebq::enable_if_t ::value, bool> verbose(const STR& log_content) const; modelo bq::enable_if_t ::value, bool> verbose(const STR& log_format_content, const Args&... args) const; modelo bq::enable_if_t ::value, bool> debug(const STR& log_content) const; modelo bq::enable_if_t ::value, bool> debug(const STR& log_format_content, const Args&... args) const; modelo bq::enable_if_t ::value, bool> info(const STR& log_content) const; modelo bq::enable_if_t ::value, bool> info(const STR& log_format_content, const Args&... args) const; modelo bq::enable_if_t ::value, bool> warning(const STR& log_content) const; modelo bq::enable_if_t ::value, bool> warning(const STR& log_format_content, const Args&... args) const; modelo bq::enable_if_t ::value, bool> erro(const STR& log_content) const; modelo bq::enable_if_t ::value, bool> erro(const STR& log_format_content, const Args&... args) const; modelo bq::enable_if_t ::value, bool> fatal(const STR& log_content) const; modelo bq::enable_if_t ::value, bool> fatal(const STR& log_format_content, const Args&... args) const;
Ao registrar mensagens, preste atenção a três pontos principais:
Como você pode ver, nossos logs são divididos em seis níveis: detalhado, depuração, informação, aviso, erro e fatal, consistente com Android. Sua importância aumenta sequencialmente. Quando enviados para o console, eles aparecerão em cores diferentes.
O parâmetro STR é semelhante ao primeiro parâmetro de printf e pode ter vários tipos de string comuns, incluindo:
Java.lang.String do Java
Cadeia de caracteres do C#
Várias codificações de strings de estilo C do C++ e 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 até mesmo tipos de string personalizados, que você pode consultar emCustom Parameter Types )
Você pode adicionar vários parâmetros após o parâmetro STR. Esses parâmetros serão formatados nos locais especificados no STR, seguindo regras semelhantes ao std::format do C++20 (exceto pela falta de suporte para argumentos posicionais e formato de data e hora). Por exemplo, usar um único {} representa uma formatação padrão de um parâmetro e {:.2f} especifica a precisão para formatar um número de ponto flutuante. Tente usar parâmetros formatados para gerar logs em vez de concatenar strings manualmente. Essa abordagem é ideal para desempenho e armazenamento em formato compactado.
Os tipos de parâmetros atualmente suportados incluem:
Ponteiros nulos (saída como nula)
Ponteiros (saída como um endereço hexadecimal começando com 0x)
bool
Caracteres de byte único (char)
Caracteres de byte duplo (char16_t, wchar_t, char do C#, char do Java)
Caracteres de quatro bytes (char32_t ou wchar_t)
Inteiros de 8 bits
Inteiros não assinados de 8 bits
Inteiros de 16 bits
Inteiros não assinados de 16 bits
Inteiros de 32 bits
Inteiros não assinados de 32 bits
Inteiros de 64 bits
Inteiros não assinados de 64 bits
Números de ponto flutuante de 32 bits
Números de ponto flutuante de 64 bits
Outros tipos de POD desconhecidos em C++ (limitados aos tamanhos 1, 2, 4 ou 8 bytes, tratados como int8, int16, int32 e int64 respectivamente)
Strings, incluindo todos os tipos de string mencionados no parâmetro STR
Qualquer classe ou objeto em C# e Java (emitindo sua string ToString())
Tipos de parâmetros personalizados, conforme detalhado em Tipos de parâmetros personalizados
Existem APIs adicionais comumente usadas que podem realizar tarefas específicas. Para obter descrições detalhadas da API, consulte bq_log/bq_log.h, bem como a classe bq.log em Java e C#. Aqui estão algumas APIs principais que precisam ser destacadas:
////// Desinicializar BqLog, invoque esta função antes que seu programa exista. /// static void uninit();
Recomenda-se executar uninit()
antes de sair do programa ou desinstalar a biblioteca dinâmica auto-implementada que usa BqLog, caso contrário o programa pode travar ao sair em certas circunstâncias específicas.
////// Se bqLog for assíncrono, uma falha no programa pode fazer com que os logs no buffer não sejam persistidos no disco. /// Se este recurso estiver habilitado, o bqLog tentará realizar uma liberação forçada dos logs no buffer em caso de falha. No entanto, /// esta funcionalidade não garante sucesso e suporta apenas sistemas POSIX. /// static void enable_auto_crash_handle();
Para uma introdução detalhada, consulte Proteção de dados em saída anormal do programa
////// Libera de forma síncrona o buffer de todos os objetos de log /// para garantir que todos os dados no buffer sejam processados após a chamada. /// static void force_flush_all_logs(); ////// Libera de forma síncrona o buffer deste objeto de log /// para garantir que todos os dados no buffer sejam processados após a chamada. /// void force_flush();
Como o bqLog usa log assíncrono por padrão, há momentos em que você pode querer sincronizar e gerar imediatamente todos os logs. Nesses casos, você precisa chamar force_flush().
////// Registra um retorno de chamada que será invocado sempre que uma mensagem de log do console for exibida. /// Isso pode ser usado para um sistema externo monitorar a saída de log do console. /// /// static void register_console_callback(bq::type_func_ptr_console_callback callback); ////// Cancela o registro de um retorno de chamada do console. /// /// static void unregister_console_callback(bq::type_func_ptr_console_callback callback);
A saída do ConsoleAppender vai para o console ou para os logs do ADB Logcat no Android, mas isso pode não cobrir todas as situações. Por exemplo, em motores de jogos personalizados ou IDEs personalizados, é fornecido um mecanismo para chamar uma função de retorno de chamada para cada saída de log do console. Isso permite reprocessar e gerar o log do console em qualquer lugar do seu programa.
Cuidado Adicional: Não gere nenhum log BQ sincronizado no retorno de chamada do console, pois isso pode facilmente levar a conflitos.
////// Ativa ou desativa o buffer do anexador do console. /// Como nosso wrapper pode ser executado em máquinas virtuais C# e Java, e não queremos invocar retornos de chamada diretamente de um thread nativo, /// podemos ativar esta opção. Dessa forma, todas as saídas do console serão salvas no buffer até que as busquemos. /// /// ///static void set_console_buffer_enable(bool enable); /// /// Busca e remove uma entrada de log do buffer do anexador do console de maneira thread-safe. /// Se o buffer do anexador do console não estiver vazio, a função on_console_callback será invocada para esta entrada de log. /// Certifique-se de não gerar logs BQ sincronizados dentro da função de retorno de chamada. /// /// Uma função de retorno de chamada a ser invocada para a entrada de log buscada se o buffer do anexador do console não estiver vazio ///True se o o buffer do appender do console não está vazio e uma entrada de log é buscada; caso contrário, False será retornado. static bool fetch_and_remove_console_buffer(bq::type_func_ptr_console_callback on_console_callback);
Além de interceptar a saída do console por meio de um retorno de chamada do console, você pode buscar ativamente as saídas de log do console. Às vezes, podemos não querer que a saída do log do console passe por um retorno de chamada porque você não sabe de qual thread o retorno de chamada virá (por exemplo, em algumas máquinas virtuais C# ou JVMs, a VM pode estar executando a coleta de lixo quando o console callback é chamado, o que pode levar a travamentos ou travamentos).
O método usado aqui envolve ativar o buffer do console por meio de set_console_buffer_enable
. Isso faz com que cada saída de log do console seja armazenada na memória até que chamemos ativamente fetch_and_remove_console_buffer
para recuperá-la. Portanto, se você optar por usar esse método, lembre-se de buscar e limpar os logs imediatamente para evitar memória não liberada.
Cuidado Adicional: Não gere nenhum log BQ sincronizado no retorno de chamada do console, pois isso pode facilmente levar a conflitos.
Cuidado adicional: Se você estiver usando este código em um ambiente IL2CPP, certifique-se de que on_console_callback esteja marcado como estático inseguro e decorado com o atributo [MonoPInvokeCallback(typeof(type_console_callback))].
////// Modifica a configuração do log, mas alguns campos, como buffer_size, não podem ser modificados. /// /// ///bool reset_config(const bq::string& config_content);
Às vezes você pode querer modificar a configuração de um log dentro do seu programa. Além de recriar o objeto de log para substituir a configuração (consulte Criando um objeto de log), você também pode usar a interface de redefinição. Entretanto, observe que nem todos os itens de configuração podem ser modificados desta forma. Para obter detalhes, consulte as Instruções de configuração
////// Desativa ou ativa temporariamente um Appender específico. /// /// /// void set_appenders_enable(const bq::string& appender_name, bool enable) ;
Por padrão, os Appenders na configuração estão ativos, mas aqui é fornecido um mecanismo para desativá-los e reativá-los temporariamente.
////// Funciona apenas quando o snapshot está configurado. /// Ele irá decodificar o buffer de snapshot para texto. /// /// se o carimbo de data e hora de cada log é o horário GMT ou o horário local ///o buffer de snapshot decodificado bq::string take_snapshot(bool use_gmt_time) const;
Às vezes, certos recursos especiais exigem a saída da última parte dos logs, o que pode ser feito usando o recurso de instantâneo. Para habilitar esse recurso, primeiro você precisa ativar o snapshot na configuração do log e definir o tamanho máximo do buffer, em bytes. Além disso, você precisa especificar os níveis de log e as categorias a serem filtradas para o instantâneo (opcional). Para configuração detalhada, consulte Configuração de instantâneo. Quando um snapshot for necessário, chamar take_snapshot() retornará a string formatada contendo as entradas de log mais recentes armazenadas no buffer do snapshot. Em C++, o tipo é bq::string
, que pode ser convertido implicitamente em std::string
.
namespace bq{ namespace tools { //Esta é uma classe utilitária para decodificar formatos de log binários. //Para usá-lo, primeiro crie um objeto log_decoder, //depois chame sua função decode para decodificar. //Após cada chamada bem-sucedida, //você pode usar get_last_decoded_log_entry() para recuperar o resultado decodificado. //Cada chamada decodifica uma entrada de log. estrutura log_decoder {privado: bq::string decode_text_; bq::appender_decode_result resultado_ = bq::appender_decode_result::sucesso; uint32_t identificador_ = 0; public: ////// Cria um objeto log_decoder, com cada objeto log_decoder correspondente a um arquivo de log binário. /// /// o caminho de um arquivo de log binário, pode ser um caminho relativo ou um caminho absoluto log_decoder(const bq::string& log_file_path); ~log_decodificador(); ////// Decodifica uma entrada de log. cada chamada desta função decodificará apenas 1 entrada de log /// ///decode result, appender_decode_result::eof significa que todo o arquivo de log foi decodificado bq::appender_decode_result decode(); ////// obtém o último resultado de decodificação /// ///bq::appender_decode_result get_last_decode_result() const; /// /// obtém o conteúdo da última entrada do log de decodificação /// ///const bq::string& get_last_decoded_log_entry() const; }; } }
Esta é uma classe de utilitário que pode decodificar arquivos de log gerados por Appenders do tipo binário em tempo de execução, como CompressedFileAppender e RawFileAppender。
Para usá-lo, primeiro crie um objeto log_decoder. Então, cada vez que você chama a função decode(), ela decodifica uma entrada de log em sequência. Se o resultado retornado for bq::appender_decode_result::success, você pode chamar get_last_decoded_log_entry() para obter o conteúdo de texto formatado da última entrada de log decodificada. Se o resultado for bq::appender_decode_result::eof, significa que todos os logs foram lidos completamente.
BqLog permite configurar se um objeto de log é síncrono ou assíncrono por meio da configuração thread_mode. As principais diferenças entre esses dois modos são as seguintes:
Registro síncrono | Registro assíncrono | |
---|---|---|
Comportamento | Depois de chamar a função de log, o log é imediatamente gravado no anexador correspondente. | Depois de chamar a função de log, o log não é gravado imediatamente; em vez disso, ele é transferido para um thread de trabalho para processamento periódico. |
Desempenho | Baixo, pois o thread que grava o log precisa ser bloqueado e aguardar que o log seja gravado no anexador correspondente antes de retornar da função de log. | Alto, pois o thread que grava o log não precisa esperar pela saída real e pode retornar imediatamente após o log. |
Segurança de linha | Alto, mas requer que os parâmetros de log não sejam modificados durante a execução da função de log. | Alto, mas requer que os parâmetros de log não sejam modificados durante a execução da função de log. |
Um equívoco comum sobre o log assíncrono é que ele é menos seguro para threads, e os usuários ficam preocupados com a possibilidade de os parâmetros serem recuperados no momento em que o thread de trabalho processa o log. Por exemplo:
{ const char str_array[5] = {'T', 'E', 'S', 'T', '�'}; const char* str_ptr = str_array; log_obj.info("Este é o parâmetro de teste: {}, {}", str_array, str_ptr); }
No exemplo acima, str_array
é armazenado na pilha e, uma vez encerrado o escopo, sua memória não é mais válida. Os usuários podem se preocupar com o fato de que, se o log assíncrono for usado, no momento em que o thread de trabalho processar o log, str_array
e str_ptr
serão variáveis inválidas.
No entanto, tal situação não ocorrerá porque o BqLog copia todo o conteúdo dos parâmetros em seu ring_buffer
interno durante a execução da função info
. Assim que a função info
retornar, as variáveis externas como str_array
ou str_ptr
não serão mais necessárias. Além disso, o ring_buffer
não armazenará um endereço de ponteiro const char*
, mas sempre armazenará a string inteira.
O verdadeiro problema potencial surge no seguinte cenário:
static std::string global_str = "olá mundo"; // Esta é uma variável global modificada por múltiplos threads.void thread_a() { log_obj.info("Este é o parâmetro de teste: {}", global_str); }
Se o conteúdo de global_str
mudar durante a execução da função info
, isso poderá levar a um comportamento indefinido. O BqLog fará o possível para evitar uma falha, mas a exatidão do resultado final não pode ser garantida.
Um Appender representa o destino de saída do log. O conceito de Appenders no bqLog é basicamente o mesmo do Log4j. Atualmente, o bqLog fornece os seguintes tipos de Appenders:
O destino de saída deste Appender é o console, incluindo o ADB do Android e o console correspondente no iOS. A codificação do texto é UTF-8.
Este Appender gera arquivos de log diretamente no formato de texto UTF-8.
Este Appender gera arquivos de log em um formato compactado, que é o highly recommended format by bqLog
. Possui o desempenho mais alto entre todos os Appenders e produz o menor arquivo de saída. No entanto, o arquivo final precisa ser decodificado. A decodificação pode ser feita em tempo de execução ou decodificação offline。
Este Appender envia o conteúdo do log binário da memória diretamente para um arquivo. Seu desempenho é superior ao do TextFileAppender, mas consome mais espaço de armazenamento. O arquivo final precisa ser decodificado. A decodificação pode ser feita em tempo de execução ou decodificação offline. Este Appender não é recomendado para uso.
Abaixo está uma comparação abrangente dos vários Appenders:
Nome | Alvo de saída | Diretamente legível | Desempenho de saída | Tamanho de saída |
---|---|---|---|---|
ConsoleAppender | Console | ✔ | Baixo | - |
TextFileAppender | Arquivo | ✔ | Baixo | Grande |
Arquivo CompactadoAppender | Arquivo | ✘ | Alto | Pequeno |
RawFileAppender | Arquivo | ✘ | Médio | Grande |
Configuração refere-se à string de configuração nas funções create_log e reset_config. Esta string usa o formato de arquivo de propriedades e suporta # comentários (mas lembre-se de iniciar uma nova linha com # para comentários).
Abaixo está um exemplo completo:
# Esta configuração configura um objeto de log com um total de 5 Appenders, incluindo dois TextFileAppenders que geram saída para dois arquivos diferentes.# O primeiro Appender é denominado appender_0 e seu tipo é ConsoleAppenderappenders_config.appender_0.type=console# O fuso horário para appender_0 é o horário local do sistemaappenders_config.appender_0.time_zone=default local time# appender_0 produzirá todos os 6 níveis de logs (nota: não deve haver espaços entre os níveis de log, ou não será possível analisar)appenders_config.appender_0.levels=[verbose,debug ,info,warning,error,fatal]# O segundo Appender é denominado appender_1 e seu tipo é TextFileAppenderappenders_config.appender_1.type=text_file# O fuso horário para appender_1 é GMT, que é UTC+0appenders_config.appender_1.time_zone=gmt# appender_1 apenas gera logs de informações de nível e superiores, outros serão ignoradosappenders_config.appender_1.levels=[info,warning,error,fatal]# O caminho para appender_1 estará no diretório bqLog relativo do programa, com nomes de arquivos começando com normal, seguido por a data e a extensão .log# No iOS, será salvo em /var/mobile/Containers/Data/Application/[APP]/Library/Caches/bqLog# No Android, será salvo em [android.content.Context .getExternalFilesDir()]/bqLogappenders_config.appender_1.file_name=bqLog/normal# O tamanho máximo do arquivo é 10.000.000 bytes; se excedido, um novo arquivo será criadoappenders_config.appender_1.max_file_size=10000000# Arquivos com mais de dez dias serão limposappenders_config.appender_1.expire_time_days=10# Se o tamanho total da saída exceder 100.000.000 bytes, os arquivos serão limpos a partir do mais antigoappenders_config.appender_1.capacity_limit=100000000# O terceiro Appender é denominado appender_2 e seu tipo é TextFileAppenderappenders_config.appender_2.type=text_file# appender_2 gerará todos os níveis de logsappenders_config.appender_2.levels=[all]# O caminho para appender_2 estará no Diretório bqLog relativo do programa, com nomes de arquivos começando com new_normal, seguido pela data e .log extensionappenders_config.appender_2.file_name=bqLog/new_normal# Esta opção só tem efeito no Android, salvando logs no diretório de armazenamento interno, que é [android .content.Context.getFilesDir()]/bqLogappenders_config.appender_2.is_in_sandbox=true# O quarto Appender é denominado appender_3 e seu tipo é CompressedFileAppenderappenders_config.appender_3.type=compressed_file# appender_3 produzirá todos os níveis de logsappenders_config.appender_3.levels=[all ]# O caminho para appender_3 estará no caminho absoluto ~/bqLog diretório do programa, com nomes de arquivos começando com compress_log, seguido pela data e .logcompr extensionappenders_config.appender_3.file_name=~/bqLog/compress_log# O quinto Appender é nomeado appender_4 e seu tipo é RawFileAppenderappenders_config.appender_4.type=raw_file# appender_4 está desabilitado por padrão e pode ser habilitado posteriormente usando set_appenders_enableappenders_config.appender_4.enable=false# appender_4 gerará todos os níveis de logsappenders_config.appender_4.levels=[all]# O caminho para appender_4 estará no diretório bqLog relativo do programa, com nomes de arquivos começando com raw_log, seguido pela data e .lograw extensionappenders_config.appender_4.file_name=bqLog/raw_log# Os logs só serão processados se sua categoria começar com ModuleA, ModuleB. SystemC, caso contrário tudo será ignorado (o conceito de Categoria é explicado em detalhes nos tópicos de uso avançado posteriormente)appenders_config.appender_4.categories_mask=[ModuleA,ModuleB.SystemC]# O tamanho total do buffer assíncrono é 65535 bytes; o significado específico é explicado posteriormentelog.buffer_size=65535# O nível de confiabilidade do log é normal; o significado específico é explicado mais tardelog.reliable_level=normal# Os logs só serão processados se sua categoria corresponder aos três caracteres curinga a seguir, caso contrário, todos serão ignorados (o conceito de categoria será explicado em detalhes nos tópicos de uso avançado posteriormente)log.categories_mask= [*default,ModuleA,ModuleB.SystemC]# Este é um log assíncrono; logs assíncronos são os logs recomendados e de melhor desempenho typelog.thread_mode=async# Se o nível de log for de erro ou fatal, inclua informações da pilha de chamadas em cada log entrylog.print_stack_levels=[error,fatal]# Habilite a funcionalidade de snapshot, o tamanho do cache de snapshot é 64Ksnapshot .buffer_size=65536# Apenas logs com informações e níveis de erro serão registrados no snapshotsnapshot.levels=[info,error]# Somente logs cuja categoria comece com ModuleA, ModuleB.SystemC serão registrados no snapshot, caso contrário serão ignorados.snapshot .categories_mask=[MóduloA.SystemA.ClassA,MóduloB]
O appenders_config
é um conjunto de configurações para Appenders. O primeiro parâmetro após appenders_config
é o nome do Appender, e todos os Appenders com o mesmo nome compartilham a mesma configuração.
Nome | Obrigatório | Valores configuráveis | Padrão | Aplicável ao ConsoleAppender | Aplicável a TextFileAppender | Aplicável a CompressedFileAppender | Aplicável a RawFileAppender |
---|---|---|---|---|---|---|---|
tipo | ✔ | console, arquivo_texto, arquivo_comprimido, arquivo_raw | ✔ | ✔ | ✔ | ✔ | |
habilitar | ✘ | Se o Appender está habilitado por padrão | verdadeiro | ✔ | ✔ | ✔ | ✔ |
níveis | ✘ | Matriz de níveis de log | [todos] | ✔ | ✔ | ✔ | ✔ |
fuso horário | ✘ | gmt ou qualquer outra string | Hora local | ✔ | ✔ | ✔ | ✔ |
nome_do_arquivo | ✔ | Caminho relativo ou absoluto | ✘ | ✔ | ✔ | ✔ | |
está_na_sandbox | ✘ | verdadeiro, falso | falso | ✘ | ✔ | ✔ | ✔ |
max_file_size | ✘ | Inteiro positivo ou 0 | 0 | ✘ | ✔ | ✔ | ✔ |
expire_time_days | ✘ | Inteiro positivo ou 0 | 0 | ✘ | ✔ | ✔ | ✔ |
limite_capacidade | ✘ | Inteiro positivo ou 0 | 0 | ✘ | ✔ | ✔ | ✔ |
categorias_mask | ✘ | Matriz de strings entre [] | Vazio | ✔ | ✔ | ✔ | ✔ |
Especifica o tipo do Appender.
console
: Representa ConsoleAppender
text_file
: Representa TextFileAppender
compressed_file
: Representa CompressedFileAppender
raw_file
: Representa RawFileAppender
O padrão é true
. Se definido como false
, o Appender será desabilitado por padrão e poderá ser habilitado posteriormente usando set_appenders_enable
.
Uma matriz entre []
, contendo qualquer combinação de verbose
, debug
, info
, warning
, error
, fatal
ou [all]
para aceitar todos os níveis. Nota: Não inclua espaços entre os níveis, ou não será possível analisar.
Especifica o fuso horário dos logs. gmt
representa o horário de Greenwich (UTC+0), e qualquer outra string ou deixá-la vazia usará o fuso horário local. O fuso horário afeta duas coisas:
O carimbo de data/hora dos logs de texto formatados (aplicável a ConsoleAppender e TextFileAppender)
Um novo arquivo de log será criado quando a meia-noite for ultrapassada no fuso horário especificado (aplicável a TextFileAppender, CompressedFileAppender e RawFileAppender).
O caminho e o prefixo do nome do arquivo para salvar arquivos. O caminho pode ser absoluto (não recomendado para Android e iOS) ou relativo. O nome do arquivo de saída final será este caminho e nome, seguido pela data, número do arquivo e extensão do Appender.
Significativo apenas no Android:
true
: os arquivos são armazenados no diretório de armazenamento interno (android.content.Context.getFilesDir()). Se não estiverem disponíveis, eles serão armazenados no diretório Armazenamento Externo (android.content.Context.getExternalFilesDir()). Se isso também não estiver disponível, eles serão armazenados no diretório Cache (android.content.Context.getCacheDir()).
false
: os arquivos são armazenados no diretório Armazenamento Externo por padrão. Se não estiverem disponíveis, eles serão armazenados no diretório Armazenamento Interno. Se isso também não estiver disponível, eles serão armazenados no diretório Cache.
O tamanho máximo do arquivo em bytes. Quando o arquivo salvo excede esse tamanho, um novo arquivo de log é criado, com os números dos arquivos aumentando sequencialmente. 0
desativa esse recurso.
O número máximo de dias para manter os arquivos. Arquivos anteriores a isso serão excluídos automaticamente. 0
desativa esse recurso.
O tamanho total máximo dos arquivos gerados por este Appender no diretório de saída. Se esse limite for excedido, os arquivos serão excluídos começando pelo mais antigo até que o tamanho total esteja dentro do limite. 0
desativa esse recurso.
Se o objeto de log for um objeto Log que suporte categorias, isso poderá ser usado para filtrar uma lista de categorias em forma de árvore. Quando o array não está vazio, este recurso está ativo. Por exemplo, [*default,ModuleA,ModuleB.SystemC]
significa que os logs com a categoria padrão