模板集合
博客
OmniStacksStaticModules
2.0.0
Toggle main menu visibility
Loading...
Searching...
No Matches
InterfaceLog.cppm
Go to the documentation of this file.
1
19
export
module
InterfaceLog
;
20
21
import
std;
22
import
bq.log;
23
24
export
namespace
interface_log
{
25
26
// ---- 公共常量与状态 -------------------------------------------------------
27
29
inline
constexpr
std::size_t
kMaxLines
= 1000;
30
32
inline
constexpr
std::string_view
kDeveloperWebsite
=
"https://suchaharcan.github.io/"
;
33
35
enum class
ApiStatus
{
36
ok,
37
failed,
38
};
39
41
enum class
LogError
{
42
invalid_path
,
43
backend_unavailable
,
44
};
45
50
struct
ApiName
{
51
std::string
value
;
52
explicit
ApiName
(std::string name) :
value
(std::move(name)) {}
53
};
54
59
struct
LogText
{
60
std::string
value
;
61
explicit
LogText
(std::string text) :
value
(std::move(text)) {}
62
};
63
70
std::expected<std::monostate, LogError>
init
(
71
const
std::filesystem::path&
log_path
=
"log/interface.log"
);
72
79
std::size_t
cleanup_old_logs
(
const
std::filesystem::path&
log_path
,
int
months_back = 3);
80
82
void
set_enabled
(
bool
enabled
);
83
85
bool
enabled
();
86
96
void
record
(ApiName api, LogText input, LogText output,
ApiStatus
status);
97
102
std::vector<std::string>
recent
();
103
105
std::size_t
count
();
106
108
void
clear
();
109
111
std::filesystem::path
log_path
();
112
118
template
<std::invocable<std::
string
_view> Visitor>
119
void
for_each_recent
(Visitor&& visitor);
120
122
std::string_view
status_name
(
ApiStatus
status)
noexcept
;
123
125
std::string_view
log_error_message
(
LogError
error)
noexcept
;
126
127
}
// namespace interface_log
128
129
// ---- 非导出内部状态 -------------------------------------------------------
130
// 内部状态放在非导出的模块作用域(module linkage,非 TU-local),
131
// 保证导出模板 for_each_recent 在其他 TU 实例化时合法可达。
132
// 所有可变状态都由 g_mutex 串行化;bqlog 只是可选落盘后端,内存环形缓冲
133
// 在 bqlog 未初始化或运行期异常时仍能继续工作。
134
namespace
interface_log
{
135
136
std::mutex
g_mutex
;
137
std::deque<std::string>
g_lines
;
138
std::optional<bq::log>
g_logger
;
139
std::filesystem::path
g_log_path
;
140
bool
g_initialized
=
false
;
141
bool
g_recording_enabled
=
true
;
142
147
std::string
now_string
() {
148
return
std::format(
"{:%Y-%m-%d %H:%M:%S}"
, std::chrono::system_clock::now());
149
}
150
151
}
// namespace interface_log
152
153
namespace
interface_log
{
154
155
std::expected<std::monostate, LogError>
init
(
const
std::filesystem::path& log_path_value) {
156
// 空路径属于可恢复配置错误;调用方仍可依赖内存日志继续运行。
157
if
(log_path_value.empty()) {
158
return
std::unexpected(
LogError::invalid_path
);
159
}
160
161
std::lock_guard lock(
g_mutex
);
162
g_recording_enabled
=
true
;
163
// 同一路径重复初始化保持幂等,避免每个测试/Fixture 重建文件后端。
164
if
(
g_initialized
&&
g_log_path
== log_path_value) {
165
return
{};
166
}
167
168
try
{
169
// 先创建父目录,再交给 bqlog 创建带日期序号的文本文件。
170
if
(log_path_value.has_parent_path()) {
171
std::filesystem::create_directories(log_path_value.parent_path());
172
}
173
174
// 单文件上限 1 MiB;超过后由 bqlog 滚动,项目启动时再做三个月清理。
175
const
std::string config = std::format(
176
"appenders_config.appender_interface.type=text_file\n"
177
"appenders_config.appender_interface.file_name={}\n"
178
"appenders_config.appender_interface.levels=[all]\n"
179
"appenders_config.appender_interface.max_file_size=1048576\n"
,
180
log_path_value.string());
181
182
// 日志名附加路径哈希,避免并行测试进程共享同名日志对象。
183
const
std::string log_name =
184
std::format(
"stacks_tools_interface_{}"
, std::hash<std::string>{}(log_path_value.string()));
185
g_logger
.emplace(bq::log::create_log(log_name, config));
186
g_log_path
= log_path_value;
187
g_initialized
=
true
;
188
// 水印属于后端初始化记录,便于从用户日志直接识别插件来源。
189
g_logger
->info(
"Omni-Stacks developer={} interface log initialized path:{}"
,
kDeveloperWebsite
,
190
log_path_value.string());
191
// 立即 flush,保证 CLI 崩溃或 Alfred 终止进程时仍能看到最近一次调用。
192
g_logger
->force_flush();
193
return
{};
194
}
catch
(
const
std::exception&) {
195
// bqlog 失败不销毁进程内环形缓冲;只记录后端不可用状态。
196
g_initialized
=
false
;
197
g_log_path
= log_path_value;
198
return
std::unexpected(
LogError::backend_unavailable
);
199
}
200
}
201
202
std::size_t
cleanup_old_logs
(
const
std::filesystem::path& log_path_value,
int
months_back) {
203
// 负保留期和空路径视为禁用清理,不做任何删除。
204
if
(log_path_value.empty() || months_back < 0) {
205
return
0;
206
}
207
const
std::filesystem::path directory =
208
log_path_value.has_parent_path() ? log_path_value.parent_path() : std::filesystem::path(
"."
);
209
std::error_code error;
210
if
(!std::filesystem::is_directory(directory, error)) {
211
return
0;
212
}
213
214
// 以“当天”而非精确时刻作为截止点;日志按自然日命名,便于稳定比较。
215
const
std::chrono::sys_days cutoff = std::chrono::floor<std::chrono::days>(
216
std::chrono::system_clock::now() - std::chrono::months{months_back});
217
const
std::chrono::year_month_day cutoff_date{cutoff};
218
219
// 将 YYYY-MM-DD 压缩成 YYYYMMDD 无符号整数,与文件名中的日期段同构。
220
const
unsigned
cutoff_number =
221
static_cast<
unsigned
>
(
static_cast<
int
>
(cutoff_date.year())) * 10000U +
222
static_cast<
unsigned
>
(cutoff_date.month()) * 100U +
static_cast<
unsigned
>
(cutoff_date.day());
223
224
// 只匹配本插件管理的命名空间,绝不按目录批量删除其他 Alfred 日志。
225
const
std::regex dated_log(R
"((?:interface\.log_|trace-)([0-9]{8}))");
226
227
std::size_t removed = 0;
228
for
(
const
auto
& entry : std::filesystem::directory_iterator(directory, error)) {
229
// 遍历错误时跳过当前项并继续,清理属于尽力而为的维护操作。
230
if
(error || !entry.is_regular_file(error)) {
231
continue
;
232
}
233
const
std::string filename = entry.path().filename().string();
234
std::smatch match;
235
bool
expired =
false
;
236
if
(std::regex_search(filename, match, dated_log) && match.size() > 1) {
237
const
auto
stamp =
static_cast<
unsigned
>
(std::stoul(match[1].str()));
238
expired = stamp < cutoff_number;
239
}
else
if
(filename ==
"trace.log"
|| filename ==
"trace.log.binary-stderr"
) {
240
// 无日期后缀的旧格式 trace 按文件 mtime 判断;统一转换到 system_clock。
241
const
auto
write_time = entry.last_write_time(error);
242
if
(!error) {
243
expired = std::filesystem::file_time_type::clock::to_sys(write_time) < cutoff;
244
}
245
}
246
if
(expired) {
247
std::filesystem::remove(entry.path(), error);
248
if
(!error) {
249
++removed;
250
}
251
}
252
error.clear();
253
}
254
return
removed;
255
}
256
257
void
record
(
ApiName
api,
LogText
input,
LogText
output,
ApiStatus
status) {
258
// 先把一次调用压成单行文本,使内存缓冲与 bqlog 文件拥有完全一致的记录格式。
259
const
std::string line = std::format(
"[{}] api={} input={} output={} status={}"
,
now_string
(),
260
api.
value
, input.
value
, output.
value
,
status_name
(status));
261
262
std::lock_guard lock(
g_mutex
);
263
// 关闭日志时尽早返回,不消耗环形缓冲,也不触发文件 flush。
264
if
(!
g_recording_enabled
) {
265
return
;
266
}
267
g_lines
.push_back(line);
268
// 环形缓冲只淘汰队首,保持“最近 kMaxLines 条”的时间顺序。
269
while
(
g_lines
.size() >
kMaxLines
) {
270
g_lines
.pop_front();
271
}
272
if
(
g_initialized
) {
273
try
{
274
g_logger
->info(
"{}"
, line);
275
g_logger
->force_flush();
276
}
catch
(
const
std::exception&) {
277
// 环形缓冲仍保留记录;bqlog 异常不破坏业务接口。
278
}
279
}
280
}
281
282
void
set_enabled
(
bool
value) {
283
// 启停是进程级状态,切换时与 record 使用同一把锁,避免部分线程继续写文件。
284
std::lock_guard lock(
g_mutex
);
285
g_recording_enabled
= value;
286
}
287
288
bool
enabled
() {
289
std::lock_guard lock(
g_mutex
);
290
return
g_recording_enabled
;
291
}
292
293
std::vector<std::string>
recent
() {
294
std::lock_guard lock(
g_mutex
);
295
return
{
g_lines
.begin(),
g_lines
.end()};
296
}
297
298
std::size_t
count
() {
299
std::lock_guard lock(
g_mutex
);
300
return
g_lines
.size();
301
}
302
303
void
clear
() {
304
std::lock_guard lock(
g_mutex
);
305
g_lines
.clear();
306
}
307
308
std::filesystem::path
log_path
() {
309
std::lock_guard lock(
g_mutex
);
310
return
g_log_path
;
311
}
312
313
template
<std::invocable<std::
string
_view> Visitor>
314
void
for_each_recent
(Visitor&& visitor) {
315
std::lock_guard lock(
g_mutex
);
316
for
(
const
auto
& line :
g_lines
) {
317
std::invoke(visitor, std::string_view(line));
318
}
319
}
320
321
std::string_view
status_name
(
ApiStatus
status)
noexcept
{
322
// 显式穷举状态;后续新增枚举值时编译器可配合警告提示遗漏分支。
323
switch
(status) {
324
case
ApiStatus::ok
:
325
return
"ok"
;
326
case
ApiStatus::failed
:
327
return
"fail"
;
328
}
329
return
"unknown"
;
330
}
331
332
std::string_view
log_error_message
(
LogError
error)
noexcept
{
333
// 业务层只消费稳定字符串,不直接暴露 bqlog 的平台错误文本。
334
switch
(error) {
335
case
LogError::invalid_path
:
336
return
"log path is empty"
;
337
case
LogError::backend_unavailable
:
338
return
"bqlog backend unavailable"
;
339
}
340
return
"unknown log error"
;
341
}
342
343
}
// namespace interface_log
InterfaceLog
接口输入/输出日志模块(STATIC 模块库)。
interface_log
Definition
InterfaceLog.cppm:24
interface_log::g_log_path
std::filesystem::path g_log_path
当前后端日志基准路径。
Definition
InterfaceLog.cppm:139
interface_log::clear
void clear()
清空内存环形缓冲(不动 bqlog 文件)。
Definition
InterfaceLog.cppm:303
interface_log::set_enabled
void set_enabled(bool enabled)
启用/禁用接口记录(内存与文件同时生效)。
Definition
InterfaceLog.cppm:282
interface_log::cleanup_old_logs
std::size_t cleanup_old_logs(const std::filesystem::path &log_path, int months_back=3)
删除超过保留期的接口日志与每日 trace 日志。
Definition
InterfaceLog.cppm:202
interface_log::g_recording_enabled
bool g_recording_enabled
Alfred 配置控制的全局日志开关。
Definition
InterfaceLog.cppm:141
interface_log::recent
std::vector< std::string > recent()
取出最近 <= kMaxLines 条记录(最旧在前)。
Definition
InterfaceLog.cppm:293
interface_log::log_error_message
std::string_view log_error_message(LogError error) noexcept
LogError 到可读消息(供 CLI / 测试使用)。
Definition
InterfaceLog.cppm:332
interface_log::status_name
std::string_view status_name(ApiStatus status) noexcept
状态枚举到可读名称(供日志/测试使用)。
Definition
InterfaceLog.cppm:321
interface_log::record
void record(ApiName api, LogText input, LogText output, ApiStatus status)
记录一次接口调用。
Definition
InterfaceLog.cppm:257
interface_log::g_mutex
std::mutex g_mutex
保护下列全部可变状态。
Definition
InterfaceLog.cppm:136
interface_log::g_lines
std::deque< std::string > g_lines
最近调用记录;队首最旧。
Definition
InterfaceLog.cppm:137
interface_log::count
std::size_t count()
当前环形缓冲条数(<= kMaxLines)。
Definition
InterfaceLog.cppm:298
interface_log::kMaxLines
constexpr std::size_t kMaxLines
环形缓冲最大记录条数(= 1000)。
Definition
InterfaceLog.cppm:29
interface_log::g_logger
std::optional< bq::log > g_logger
可选 bqlog 文件后端。
Definition
InterfaceLog.cppm:138
interface_log::g_initialized
bool g_initialized
bqlog 是否已成功初始化。
Definition
InterfaceLog.cppm:140
interface_log::LogError
LogError
可恢复的日志初始化错误。
Definition
InterfaceLog.cppm:41
interface_log::LogError::backend_unavailable
@ backend_unavailable
bqlog 后端初始化失败(环形缓冲仍可用)
Definition
InterfaceLog.cppm:43
interface_log::LogError::invalid_path
@ invalid_path
日志路径为空
Definition
InterfaceLog.cppm:42
interface_log::init
std::expected< std::monostate, LogError > init(const std::filesystem::path &log_path="log/interface.log")
初始化 bqlog TextFileAppender(可重复调用;路径变化会重建后端)。
Definition
InterfaceLog.cppm:155
interface_log::ApiStatus
ApiStatus
接口调用结果状态。
Definition
InterfaceLog.cppm:35
interface_log::ApiStatus::failed
@ failed
调用失败(错误已记录在 output 文本中)
Definition
InterfaceLog.cppm:37
interface_log::ApiStatus::ok
@ ok
调用成功
Definition
InterfaceLog.cppm:36
interface_log::for_each_recent
void for_each_recent(Visitor &&visitor)
在锁内逐条访问环形缓冲(函数对象设计)。
Definition
InterfaceLog.cppm:314
interface_log::log_path
std::filesystem::path log_path()
当前配置的日志路径。
Definition
InterfaceLog.cppm:308
interface_log::enabled
bool enabled()
当前是否启用接口记录。
Definition
InterfaceLog.cppm:288
interface_log::now_string
std::string now_string()
当前时间字符串(本地时间,秒级精度)。
Definition
InterfaceLog.cppm:147
interface_log::kDeveloperWebsite
constexpr std::string_view kDeveloperWebsite
开发者网站水印;每次初始化 bqlog 与 CLI 调用日志都会写出。
Definition
InterfaceLog.cppm:32
interface_log::ApiName
接口名强类型。 语义:调用方必须显式构造 ApiName,避免把任意字符串误当接口名。
Definition
InterfaceLog.cppm:50
interface_log::ApiName::ApiName
ApiName(std::string name)
Definition
InterfaceLog.cppm:52
interface_log::ApiName::value
std::string value
Definition
InterfaceLog.cppm:51
interface_log::LogText
接口输入/输出文本强类型。 语义:与 ApiName 一样显式构造,参数顺序在编译期被类型约束。
Definition
InterfaceLog.cppm:59
interface_log::LogText::value
std::string value
Definition
InterfaceLog.cppm:60
interface_log::LogText::LogText
LogText(std::string text)
Definition
InterfaceLog.cppm:61
CXX-CMake-template
CXX-CMake-template
single-exe-static-modules
modules
interface_log
include
InterfaceLog.cppm
Generated by
1.18.0