菜单

OpLog.h 使用手册

下载

OpLog.h 使用手册

本文说明 src/op/OpLog.h 中 OpLogMgr 的使用方式,并结合 OpLog.cpp 说明当前实现的异步、路径和线程安全特性。

1. 初始化

cpp 复制代码
#include "op/OpLog.h"

using namespace stt;
using namespace stt::op;

OpLogMgr* logMgr = OpLogMgr::getInstance(getRuntime());

OpLogMgr 是进程级单例。构造时启动 detach 的无限后台线程,约每 60 秒将队列中的 SQL 写入数据库。传入的 Runtime、数据库和管理器必须在整个线程运行期间有效。

2. API 总览

API 作用 主要注意事项
getInstance 获取单例 首次 Runtime 生效
add 将一条日志加入待写队列 不会立即落盘
refresh 立即刷新待写 SQL 不刷新显示控件
pause / resume 暂停或恢复接收日志 暂停期间日志直接丢弃
clear 清空已保存日志 不清空尚未落盘的队列
bind 将日志视图绑定到表格 仅支持 STTTable
exportAs 同步导出 会阻塞调用线程并显示 UI 提示
exportAsAsync 名称为异步,当前实际不是独立异步线程

3. 写入日志

cpp 复制代码
void add(
    const string& page,
    const string& widget,
    const string& action,
    const string& var,
    const string& info
);

示例:

cpp 复制代码
logMgr->add(
    "Frm01",
    "wMButtonExport",
    "CLICK",
    "ExportState",
    "用户开始导出历史数据"
);

add 只把 INSERT SQL 放进内存队列。后台线程通常约 60 秒后写盘;需要立即查询或导出最新记录时,先调用 refresh。

关键行为:

  • info 为空时,整条日志被忽略。
  • pause 状态下,新日志被直接丢弃,不会等待 resume 后补写。
  • page、widget、action、var、info 当前没有调用 StringUtil::sqlEsc,单引号可能破坏 SQL,外部可控文本还会产生 SQL 注入风险。
  • 调用方与后台线程对队列和暂停标志的访问没有锁,存在数据竞争。

因此输入至少应避免单引号,安全要求较高的项目应先修复底层为参数化 SQL。

4. 立即落盘

cpp 复制代码
void refresh();
cpp 复制代码
logMgr->add("Frm01", "btnSave", "CLICK", "", "保存配方");
logMgr->refresh();

refresh 会立即执行当前待写 SQL,但不会让已绑定的 STTTable 自动重新查询。写盘后如要更新页面,还需调用表格的导航或刷新方法,例如 navFirst。

5. 暂停与恢复

cpp 复制代码
void pause();
void resume();
cpp 复制代码
logMgr->pause();
// 此期间调用 add 的日志会丢弃
logMgr->resume();

适合在批量维护操作中临时禁止记录,但不能用作“缓冲后批量写入”。

6. 清空日志

cpp 复制代码
static void clear();

clear 删除数据库中的持久化行并重置 SQLite 自增序列:

cpp 复制代码
// 先决定如何处理尚未写盘的队列
logMgr->refresh();
OpLogMgr::clear();
table->navFirst();

注意:clear 不会清空内存中的待写队列。若直接 clear,后台 refresh 后可能把 clear 之前排队的记录重新插入。需要“彻底清空到当前时刻”时,应先暂停,调用 refresh,再 clear,最后根据业务决定何时 resume。

7. 绑定表格

cpp 复制代码
bool bind(STTWidget* wTable, bool isDesc = true);
cpp 复制代码
STTTable* table = /* 页面中的日志表 */;
bool ok = logMgr->bind(table, true);
if (!ok) {
    // 检查控件类型和数据库状态
}

isDesc=true 表示最新记录在前。当前实现只接受 STTTable,会 new 一个 TTableView 并绑定。头文件没有明确暴露该视图的所有权,页面反复绑定可能引入生命周期或泄漏问题,应尽量只绑定一次。

8. 同步导出 exportAs

cpp 复制代码
static bool exportAs(const string& path);
cpp 复制代码
logMgr->refresh();
bool ok = OpLogMgr::exportAs("logs/oplog.csv");

该方法会同步查询全部日志到内存并写 CSV,调用期间可能阻塞 UI,同时显示成功或失败对话框。

路径规则:

平台 path 的处理
ARM 在 path 前拼接检测到的 U 盘根路径
Windows 在 path 前拼接 ./data/

父目录不会自动创建。传 "logs/oplog.csv" 前应确保对应 logs 目录存在。大数据量会同时占用较多内存和 UI 时间。

9. exportAsAsync 的实际行为

cpp 复制代码
static void exportAsAsync(const string& path);
cpp 复制代码
logMgr->refresh();
OpLogMgr::exportAsAsync("/mnt/udisk/oplog.csv");

名称虽然是 Async,但当前实现直接调用 FileUtil::exportDB2CSV 的四参数同步重载:

  • 没有创建工作线程。
  • 不检查 bool 返回值和错误字符串。
  • 不显示 UI 结果。
  • 不自动拼接 U 盘根目录或 Windows 的 ./data/。
  • path 按调用方给出的原样使用。

因此不要假设它不会阻塞。需要真正异步时,应使用已修复的 FileUtil 异步重载,或在确保 Runtime/UI 生命周期安全的前提下由业务工作线程执行同步导出。

10. 推荐流程

cpp 复制代码
void Frm01::recordExportClick() {
    OpLogMgr* logs = OpLogMgr::getInstance(getRuntime());
    logs->add("Frm01", "btnExport", "CLICK", "", "开始导出");
    logs->refresh();
}

bool Frm01::exportLogs() {
    OpLogMgr* logs = OpLogMgr::getInstance(getRuntime());
    logs->refresh();

    // ARM 下 path 是 U 盘根目录内的相对路径
    return OpLogMgr::exportAs("oplog.csv");
}

11. 线程与安全清单

  • 后台工作线程无法停止,析构函数为空且线程捕获 this。
  • 不要在 Runtime 或数据库销毁后继续使用单例。
  • add 与后台刷新存在无锁数据竞争,高频多线程写日志并不安全。
上一个
EventMgr.h 使用手册
下一个
RCP.h 使用手册
最近修改: 2026-10-21Powered by