All language Wrappers share consistent API naming and semantics. Each API below shows all supported languages.
Create a Log through the create_log static function. If the name already exists, the existing object is returned and its configuration is updated (some fields like buffer_size cannot be changed, see Configuration).
C++
#include <bq_log/bq_log.h>
// bq::string accepts char*, std::string, std::string_view implicitly
static bq::log bq::log::create_log(const bq::string& log_name, const bq::string& config_content);Java / Kotlin
import bq.log;
bq.log logObj = bq.log.create_log("my_log", config);C#
using bq;
bq.log logObj = bq.log.create_log("my_log", config);TypeScript (Node.js / HarmonyOS ArkTS)
import { bq } from "@pippocao/bqlog"; // Node.js ESM
// import { bq } from "bqlog"; // HarmonyOS ArkTS
const logObj = bq.log.create_log("my_log", config);Python
from bq.log import log
log_obj = log.create_log("my_log", config)Go
import bq "github.com/Tencent/BqLog/go/v2"
// the third argument is the category names, nil for a plain log
logObj := bq.Create_log("my_log", config, nil)Key points:
- The return value will never be null in any language. If creation fails, check via
is_valid(). - If
log_nameis empty, BqLog auto-assigns a unique name like"AutoBqLog_1". - Calling
create_logwith an existing name reuses the object and updates its config. - Safe to call in global / static initializers — no Static Initialization Order issues.
Retrieve an already-created Log object by name:
C++
static bq::log bq::log::get_log_by_name(const bq::string& log_name);Java / Kotlin
bq.log logObj = bq.log.get_log_by_name("my_log");C#
bq.log logObj = bq.log.get_log_by_name("my_log");TypeScript
const logObj = bq.log.get_log_by_name("my_log");Python
log_obj = log.get_log_by_name("my_log")Go
logObj := bq.Get_log_by_name("my_log")Note: Ensure the Log has been created via
create_logfirst, otherwiseis_valid()returns false.
BqLog provides 6 log levels: verbose, debug, info, warning, error, fatal (importance increasing).
Format parameters use {} placeholders following C++20 std::format rules (no positional index or time formatting). Strongly recommend using format parameters instead of string concatenation for best performance and compression.
C++
log_obj.info("Hello {}, count={}", "world", 42);
log_obj.error("Error code: {}", err_code);
// All levels: verbose, debug, info, warning, error, fatalSupported STR types: char*, char16_t*, char32_t*, wchar_t*, std::string, std::u16string, std::wstring, Unreal FString / FName / FText, etc.
Besides the normal mode (log_obj.info(...) and the other level functions), C++ has a fast mode: six macros that write exactly the same log entry while costing the calling thread much less.
BQ_LOG_FAST_INFO(log_obj, "Hello {}, count={}", "world", 42);
BQ_LOG_FAST_ERROR(log_obj, "Error code: {}", err_code);
// BQ_LOG_FAST_VERBOSE, BQ_LOG_FAST_DEBUG, BQ_LOG_FAST_INFO, BQ_LOG_FAST_WARNING, BQ_LOG_FAST_ERROR, BQ_LOG_FAST_FATAL
// A generated category log: the same macro, with the category before the format string, as in info(cat, ...)
BQ_LOG_FAST_INFO(my_category_log, my_category_log.cat.Shop.Seller, "order {} paid", order_id);The first call at each call site registers its format string once; after that, the thread only checks the level, stores the timestamp and the raw arguments into its own buffer, and returns. The format string is not copied or hashed again, and nothing goes through a library function call.
Use it on hot paths. For everything else, the normal mode is the better default, because fast mode has a price:
- Fixed log object, category and format string per call site. A call site binds the log object and format string of its first call (the category is a compile-time type, so it is fixed anyway). Passing a different log object or a different format string to the same call site later (for example through a helper function that forwards its arguments into one
BQ_LOG_FAST_*line) is not supported: the entry still goes to the first log object with the first format string. Configuration changes (reset_config, levels, category masks) do apply. - Slightly more memory. Each registered format string is kept once in the log's metadata area for its lifetime, and every thread that uses fast mode keeps one 128-byte thread local slot.
- Weaker IDE hints. These are macros, so code completion and parameter hints are not as helpful as with the member functions of the normal mode. Argument types are still checked at compile time.
Categories are supported: when the second argument is log.cat.xxx the entry goes to that category, when it is the format string the default category is used. Which one is decided at compile time, so a call with a category is as fast as one without. Supported arguments are the same as in the normal mode. When a level is configured to print a stack trace (log.print_stack_levels) or the log runs in sync mode, a fast mode call quietly falls back to the normal mode, so the output stays correct; it is just not faster in that case.
From C++20 the fast path is also available as a member function template, with the format string as a template argument:
log_obj.fast_info<"Hello {}, count={}">("world", 42);
log_obj.fast_error<"Error code: {}">(err_code);
// fast_verbose, fast_debug, fast_info, fast_warning, fast_error, fast_fatal
// With a category: the category first, then the format string
my_category_log.fast_info<my_category_log.cat.Shop.Seller, "order {} paid">(order_id);It writes the same entries as BQ_LOG_FAST_* at the same cost. Unlike the macros, a call site is not bound to one log object, and IDE completion and compiler diagnostics work normally. This is also the fast log available through import bq.log;, since modules do not export macros.
The format string may be "", u8"", u"", U"" or L"". With MSVC, compile narrow non-ASCII literals with /utf-8.
Each distinct format string becomes part of a symbol name, so unstripped binaries grow faster than with the macros; strip release builds as usual.
Java / Kotlin
import static bq.utils.param.no_boxing;
logObj.info("Hello {}, count={}", "world", no_boxing(42));
logObj.error("Error code: {}", no_boxing(errCode));
// All levels: verbose, debug, info, warning, error, fatalImportant: Use
bq.utils.param.no_boxing()to wrap primitive types (int,float,boolean, etc.) to avoid boxing and GC pressure. Withoutno_boxing, primitives will be auto-boxed.
C#
logObj.info("Hello {}, count={}", "world", 42);
logObj.error("Error code: {}", errCode);
// All levels: verbose, debug, info, warning, error, fatalWhen parameter count ≤ 12, zero boxing/unboxing occurs. Beyond 12 parameters, boxing degrades gracefully.
TypeScript
logObj.info("Hello {}, count={}", "world", 42);
logObj.error("Error code: {}", errCode);
// All levels: verbose, debug, info, warning, error, fatalParameters are passed directly via NAPI — no extra boxing.
Python
log_obj.info("Hello {}, count={}", "world", 42)
log_obj.error("Error code: {}", err_code)
# All levels: verbose, debug, info, warning, error, fatalGo
logObj.Info("Hello {}, count={}", "world", 42)
logObj.Error("Error code: {}", errCode)
// All levels: Verbose, Debug, Info, Warning, Error, Fatal
// A log created with category names (Create_category_log) takes the category index first
catLog := bq.Create_category_log("my_cat_log", config, []string{"", "Gameplay"})
catLog.Info_c(1, "score={}", 42)
// Verbose_c, Debug_c, Info_c, Warning_c, Error_c, Fatal_cAny number of arguments. Built-in numbers,
boolandstring(including named types such as enums) keep their native types, so formatting still happens asynchronously on the worker thread. Values implementingfmt.Stringer/errorare logged throughString()/Error(), any other value throughfmt.Sprint, andnilasnull. Arguments are only converted after the level and category check passes. Every level function returnsbool: whether the entry was written.
- Null pointer →
null - Pointer →
0xhex address bool, single/double/four-byte characters- 8/16/32/64-bit integers and unsigned integers
- 32-bit and 64-bit floating point numbers
- All string types in each language
- C# / Java: any object (via
ToString()) - Go: any value (via
String()/Error(), otherwisefmt.Sprint) - C++: POD types (1/2/4/8 bytes), custom types (see Advanced Usage — Custom parameter types)
Ensure all buffered logs are written to disk. Call before program exit or at critical checkpoints.
C++
bq::log::force_flush_all_logs(); // flush all log objects
log_obj.force_flush(); // flush single log objectJava / Kotlin
bq.log.force_flush_all_logs();
logObj.force_flush();C#
bq.log.force_flush_all_logs();
logObj.force_flush();TypeScript
bq.log.force_flush_all_logs();
logObj.force_flush();Python
log.force_flush_all_logs()
log_obj.force_flush()Go
bq.Force_flush_all_logs()
logObj.Force_flush()Enable automatic buffer flush on abnormal exit (POSIX only):
C++
bq::log::enable_auto_crash_handle();Java / Kotlin
bq.log.enable_auto_crash_handle();C#
bq.log.enable_auto_crash_handle();TypeScript
bq.log.enable_auto_crash_handle();Python
log.enable_auto_crash_handle()Go
bq.Enable_auto_crash_handle()See also: Advanced Usage — Data protection on abnormal exit and log.recovery config.
Whether log.recovery=true is supported on the current platform:
C++
bq::recovery_capability capability = bq::log::get_recovery_capability();Native C ABI
bq_recovery_capability __api_get_recovery_capability(void);Java / Kotlin
bq.def.recovery_capability capability = bq.log.get_recovery_capability();C#
bq.def.recovery_capability capability = bq.log.get_recovery_capability();TypeScript
const capability = bq.log.get_recovery_capability();Python
capability = log.get_recovery_capability()Go
capability := bq.Get_recovery_capability()ready means recovery is available; any other value names what is missing.
What the linked library was actually built with, as bits — the same values it prints once at startup, so a bug report describes the binary instead of the caller's headers.
C++
uint32_t capabilities = bq::log::get_platform_capabilities();
if ((capabilities & bq::platform_capability::memory_map) == 0) {
// crash recovery is unavailable on this build
}Native C ABI
uint32_t __api_get_platform_capabilities(void);Java / Kotlin
int capabilities = bq.log.get_platform_capabilities();
boolean memory_map = (capabilities & bq.def.platform_capability.memory_map) != 0;C#
bq.def.platform_capability capabilities = bq.log.get_platform_capabilities();
bool memory_map = capabilities.HasFlag(bq.def.platform_capability.memory_map);TypeScript
const capabilities = bq.log.get_platform_capabilities();
const memory_map = (capabilities & bq.platform_capability.memory_map) !== 0;Python
capabilities = log.get_platform_capabilities()
memory_map = bool(capabilities & bq.defs.platform_capability.memory_map)Go
capabilities := bq.Get_platform_capabilities()
memory_map := capabilities&def.Platform_capability_memory_map != 0 // def = the bq/def package| Bit | Present means |
|---|---|
memory_map |
buffers are file-backed; crash recovery needs it |
multi_process |
several processes may share one log directory |
cross_process_file_lock |
a second process can be kept out of an instance |
crash_handler |
enable_auto_crash_handle() installs a handler |
backtrace |
the stack field of an entry carries a backtrace |
dirent |
expire_time_* and capacity_limit work |
thread_name |
threads report a name instead of thread_<id> |
default_base_dir |
a writable working directory was found; without it reset_base_dir is mandatory |
user_counter |
timestamps come from a platform counter instead of the system clock |
A console clears most of these on purpose; see Integration Guide — console platforms.
C++
static void bq::log::register_console_callback(bq::type_func_ptr_console_callback callback);
static void bq::log::unregister_console_callback(bq::type_func_ptr_console_callback callback);Java / Kotlin
bq.log.register_console_callback(callback);
bq.log.unregister_console_callback(callback);C#
bq.log.register_console_callback(callback);
bq.log.unregister_console_callback(callback);TypeScript
bq.log.register_console_callback(callback);
bq.log.unregister_console_callback(callback);Python
log.register_console_callback(callback)
log.unregister_console_callback(callback)Go
// callback: func(log_id uint64, category_idx int32, level def.Log_level, content string)
// (def is "github.com/Tencent/BqLog/go/v2/def")
bq.Register_console_callback(callback)
bq.Unregister_console_callback(callback)Note:
- Do not call any synchronous BqLog flush functions inside the callback — this will deadlock.
- Unity / Tuanjie / Unreal plugins already redirect ConsoleAppender to the editor log window automatically.
When direct native-thread callbacks are not suitable (C#, Java, IL2CPP, Node.js, Go), use buffered fetch mode:
// Enable buffering (disables register_console_callback and default console output)
bq::log::set_console_buffer_enable(true);
// Poll from your logic thread — MUST call continuously to avoid unbounded memory growth
bq::log::fetch_and_remove_console_buffer(on_console_callback);IL2CPP: Ensure callback is
static unsafewith[MonoPInvokeCallback(typeof(type_console_callback))].
Go:
bq.Set_console_buffer_enable(true)
// fetches one entry per call, returns false when the buffer is empty;
// keep fetching from one goroutine pinned with runtime.LockOSThread
for bq.Fetch_and_remove_console_buffer(func(log_id uint64, category_idx int32, level def.Log_level, content string) {
// ...
}) {
}C++
bool success = log_obj.reset_config(new_config);Java / Kotlin
boolean success = logObj.reset_config(newConfig);C#
bool success = logObj.reset_config(newConfig);TypeScript
const success = logObj.reset_config(newConfig);Python
success = log_obj.reset_config(new_config)Go
success := logObj.Reset_config(newConfig)Some fields (buffer_size, thread_mode) cannot be modified at runtime. See Configuration.
C++
log_obj.set_appender_enable("appender_name", false); // disable
log_obj.set_appender_enable("appender_name", true); // re-enableJava / Kotlin
logObj.set_appender_enable("appender_name", false); // disable
logObj.set_appender_enable("appender_name", true); // re-enableC#
logObj.set_appender_enable("appender_name", false);
logObj.set_appender_enable("appender_name", true);TypeScript
logObj.set_appender_enable("appender_name", false);
logObj.set_appender_enable("appender_name", true);Python
log_obj.set_appender_enable("appender_name", False)
log_obj.set_appender_enable("appender_name", True)Go
logObj.Set_appender_enable("appender_name", false) // disable
logObj.Set_appender_enable("appender_name", true) // re-enableCapture recent logs for anomaly reporting. Requires snapshot to be enabled in Configuration.
C++
bq::string snapshot = log_obj.take_snapshot("UTC+8");
// bq::string implicitly converts to std::stringJava / Kotlin
String snapshot = logObj.take_snapshot("UTC+8");C#
string snapshot = logObj.take_snapshot("UTC+8");TypeScript
const snapshot: string = logObj.take_snapshot("UTC+8");Python
snapshot = log_obj.take_snapshot("UTC+8")Go
snapshot := logObj.Take_snapshot("UTC+8")For decoding CompressedFileAppender logs at runtime:
bq::tools::log_decoder decoder("path/to/file.logcompr", "optional_private_key");
while (decoder.decode() == bq::appender_decode_result::success) {
auto& text = decoder.get_last_decoded_log_entry();
// process text...
}
// Or decode entire file at once:
bq::tools::log_decoder::decode_file("input.logcompr", "output.txt", "optional_key");Go:
decoder := bq.Decoder_create("path/to/file.logcompr", "optional_private_key")
if decoder.Is_valid() {
defer decoder.Destroy()
for text, ok := decoder.Decode(); ok; text, ok = decoder.Decode() {
// process text...
}
}
// Or decode entire file at once:
bq.Decode_file("input.logcompr", "output.txt", "optional_key")Download {os}_{arch}_tools_{version} from Releases, or install it with a package manager: brew install bqlog puts it on the PATH; vcpkg needs vcpkg install "bqlog[tools]" and installs it under installed/<triplet>/tools/bqlog/.
./BqLog_LogDecoder FileToDecode [-o OutputFile] [-k PrivateKeyFile]Note: Binary format may be incompatible between BqLog versions. Use the matching decoder version. See also: Advanced Usage — Log encryption and decryption
Controlled by log.thread_mode in Configuration.
Synchronous (sync) |
Asynchronous (async / independent) |
|
|---|---|---|
| Behavior | Log is processed immediately before function returns | Log is written to buffer, worker thread processes later |
| Performance | Lower — caller blocks until output completes | Higher — caller returns immediately |
| Thread Safety | Safe if parameters aren't modified during the call | Same guarantee; uses high-concurrency ring buffer internally |
BqLog copies all parameters to the internal ring buffer during the call. Once the log function returns, all data is safely stored internally. The worker thread never accesses the caller's stack.
The only unsafe scenario is other threads modifying a parameter while the log call is in progress:
static std::string global_str = "hello"; // modified by other threads concurrently
void thread_a() {
log_obj.info("param: {}", global_str); // undefined if global_str is modified during this call
}Rule: Ensure parameters are not modified during a single log call, regardless of sync/async mode.
| Name | Output | Readable | Performance | Size | Encryption |
|---|---|---|---|---|---|
| ConsoleAppender | Console / ADB Logcat | Yes | Low | - | No |
| TextFileAppender | UTF-8 text file | Yes | Low | Large | No |
| CompressedFileAppender | Binary compressed file | No | High | Small | Yes (RSA2048 + AES256) |
CompressedFileAppender is the recommended default for file output.
All build scripts are in /build:
/build
├── demo # Demo Build
├── lib # Native Static / Dynamic Library Build
├── test # Test Project Build
├── tools # Tools (LogDecoder, CategoryLogGenerator, etc.)
├── wrapper # Language Wrapper Projects (Java / C# etc.)
├── benchmark # Benchmark Project Build
└── plugin # Game Engine Plugins (Unity / Tuanjie / Unreal)
Required env vars:
ANDROID_NDK_ROOT— for Android buildsJAVA_HOME— for Java wrapper (most scripts enable it by default)
