LikesProgramLogging 是 LikesProgram 的轻量异步日志扩展包。它只依赖 LikesProgramCore,不依赖 Metrics、Threading、Net 或第三方日志库。
它适合二次开发时快速接入:
- 按级别过滤日志。
- 后台线程异步分发日志。
- 输出到控制台或文件。
- 支持文本格式和 JSON Lines。
- 支持 logger name、thread name、module、category、trace id、span id、request id 和自定义上下文字段。
- 支持按 Sink 失败重试、退避和有界重试队列。
- 支持文件轮转、保留策略和可选多进程同文件写入。
- 支持运行时替换 Sink、开放式
LoggerConfig配置和导出诊断信息。
Logging 默认不构建。使用前需要在配置 LikesProgram 时显式开启:
cd C:\Users\TX2\Desktop\LikesProgramProjects\LikesProgram
cmake -S . -B build-logging -DLIKESPROGRAM_BUILD_LOGGING=ON
cmake --build build-logging --config Debug
ctest --test-dir build-logging -R LikesProgramLoggingTests --output-on-failure -C Debug在外部项目中链接:
find_package(LikesProgram CONFIG REQUIRED)
add_executable(MyApp main.cpp)
target_link_libraries(MyApp PRIVATE LikesProgram::Logging)LikesProgram::Logging 会传递依赖 LikesProgram::Core。你也可以为了阅读清楚显式写上:
target_link_libraries(MyApp PRIVATE
LikesProgram::Core
LikesProgram::Logging
)#include <LikesProgram/Logging/Logger.hpp>
#include <LikesProgram/Logging/sinks/ConsoleSink.hpp>
#include <chrono>
#include <source_location>
int main() {
auto& logger = LikesProgram::Log::Logger::Instance(true, true);
logger.SetLevel(LikesProgram::Log::Level::Info);
logger.SetLoggerName(u"demo");
logger.AddSink(LikesProgram::Log::ConsoleSink::CreateSink());
logger.Log(LikesProgram::Log::Level::Info, std::source_location::current(),
u"service {} started on port {}", u"orders", 8080);
logger.Flush(std::chrono::seconds(5));
logger.Shutdown();
return 0;
}解释:
Instance(true, true)获取全局 Logger,并自动启动后台线程,第二个true表示输出 source location 调试信息。SetLevel(Info)表示低于 Info 的 Trace/Debug 会被过滤。AddSink(ConsoleSink::CreateSink())添加控制台输出。Log(...)是异步入队,真正写入由后台线程完成。Flush(...)等待队列处理完并刷新所有 Sink。Shutdown()停止后台线程,默认清空 Sink 列表。
优先使用聚合头:
#include <LikesProgram/Logging/Logging.hpp>也可以按需包含叶子头:
#include <LikesProgram/Logging/LoggerType.hpp>
#include <LikesProgram/Logging/Logger.hpp>
#include <LikesProgram/Logging/LoggerConfig.hpp>
#include <LikesProgram/Logging/sinks/Sink.hpp>
#include <LikesProgram/Logging/sinks/ConsoleSink.hpp>
#include <LikesProgram/Logging/sinks/FileSink.hpp>这些 API 可用于启动日志、诊断输出、插件加载检查等场景。
#include <LikesProgram/Logging/Logging.hpp>
const char* name = LikesProgram::Logging::PackageName(); // "LikesProgramLogging"
const char* version = LikesProgram::Logging::PackageVersion(); // 跟随 LikesProgram 版本
bool ok = LikesProgram::Logging::PackageAvailable(); // true 表示已链接到当前进程返回值是 C 字符串或 bool,不需要调用方释放内存。
级别从低到高:
LikesProgram::Log::Level::Trace;
LikesProgram::Log::Level::Debug;
LikesProgram::Log::Level::Info;
LikesProgram::Log::Level::Warn;
LikesProgram::Log::Level::Error;
LikesProgram::Log::Level::Fatal;设置过滤级别:
logger.SetLevel(LikesProgram::Log::Level::Warn);此时 Trace、Debug、Info 都不会入队。被过滤的日志不会格式化参数,因此禁用级别下的昂贵参数也不会被拷贝或格式化。
级别和文本互转:
using namespace LikesProgram::Log;
LikesProgram::String text = LevelToString(Level::Warn); // "Warn"
Level level = StringToLevel(u"error"); // Level::Error
Level fallback = StringToLevel(u"missing", Level::Info);StringToLevel 不区分大小写,无法识别时返回 defaultLevel,默认是 Trace。
Logger 是进程内全局单例:
auto& logger1 = LikesProgram::Log::Logger::Instance();
auto& logger2 = LikesProgram::Log::Logger::Instance(true);
auto& logger3 = LikesProgram::Log::Logger::Instance(true, true);兼容别名:
auto& logger = LikesProgram::Logger::Instance(true); // 等价 LikesProgram::Log::Logger::Instance(true)区别:
Instance():只获取单例,不自动启动。Instance(true):获取单例,如果还没启动或已关闭,则启动。Instance(true, debug):同时设置是否输出调用点调试信息。
手动启动:
auto& logger = LikesProgram::Log::Logger::Instance();
logger.AddSink(LikesProgram::Log::ConsoleSink::CreateSink());
bool started = logger.Start();Start() 可重复调用。已启动时返回 true。
关闭:
logger.Shutdown(); // 等待 drain,清空 Sink
logger.Shutdown(false); // 等待 drain,不清空 Sink
bool ok = logger.Shutdown(std::chrono::seconds(5), true);带超时的 Shutdown(timeout, clearSink):
- 会请求停止后台线程。
- 会调用
Flush(timeout)等待队列清空。 - 超时返回
false。 - 如果短超时失败,可以释放阻塞资源后再次调用更长超时的
Shutdown。 clearSink=true时成功关闭后清空 Sink 列表。
推荐直接调用 Logger::Log:
logger.Log(LikesProgram::Log::Level::Info, std::source_location::current(),
u"user {} paid {}", userId, amount);格式串使用 LikesProgram::String::Format 语法。更多语法见 Core 使用手册中的 String::Format 章节。
无参数日志不会走格式化:
logger.Log(LikesProgram::Log::Level::Info, std::source_location::current(),
u"service started");便捷宏:
LogTrace(u"trace {}", id);
LogDebug(u"debug {}", id);
LogInfo(u"hello {}", name);
LogWarn(u"slow request {}", ms);
LogError(u"failed: {}", reason);
LogFatal(u"fatal: {}", reason);宏会使用全局 Logger::Instance() 和 std::source_location::current()。使用宏前先配置 Sink 并启动 Logger,这样日志才会进入正常输出流程。
如果 Logger 未启动,写日志会增加 droppedMessages,不会自动启动。
设置 Sink 输出编码:
logger.SetEncoding(LikesProgram::String::Encoding::UTF8);
logger.SetEncoding(LikesProgram::String::Encoding::GBK);默认是 UTF-8。编码快照会写入每条 Message,由 Sink 在输出时使用。文件 Sink 会按该编码写入文件。
LoggerOptions 控制异步队列和输出格式:
LikesProgram::Log::LoggerOptions options;
options.maxQueueSize = 65536;
options.overflowPolicy = LikesProgram::Log::QueueOverflowPolicy::Block;
options.enqueueTimeout = std::chrono::milliseconds(100);
options.outputFormat = LikesProgram::Log::LogOutputFormat::Text;
logger.Configure(options);字段说明:
| 字段 | 默认值 | 说明 |
|---|---|---|
maxQueueSize |
65536 |
异步队列上限,0 表示不限制 |
overflowPolicy |
Block |
队列满时的策略 |
enqueueTimeout |
100ms |
Block 策略等待容量的最长时间 |
outputFormat |
Text |
日志输出格式,Text 或 JsonLines |
背压策略:
options.overflowPolicy = LikesProgram::Log::QueueOverflowPolicy::Block;Block:
- 队列满时写日志的线程等待容量。
enqueueTimeout > 0时等待超时会丢弃本条日志,enqueueTimeouts和droppedMessages增加。enqueueTimeout <= 0时一直等到有容量或 Logger 停止。
options.overflowPolicy = LikesProgram::Log::QueueOverflowPolicy::DropNewest;DropNewest:
- 队列满时丢弃当前新日志。
droppedMessages增加。
options.overflowPolicy = LikesProgram::Log::QueueOverflowPolicy::DropOldest;DropOldest:
- 队列满时先丢弃队列中最旧的一条,再放入当前新日志。
droppedMessages增加。
添加 Sink:
logger.AddSink(LikesProgram::Log::ConsoleSink::CreateSink());一次性替换 Sink:
std::vector<std::shared_ptr<LikesProgram::Log::Sink>> sinks;
sinks.push_back(LikesProgram::Log::ConsoleSink::CreateSink());
logger.SetSinks(sinks);清空 Sink:
logger.ClearSinks();行为说明:
AddSink(nullptr)会被忽略。SetSinks会过滤空指针,然后原子替换当前列表。ClearSinks后日志仍可入队和统计,但不会输出到任何地方。- 后台分发线程调用 Sink 时会持有共享锁;替换 Sink 会等待当前分发批次结束后生效。
- 某个 Sink 的
Write或Flush抛异常不会影响其他 Sink,失败会计入sinkWriteFailures并写到std::cerr。
LoggerConfig 是 Logging 的原生运行时配置模型。它不依赖 LikesProgramConfig,也不使用封闭的 SinkKind 枚举;每个 SinkConfig 都直接包装一个 std::shared_ptr<Sink>,因此可以接入内置 Sink 或业务自定义 Sink。
#include <LikesProgram/Logging/Logging.hpp>
#include <chrono>
#include <stdexcept>
LikesProgram::Log::LoggerConfig config;
config.level = LikesProgram::Log::Level::Info;
config.loggerName = u"orders-service";
config.encoding = LikesProgram::String::Encoding::UTF8;
config.debug = true;
config.options.maxQueueSize = 65536;
config.options.overflowPolicy = LikesProgram::Log::QueueOverflowPolicy::Block;
config.options.enqueueTimeout = std::chrono::milliseconds(100);
config.options.outputFormat = LikesProgram::Log::LogOutputFormat::JsonLines;
LikesProgram::Log::ConsoleSinkConfig console;
console.minLevel = LikesProgram::Log::Level::Warn;
auto consoleSink = LikesProgram::Log::MakeConsoleSinkConfig(console);
LikesProgram::Log::FileSinkConfig file;
file.name = u"service-file";
file.path = u"./logs";
file.filename = u"orders.log";
file.minLevel = LikesProgram::Log::Level::Info;
file.fileOptions.maxFileSizeMB = 64;
file.fileOptions.retentionDays = 14;
file.retry.enabled = true;
file.retry.maxAttempts = 3;
file.retry.maxQueueSize = 1024;
file.retry.initialBackoff = std::chrono::milliseconds(50);
auto fileSink = LikesProgram::Log::MakeFileSinkConfig(file);
if (!consoleSink.IsOk()) {
throw std::runtime_error(consoleSink.GetStatus().ToString().ToStdString());
}
if (!fileSink.IsOk()) {
throw std::runtime_error(fileSink.GetStatus().ToString().ToStdString());
}
config.sinks.push_back(consoleSink.Value());
config.sinks.push_back(fileSink.Value());
auto& logger = LikesProgram::Log::Logger::Instance(false, false);
auto status = logger.ApplyConfig(config);
if (!status.IsOk()) {
throw std::runtime_error(status.ToString().ToStdString());
}
if (!logger.Start()) {
throw std::runtime_error("failed to start logger");
}ValidateLoggerConfig(config) 可以在应用前单独校验。Logger::ApplyConfig 会先校验,失败时保留当前运行配置。
EffectiveConfig() 可以导出当前生效配置快照:
auto effective = logger.EffectiveConfig();
if (effective.IsOk()) {
auto currentLevel = effective.Value().level;
}LikesProgramLogging 代码本身不依赖 LikesProgramConfig。如果你的应用同时启用了 Config 包,可以在应用层读取配置值树,再手动映射到 LoggerConfig。
示例 TOML:
[logging]
level = "info"
logger = "orders-service"
debug = true
format = "json"
encoding = "utf8"
[logging.queue]
max_size = 65536
overflow = "block"
enqueue_timeout_ms = 100
[logging.console]
enabled = true
min_level = "warn"
[logging.file]
enabled = true
path = "./logs"
filename = "orders.log"
min_level = "info"
max_file_size_mb = 64
retention_days = 14
max_retained_files = 128
multi_process = false
[logging.file.retry]
enabled = true
max_attempts = 3
max_queue_size = 1024
initial_backoff_ms = 50
max_backoff_ms = 500映射代码:
#include <LikesProgram/Config/Config.hpp>
#include <LikesProgram/Logging/Logging.hpp>
#include <chrono>
#include <stdexcept>
namespace {
LikesProgram::Log::Level ReadLevel(
const LikesProgram::Config::Configuration& config,
const LikesProgram::String& key,
LikesProgram::Log::Level fallback) {
return LikesProgram::Log::StringToLevel(config.GetString(key, LikesProgram::Log::LevelToString(fallback)),
fallback);
}
LikesProgram::Log::LogOutputFormat ReadFormat(
const LikesProgram::Config::Configuration& config,
const LikesProgram::String& key) {
auto text = config.GetString(key, u"text").ToLower();
return text == u"json" || text == u"json_lines" || text == u"jsonlines"
? LikesProgram::Log::LogOutputFormat::JsonLines
: LikesProgram::Log::LogOutputFormat::Text;
}
LikesProgram::String::Encoding ReadEncoding(
const LikesProgram::Config::Configuration& config,
const LikesProgram::String& key) {
auto text = config.GetString(key, u"utf8").ToLower();
if (text == u"gbk") return LikesProgram::String::Encoding::GBK;
if (text == u"utf16" || text == u"utf-16") return LikesProgram::String::Encoding::UTF16;
if (text == u"utf32" || text == u"utf-32") return LikesProgram::String::Encoding::UTF32;
return LikesProgram::String::Encoding::UTF8;
}
LikesProgram::Log::QueueOverflowPolicy ReadOverflow(
const LikesProgram::Config::Configuration& config,
const LikesProgram::String& key) {
auto text = config.GetString(key, u"block").ToLower();
if (text == u"drop_newest" || text == u"drop-newest" || text == u"dropnewest") {
return LikesProgram::Log::QueueOverflowPolicy::DropNewest;
}
if (text == u"drop_oldest" || text == u"drop-oldest" || text == u"dropoldest") {
return LikesProgram::Log::QueueOverflowPolicy::DropOldest;
}
return LikesProgram::Log::QueueOverflowPolicy::Block;
}
LikesProgram::Log::RetryConfig ReadRetry(
const LikesProgram::Config::Configuration& config,
const LikesProgram::String& prefix) {
LikesProgram::Log::RetryConfig retry;
retry.enabled = config.GetBool(prefix + u".enabled", false);
retry.maxAttempts = static_cast<uint32_t>(config.GetInt64(prefix + u".max_attempts", 0));
retry.maxQueueSize = static_cast<size_t>(config.GetInt64(prefix + u".max_queue_size", 4096));
retry.initialBackoff = std::chrono::milliseconds(
config.GetInt64(prefix + u".initial_backoff_ms", 0));
retry.maxBackoff = std::chrono::milliseconds(
config.GetInt64(prefix + u".max_backoff_ms", 0));
return retry;
}
LikesProgram::Log::LoggerConfig BuildLoggerConfig(
const LikesProgram::Config::Configuration& config) {
LikesProgram::Log::LoggerConfig loggerConfig;
loggerConfig.level = ReadLevel(config, u"logging.level", LikesProgram::Log::Level::Info);
loggerConfig.loggerName = config.GetString(u"logging.logger", u"service");
loggerConfig.debug = config.GetBool(u"logging.debug", false);
loggerConfig.encoding = ReadEncoding(config, u"logging.encoding");
loggerConfig.options.maxQueueSize = static_cast<size_t>(
config.GetInt64(u"logging.queue.max_size", 65536));
loggerConfig.options.overflowPolicy = ReadOverflow(config, u"logging.queue.overflow");
loggerConfig.options.enqueueTimeout = std::chrono::milliseconds(
config.GetInt64(u"logging.queue.enqueue_timeout_ms", 100));
loggerConfig.options.outputFormat = ReadFormat(config, u"logging.format");
if (config.GetBool(u"logging.console.enabled", true)) {
LikesProgram::Log::ConsoleSinkConfig console;
console.minLevel = ReadLevel(config, u"logging.console.min_level", loggerConfig.level);
console.overrideOutputFormat = true;
console.outputFormat = loggerConfig.options.outputFormat;
auto sink = LikesProgram::Log::MakeConsoleSinkConfig(console);
if (!sink.IsOk()) {
throw std::runtime_error(sink.GetStatus().ToString().ToStdString());
}
loggerConfig.sinks.push_back(sink.Value());
}
if (config.GetBool(u"logging.file.enabled", false)) {
LikesProgram::Log::FileSinkConfig file;
file.path = config.GetString(u"logging.file.path", u"./logs");
file.filename = config.GetString(u"logging.file.filename", u"service.log");
file.minLevel = ReadLevel(config, u"logging.file.min_level", loggerConfig.level);
file.overrideOutputFormat = true;
file.outputFormat = loggerConfig.options.outputFormat;
file.fileOptions.maxFileSizeMB = static_cast<size_t>(
config.GetInt64(u"logging.file.max_file_size_mb", 30));
file.fileOptions.retentionDays = static_cast<uint32_t>(
config.GetInt64(u"logging.file.retention_days", 0));
file.fileOptions.maxRetainedFiles = static_cast<size_t>(
config.GetInt64(u"logging.file.max_retained_files", 0));
file.multiProcess.enabled = config.GetBool(u"logging.file.multi_process", false);
file.retry = ReadRetry(config, u"logging.file.retry");
auto sink = LikesProgram::Log::MakeFileSinkConfig(file);
if (!sink.IsOk()) {
throw std::runtime_error(sink.GetStatus().ToString().ToStdString());
}
loggerConfig.sinks.push_back(sink.Value());
}
return loggerConfig;
}
}
void ConfigureLoggingFromToml(const LikesProgram::String& tomlText) {
auto config = LikesProgram::Config::Configuration::FromToml(tomlText);
auto loggerConfig = BuildLoggerConfig(config);
auto& logger = LikesProgram::Log::Logger::Instance(false, false);
auto status = logger.ApplyConfig(loggerConfig);
if (!status.IsOk()) {
throw std::runtime_error(status.ToString().ToStdString());
}
if (!logger.Start()) {
throw std::runtime_error("failed to start logger");
}
}这个例子把格式解析留在应用层,因此你可以按业务需要换成 JSON、YAML、环境变量或命令行参数,而 Logging 包仍保持只依赖 Core。
控制台 Sink 输出到当前进程标准输出。
#include <LikesProgram/Logging/sinks/ConsoleSink.hpp>
auto sink = LikesProgram::Log::ConsoleSink::CreateSink();
logger.AddSink(sink);也可以直接构造:
LikesProgram::Log::ConsoleSink sink;ConsoleSink::Write 会按日志级别应用控制台颜色,然后写出统一格式化后的日志文本。
文件 Sink 按日期目录写文件,并支持大小轮转和保留策略。
最简单用法:
#include <LikesProgram/Logging/sinks/FileSink.hpp>
auto sink = LikesProgram::Log::FileSink::CreateSink(u"./logs", u"service.log");
logger.AddSink(sink);参数为空时:
path为空使用./logsfilename为空使用Logger.log
输出目录形态:
logs/
2026-06-29/
service.log
1_service.log
2_service.log
完整选项:
LikesProgram::Log::FileSinkOptions options;
options.maxFileSizeMB = 64;
options.maxFileSizeBytes = 0;
options.retentionDays = 14;
options.maxRetainedFiles = 128;
options.maxTotalSizeBytes = 1024ull * 1024ull * 1024ull;
auto sink = LikesProgram::Log::FileSink::CreateSink(u"./logs", u"service.log", options);
logger.AddSink(sink);字段说明:
| 字段 | 默认值 | 说明 |
|---|---|---|
maxFileSizeMB |
30 |
单文件最大 MB,0 表示不按 MB 配置大小轮转 |
maxFileSizeBytes |
0 |
精确字节上限,非 0 时优先于 maxFileSizeMB |
retentionDays |
0 |
按文件修改时间保留天数,0 表示不按天清理 |
maxRetainedFiles |
0 |
最多保留托管日志文件数,0 表示不限制 |
maxTotalSizeBytes |
0 |
托管日志文件总字节上限,0 表示不限制 |
运行中更新策略:
auto fileSink = std::make_shared<LikesProgram::Log::FileSink>(
u"./logs", u"service.log", options);
logger.AddSink(fileSink);
options.maxRetainedFiles = 32;
fileSink->Configure(options);
auto current = fileSink->Options();轮转规则:
- 日期变化会重新打开当天目录下的日志文件。
- 当前文件大小达到上限后,下一次写入前打开
1_filename、2_filename等轮转文件。 - 保留策略只管理当前
filename命名规则匹配的文件,不删除其他文件。 - 清理时不会删除当前正在打开的文件。
错误边界:
- 日志根路径无法创建目录、被普通文件占用或文件无法打开时,构造
FileSink会抛异常。 - 写入和 flush 失败时,异常会被 Logger 捕获并计入
sinkWriteFailures。 FileSink析构时会尝试 flush,但析构不会向外抛异常。
继承 Sink 并实现 Write:
#include <LikesProgram/Logging/sinks/Sink.hpp>
#include <mutex>
#include <vector>
class MemorySink final : public LikesProgram::Log::Sink {
public:
MemorySink() : Sink(u"MemorySink") {}
void Write(const LikesProgram::Log::Message& message) override {
auto line = FormatLogMessage(message);
std::lock_guard<std::mutex> lock(mutex_);
lines_.push_back(line);
}
void Flush() override {
// 如果有缓冲,在这里提交。没有缓冲可以不重写。
}
private:
std::mutex mutex_;
std::vector<LikesProgram::String> lines_;
};要点:
Sink构造函数的名称会出现在文本日志的[SinkName]字段和 JSON Lines 的"sink"字段。FormatLogMessage(message)可复用内置文本/JSON 格式。Write可能被后台线程调用;如果 Sink 自己保存共享数据,需要在 Sink 里加锁。Flush默认无操作。
设置全局 Logger 名称:
logger.SetLoggerName(u"orders-service");设置当前线程上下文:
LikesProgram::Log::Logger::SetThreadName(u"worker-1");
LikesProgram::Log::Logger::SetModule(u"orders");
LikesProgram::Log::Logger::SetCategory(u"checkout");
LikesProgram::Log::Logger::SetTraceId(u"trace-123");
LikesProgram::Log::Logger::SetSpanId(u"span-456");
LikesProgram::Log::Logger::SetRequestId(u"req-789");
LikesProgram::Log::Logger::SetContextField(u"tenant", u"blue");删除或清空:
LikesProgram::Log::Logger::RemoveContextField(u"tenant");
LikesProgram::Log::Logger::ClearThreadName();
LikesProgram::Log::Logger::ClearContext();ClearContext() 会清空 module、category、traceId、spanId、requestId 和自定义字段,但不会清空 thread name。清 thread name 要调用 ClearThreadName()。
上下文是线程局部的:
- 一个线程设置的 module/traceId 不会影响另一个线程。
- 写入日志时会复制上下文快照到
Message。 - 后续修改上下文不会改变已经入队的日志。
临时上下文:
LikesProgram::Log::Logger::SetModule(u"outer");
LikesProgram::Log::Logger::SetContextField(u"tenant", u"blue");
{
LikesProgram::Log::LoggerContextScope scope(u"tenant", u"green");
scope.SetModule(u"inner");
scope.SetTraceId(u"trace-inner");
scope.SetContextField(u"operation", u"create");
logger.Log(LikesProgram::Log::Level::Info, std::source_location::current(),
u"inside scope");
}
logger.Log(LikesProgram::Log::Level::Info, std::source_location::current(),
u"outside scope");离开作用域后会恢复构造 LoggerContextScope 前的线程上下文。
默认输出格式是 Text:
LikesProgram::Log::LoggerOptions options;
options.outputFormat = LikesProgram::Log::LogOutputFormat::Text;
logger.Configure(options);文本日志形态大致为:
[2026-06-29 10:20:30.123] [T:worker-a] [ConsoleSink] [Info] [Logger:orders] [Module:checkout] [TraceId:trace-1] [Context.tenant:blue] [Function:main] (main.cpp:12) message
字段说明:
- 时间:本地时间,毫秒精度。
T:线程展示名;未设置时输出底层 thread id。- Sink 名称:如
ConsoleSink、FileSink或自定义名称。 - Level:
Trace/Debug/Info/Warn/Error/Fatal。 - Logger/Module/Category/TraceId/SpanId/RequestId:非空时才输出。
- 自定义字段:显示为
[Context.<key>:<value>]。 - Debug 开启时输出
[Function:...] (file:line)。
开启 JSON Lines:
LikesProgram::Log::LoggerOptions options;
options.outputFormat = LikesProgram::Log::LogOutputFormat::JsonLines;
logger.Configure(options);输出是一行一个 JSON 对象:
{"timestamp":"2026-06-29T10:20:30.123+0800","level":"Warn","logger":"orders","sink":"ConsoleSink","thread":"worker-a","thread_id":"1234","process_id":100,"module":"checkout","request_id":"req-42","message":"slow request 7","context":{"tenant":"blue"}}说明:
- 这是日志场景的固定字段 writer,不是通用 JSON 库。
- 字符串使用 Core 的
String::EscapeJson转义。 - 自定义字段放在
context子对象中。 - 如果自定义字段名和内置字段同名,例如
module,它仍放在context.module,不会覆盖顶层"module"。 - 空字符串字段通常不输出。
自定义 Sink 会收到 Message:
struct Message {
Level level;
String msg;
String file;
int line;
std::thread::id tid;
String threadName;
std::chrono::system_clock::time_point timestamp;
String func;
Level minLevel;
String::Encoding encoding;
bool debug;
LogOutputFormat outputFormat;
String loggerName;
String module;
String category;
String traceId;
String spanId;
String requestId;
uint64_t processId;
std::vector<LogContextField> contextFields;
};二开自定义 Sink 时一般只需要 level、msg、timestamp、loggerName、module、traceId、contextFields,或直接调用 FormatLogMessage(message)。
等待队列清空并刷新 Sink:
bool drained = logger.Flush(std::chrono::seconds(5));Flush 行为:
- 等待队列为空且没有正在写 Sink 的消息。
- 成功 drain 后调用所有 Sink 的
Flush()。 - Sink 的
Flush()抛异常不会让Logger::Flush返回 false,但会计入sinkWriteFailures。 - 等待超时返回
false,并计入flushTimeouts。
运行统计:
auto stats = logger.Stats();
stats.acceptedMessages;
stats.processedMessages;
stats.droppedMessages;
stats.enqueueTimeouts;
stats.sinkWriteFailures;
stats.flushTimeouts;
stats.currentQueueSize;
stats.queueHighWatermark;
stats.sinkCount;
stats.running;配置和统计诊断:
auto diagnostics = logger.Diagnostics();
diagnostics.options;
diagnostics.stats;
diagnostics.minLevel;
diagnostics.encoding;
diagnostics.loggerName;
diagnostics.debug;导出诊断:
auto text = logger.ExportDiagnostics();
auto json = logger.ExportDiagnostics(LikesProgram::Log::LogOutputFormat::JsonLines);文本以 LoggerDiagnostics{...} 开头,适合人工排查。JSON Lines 形式会输出固定字段对象,适合机器采集。
服务启动时:
auto& logger = LikesProgram::Log::Logger::Instance(false, false);
logger.Shutdown(true); // 如果上一次测试或热重载留下状态,先清掉
LikesProgram::Log::LoggerOptions options;
options.maxQueueSize = 65536;
options.overflowPolicy = LikesProgram::Log::QueueOverflowPolicy::Block;
options.enqueueTimeout = std::chrono::milliseconds(100);
options.outputFormat = LikesProgram::Log::LogOutputFormat::Text;
logger.Configure(options);
logger.SetEncoding(LikesProgram::String::Encoding::UTF8);
logger.SetLevel(LikesProgram::Log::Level::Info);
logger.SetLoggerName(u"my-service");
logger.SetSinks({
LikesProgram::Log::ConsoleSink::CreateSink(),
LikesProgram::Log::FileSink::CreateSink(u"./logs", u"my-service.log")
});
logger.Start();服务退出时:
bool drained = logger.Shutdown(std::chrono::seconds(5), true);
if (!drained) {
// 这里可以输出到 stderr 或上报退出异常
}Logger::Instance() 只获取单例,不启动后台线程。需要自动启动时使用 Instance(true),或者获取后手动调用 Start()。
没有 Sink 时,日志不会出现在控制台或文件中;如果 Logger 已启动,这些日志仍会进入队列并计入统计。
使用 LogInfo、LogWarn 等宏时,也需要提前配置并启动全局 Logger。
Log(...) 是异步入队。程序退出前调用 Flush() 或 Shutdown(),可以等待队列中的日志写完。
Shutdown() 默认清空 Sink。需要保留 Sink 列表并稍后再次 Start() 时,使用 Shutdown(false)。
上下文是线程局部的。在线程池中复用线程时,推荐在任务范围内使用 LoggerContextScope,或在任务结束时主动 ClearContext()。
FileSink 的保留策略只管理符合当前 filename 命名规则的日志文件,其他文件会保留。
JSON Lines 是日志输出格式,适合日志采集;复杂对象序列化仍建议使用专门的 JSON 库。
examples/LoggingExample.cpp:包身份、Logger 启停、控制台 Sink 基础用法。tests/LoggingPackageTests.cpp:可参考的行为样例,包含背压、异常隔离、上下文、JSON Lines、文件轮转、失败重试、多进程文件写入、LoggerConfig 和诊断。benchmarks/LoggingBenchmark.cpp:Release 场景下的日志开销观察样例。