BqLog(扁鹊日志)(V 1.4.0)
中文文档
BqLog is a lightweight, high-performance logging system used in projects such as "Honor of Kings," and it has been successfully deployed and is running smoothly.
Supported Platforms
- Windows 64 bit
- MacOS
- Linux
- iOS
- Android(X86_64, arm64-v8a、armeabi-v7a)
Supported Languages
- C++
- Java
- Kotlin
- C#
Features
- Compared to existing open-source logging libraries, BqLog offers significant performance advantages (see Benchmark). It is not only suitable for servers and clients but also highly compatible with mobile devices.
- With low memory consumption, in the Benchmark case of 10 threads and 20,000,000 log entries, BqLog itself consumes less than 1 MB of memory.
- Provides a high-performance, high-compression real-time log format
- Can be used normally in game engines (
Unity
,Unreal
), with support for common types provided for Unreal. - Supports
UTF-8
,UTF-16
,UTF-32
characters and strings, as well as common parameter types like bool, float, double, and various lengths and types of integers - Supports
C++20
format specifications
- Asynchronous logging supports crash review to avoid data loss (inspired by XLog)
- Extremely small size, with the dynamic library being only about 200k after Android compilation
- Does not generate additional heap allocations in Java and C#, avoiding constant new object creation during runtime
- Only depends on the standard C language library and platform APIs, and can be compiled in Android's
ANDROID_STL = none
mode - Supports
C++11
and later compilation standards, and can be compiled under strict requirements of -Wall -Wextra -pedantic -Werror - Compilation module is based on
CMake
and provides compilation scripts for different platforms, making it easy to use - Supports custom parameter types
- Very friendly to code suggestions
Menu
Integrating BqLog into Your Project
Simple Demo
Architecture Overview
Main Process API Usage Instructions
1-Creating a Log Object
2-Retrieving a Log Object
3-Logging Messages
4-Other APIs
Synchronous and Asynchronous Logging
1. Thread safety of asynchronous logging
Introduction to Appenders
1. ConsoleAppender
2. TextFileAppender
3. CompressedFileAppender(Highly Recommended)
4. RawFileAppender
Configuration Instructions
1. Complete Example
2. Detailed Explanation
Offline Decoding of Binary Format Appenders
Build Instructions
1. Library Build
2. Demo Build and Run
3. Automated Test Run Instructions
4. Benchmark Run Instructions
Advanced Usage Topics
1. No Heap Allocation
2. Log Objects with Category Support
3. Data Protection on Program Abnormal Exit
4. About NDK and ANDROID_STL = none
5. Custom Parameter Types
6. Using BqLog in Unreal Engine
Benchmark
1. Benchmark Description
2. BqLog C++ Benchmark Code
3. BqLog Java Benchmark Code
4. Log4j Benchmark Code
5. Benchmark Results
Integrating BqLog into Your Project
BqLog can be integrated into your project in various forms. For C++, it supports dynamic libraries, static libraries, and source files. For Java and C#, it supports dynamic libraries with wrapper source code. Below are the methods to include BqLog:
C++ (Dynamic Library)
The code repository includes precompiled dynamic library files located in /dist/dynamic_lib/. To integrate BqLog into your project using the library files, you need to do the following:
- Select the dynamic library file corresponding to your platform and add it to your project's build system.
- Copy the /dist/dynamic_lib/include directory into your project and add it to the include directory list. (If you are using XCode's .framework library, you can skip this step as the .framework file already includes the header files).
C++ (Static Library)
The code repository includes precompiled static library files located in /dist/static_lib/. To integrate BqLog into your project using the library files, you need to do the following:
- Select the static library file corresponding to your platform and add it to your project's build system.
- Copy the /dist/static_lib/include directory into your project and add it to the include directory list. (If you are using XCode's .framework library, you can skip this step as the .framework file already includes the header files).
C++ (Source Code)
BqLog also supports direct inclusion of source code into your project for compilation. To integrate BqLog using the source code, follow these steps:
- Copy the /src directory into your project as a source code reference.
- Copy the /include directory into your project and add it to the include directory list.
- If compiling the Windows version in Visual Studio, add /Zc:__cplusplus to the compilation options to ensure the current C++ compiler standard support is correctly determined.
- If using the source code in Android's NDK, please refer to 4. About NDK and ANDROID_STL = none for important considerations.
C#
In C#, BqLog can be used via a native dynamic library and a C# Wrapper, supporting Mono, Microsoft CLR, and Unity engines. Unity is compatible with both Mono and IL2CPP modes. To use BqLog in C#, follow these steps:
- Select the dynamic library file corresponding to your platform from /dist/dynamic_lib/ and add it to your project (for Unity, refer to Unity Import and configure plug-ins).
- Copy the source code files from /wrapper/csharp/src into your project.
Java
In Java, BqLog can be used via a native dynamic library and a Java Wrapper, supporting common JVM environments and Android. To integrate BqLog into a JVM, follow these steps:
- Select the dynamic library file corresponding to your platform from /dist/dynamic_lib/ and add it to your project.
- Copy the source code files from /wrapper/java/src into your project.
- (Optional) Copy the /dist/dynamic_lib/include directory into your project and add it to the include directory list if you intend to call BqLog from the NDK.
Simple Demo
The following code will output over 1000 logs to your console (or ADB Logcat if on Android)
C++
#if defined(WIN32)
#include <windows.h>
#endif
#include <string>
#include <bq_log/bq_log.h>
int main()
{
#if defined(WIN32)
// Switch Windows command line to UTF-8 because BqLog outputs all final text in UTF-8 encoding to avoid display issues
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
#endif
// This string is the log configuration. Here it configures a logger with one appender (output target) named appender_0, which outputs to the console.
std::string config = R"(
# This appender's output target is the console
appenders_config.appender_0.type=console
# This appender uses local time for timestamps
appenders_config.appender_0.time_zone=default local time
# This appender outputs logs of these 6 levels (no spaces in between)
appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal]
)";
bq::log log = bq::log::create_log("my_first_log", config); // Create a log object using the config
for(int i = 0; i < 1024; ++i)
{
log.info("This is an info test log, the format string is 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"This is an error test log, the format string is UTF-32");
bq::log::force_flush_all_logs(); // BqLog defaults to asynchronous output. To ensure logs are visible before program exit, force flush to sync output once.
return 0;
}
C#
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 = @"
# This appender's output target is the console
appenders_config.appender_0.type=console
# This appender uses local time for timestamps
ppenders_config.appender_0.time_zone=default local time
# This appender outputs logs of these 6 levels (no spaces in between)
appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal]
";
bq.log log = bq.log.create_log("my_first_log", config); // Create a log object using the config
for (int i = 0; i < 1024; ++i)
{
log.info("This is an info test log, the format string is UTF-16, param int:{}, param bool :{}, param string:{}, param float:{}", i, true, "String Text", 4.3464f);
}
bq.log.force_flush_all_logs();
Console.ReadKey();
}
}
Java#
public class demo_main {
public static void main(String[] args) {
// TODO Auto-generated method stub
String config = """
# This appender's output target is the console
appenders_config.appender_0.type=console
# This appender uses local time for timestamps
appenders_config.appender_0.time_zone=default local time
# This appender outputs logs of these 6 levels (no spaces in between)
appenders_config.appender_0.levels=[verbose,debug,info,warning,error,fatal]
""";
bq.log log = bq.log.create_log("my_first_log", config); // Create a log object using the config
for (int i = 0; i < 1024; ++i)
{
log.info("This is an info test log, the format string is UTF-16, param int:{}, param bool :{}, param string:{}, param float:{}", i, true, "String Text", 4.3464f);
}
bq.log.force_flush_all_logs();
}
}
Architecture Overview
The diagram above clearly illustrates the basic structure of BqLog. On the right side of the diagram is the internal implementation of the BqLog library, while on the left side is your program and code. Your program can call BqLog using the provided wrappers (object-oriented APIs for different languages). In the diagram, two Logs are created: one named "Log A" and the other named "Log B." Each Log is attached to one or more Appenders. An Appender can be understood as the output target of the log content. This can be the console (ADB Logcat logs for Android), text files, or even specialized formats like compressed log files or regular binary log format files.
Within the same process, wrappers for different languages can access the same Log object. For example, if a Log object named Log A is created in Java, it can also be accessed and used from the C++ side by the name Log A.
In extreme cases, such as a Unity-developed game running on the Android system, you might involve Java, Kotlin, C#, and C++ languages within the same app. They can all share the same Log object. You can create the Log on the Java side using create_log, and then access it in other languages using get_log_by_name.
Main Process API Usage Instructions
Note: The following APIs are declared in the bq::log (or bq.log) class. To save space, only the C++ APIs are listed. The APIs in Java and C# are identical and will not be repeated here.
In C++, bq::string
is the UTF-8 string type in the BqLog library. You can also pass in c-style strings like char or std::string
or std::string_view
, which will be automatically and implicitly converted.
1. Creating a Log Object
A log object can be created using the create_log static function. Its declaration is as follows:
//C++ API
/// <summary>
/// Create a log object
/// </summary>
/// <param name="log_name">If the log name is an empty string, bqLog will automatically assign you a unique log name. If the log name already exists, it will return the previously existing log object and overwrite the previous configuration with the new config.</param>
/// <param name="config_content">Log config string</param>
/// <returns>A log object, if create failed, the is_valid() method of it will return false</returns>
static log create_log(const bq::string& log_name, const bq::string& config_content);
The code creates a log object by passing in the name of the log object and a configuration string. The log configuration can be referenced in the Configuration Instructions. Here are a few key points to note:
- Regardless of whether it is C# or Java, the returned log object will never be null. However, due to configuration errors or other reasons, an invalid log object might be created. Therefore, you should use the is_valid() function to check the returned object. Performing operations on an invalid object may cause the program to crash.
- If an empty string is passed as the log name, bqLog will automatically generate a unique log name, such as "AutoBqLog_1."
- Calling create_log on an already existing log object with the same name will not create a new log object but will overwrite the previous configuration with the new one. However, some parameters cannot be modified in this process; see Configuration Instructions for details.
- Except when using in the NDK (refer to 4. About NDK and ANDROID_STL = none), you can initialize the log object directly in global or static variables using this API in other situations.
2. Retrieving a Log Object
If a log object has already been created elsewhere, you can obtain the created log object directly using the get_log_by_name function.
//C++ API
/// <summary>
/// Get a log object by it's name
/// </summary>
/// <param name="log_name">Name of the log object you want to find</param>
/// <returns>A log object, if the log object with specific name was not found, the is_valid() method of it will return false</returns>
static log get_log_by_name(const bq::string& log_name);
You can also use this function to initialize a log object in global variables or static functions. However, note that you must ensure the log object with the specified name already exists. Otherwise, the returned log object will be unusable, and its is_valid() method will return false.
3. Logging Messages
///Core log functions, there are 6 log levels:
///verbose, debug, info, warning, error, fatal