log_reader --- 本地日志文件读取工具开发指南
概述
log_reader 是 microLog 提供的本地日志文件读取工具,用于读取 tinySeqFile 格式的日志文件。该模块采用 Pimpl 模式 封装内部实现,对外提供简洁、稳定的 API 接口,支持加密日志解密、多种输出方式以及跨平台运行。
文中代码仅仅为展示开发逻辑,并没有进行编译测试
项目仓库:https://gitee.com/galaxy_0/micro-log
支持特性
| 特性 | 说明 |
|---|---|
| 自动识别文件格式 | 读取文件头,自动获取 max_count 和 page_size |
| 加密日志解密 | 支持 AES128 / AES256 加密日志的实时解密 |
| 多种读取方式 | 一次性读取、回调逐条处理、输出到文件或终端 |
| 索引访问 | 支持按索引读取单条日志记录 |
| 跨平台 | 兼容 Windows 和 Linux 系统 |
接口定义
头文件
cpp
#include "logDetail/log_reader.hpp"
核心类
cpp
namespace microLog {
class log_reader {
public:
// 读取模式
enum class read_mode {
TERMINAL, // 终端模式,输出带索引前缀(如 "[123] log content")
TEXT // 纯文本模式,仅输出日志内容
};
// 加密类型
enum class crypto_type {
NONE, // 无加密
AES128, // AES-128 加密
AES256 // AES-256 加密
};
// 配置选项
struct options {
std::string log_file; // 日志文件路径
std::string output_file; // 输出文件路径(可选)
read_mode mode = read_mode::TERMINAL;
crypto_type crypto = crypto_type::NONE;
std::string key; // 加密密钥(128位需16字节,256位需32字节)
std::string iv; // 初始化向量(16字节)
};
// 日志记录结构
struct record {
uint64_t index; // 记录索引(即序号)
std::string content; // 日志内容(已解密,若有)
};
// 回调函数类型:返回 true 继续读取,返回 false 停止
using read_callback = std::function<bool(uint64_t index, const std::string& content)>;
};
}
核心方法
| 方法 | 签名 | 说明 |
|---|---|---|
| 构造函数 | log_reader() |
创建读取器实例 |
| 析构函数 | ~log_reader() |
销毁实例,自动关闭文件 |
| 打开文件 | bool open(const std::string& log_file) |
打开日志文件(无加密) |
| 打开文件(带选项) | bool open(const options& opts) |
打开日志文件并设置加密参数 |
| 设置加密 | bool set_crypto(crypto_type type, const std::string& key, const std::string& iv) |
单独设置解密参数 |
| 获取总记录数 | uint64_t get_total_count() const |
返回文件中的记录总数 |
| 获取首条索引 | uint64_t get_first_index() const |
返回第一条记录的索引 |
| 读取全部 | bool read_all(std::vector<record>& records) |
一次性将所有记录读入 vector |
| 回调读取 | bool read_with_callback(read_callback cb) |
通过回调函数逐条处理 |
| 读取到文件 | bool read_to_file(const std::string& file_path) |
将日志内容写入指定文件 |
| 读取到终端 | bool read_to_stdout(read_mode mode = read_mode::TERMINAL) |
输出到标准输出 |
| 关闭文件 | void close() |
手动关闭日志文件 |
| 检查有效性 | bool is_valid() const |
检查读取器是否处于有效状态 |
使用示例
基本使用(无加密)
cpp
#include "logDetail/log_reader.hpp"
int main() {
microLog::log_reader reader;
if (!reader.open("./tinylog.bin")) {
std::cerr << "打开日志文件失败" << std::endl;
return 1;
}
std::cout << "总记录数: " << reader.get_total_count() << std::endl;
std::cout << "首条索引: " << reader.get_first_index() << std::endl;
// 回调方式逐条读取
reader.read_with_callback([](uint64_t idx, const std::string& content) {
std::cout << "[" << idx << "] " << content;
return true; // 继续读取
});
reader.close();
return 0;
}
读取到文件
cpp
microLog::log_reader reader;
if (reader.open("./tinylog.bin")) {
reader.read_to_file("./output.txt");
}
读取加密日志
cpp
microLog::log_reader reader;
// 方式1:通过 options 结构一次性设置
microLog::log_reader::options opts;
opts.log_file = "./tinylog_encrypted.bin";
opts.crypto = microLog::log_reader::crypto_type::AES256;
opts.key = "0123456789abcdef0123456789abcdef"; // 32 字节
opts.iv = "0123456789abcdef"; // 16 字节
if (reader.open(opts)) {
reader.read_to_stdout();
}
// 方式2:先打开再设置加密
// reader.open("./tinylog_encrypted.bin");
// reader.set_crypto(microLog::log_reader::crypto_type::AES256, key, iv);
读取到 vector
cpp
microLog::log_reader reader;
if (reader.open("./tinylog.bin")) {
std::vector<microLog::log_reader::record> records;
if (reader.read_all(records)) {
for (const auto& rec : records) {
std::cout << "[" << rec.index << "] " << rec.content;
}
}
}
条件过滤读取
cpp
microLog::log_reader reader;
if (reader.open("./tinylog.bin")) {
// 只读取包含 "[ERROR]" 的日志
reader.read_with_callback([](uint64_t idx, const std::string& content) {
if (content.find("[ERROR]") != std::string::npos) {
std::cout << "[" << idx << "] " << content;
}
return true;
});
}
高级用法
进度显示
cpp
microLog::log_reader reader;
if (reader.open("./tinylog.bin")) {
uint64_t total = reader.get_total_count();
uint64_t count = 0;
reader.read_with_callback([&](uint64_t idx, const std::string& content) {
std::cout << content;
if (++count % 1000 == 0) {
std::cerr << "\r进度: " << (count * 100 / total) << "%";
}
return true;
});
std::cerr << "\r进度: 100%" << std::endl;
}
批量处理
cpp
microLog::log_reader reader;
if (reader.open("./tinylog.bin")) {
std::vector<microLog::log_reader::record> batch;
const size_t BATCH_SIZE = 100;
reader.read_with_callback([&](uint64_t idx, const std::string& content) {
batch.push_back({idx, content});
if (batch.size() >= BATCH_SIZE) {
process_batch(batch); // 自定义批量处理函数
batch.clear();
}
return true;
});
if (!batch.empty()) {
process_batch(batch);
}
}
工具程序
编译
bash
cmake .. -DBUILD_TOOLS=ON
make log_reader_tool
命令行参数
| 参数 | 说明 | 示例 |
|---|---|---|
-f |
指定日志文件路径(必需) | -f ./tinylog.bin |
-t |
指定输出文件路径(可选) | -t ./output.txt |
-m |
读取模式:terminal 或 text |
-m text |
-k |
AES 密钥(十六进制字符串) | -k 0123456789abcdef0123456789abcdef |
-i |
初始化向量(十六进制字符串) | -i 0123456789abcdef |
使用示例
bash
# 读取到终端(带索引)
./log_reader_tool -f ./tinylog.bin
# 读取到终端(纯文本)
./log_reader_tool -f ./tinylog.bin -m text
# 输出到文件
./log_reader_tool -f ./tinylog.bin -t ./output.txt
# 读取加密日志
./log_reader_tool -f ./tinylog.bin -k "0123456789abcdef0123456789abcdef" -i "0123456789abcdef"
内部实现
文件格式
tinySeqFile 日志文件采用定长头部 + 变长记录的结构,具体布局如下:
#mermaid-svg-NNYAfAiu0AZmOPXD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NNYAfAiu0AZmOPXD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NNYAfAiu0AZmOPXD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NNYAfAiu0AZmOPXD .error-icon{fill:#552222;}#mermaid-svg-NNYAfAiu0AZmOPXD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NNYAfAiu0AZmOPXD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NNYAfAiu0AZmOPXD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NNYAfAiu0AZmOPXD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NNYAfAiu0AZmOPXD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NNYAfAiu0AZmOPXD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NNYAfAiu0AZmOPXD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NNYAfAiu0AZmOPXD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NNYAfAiu0AZmOPXD .marker.cross{stroke:#333333;}#mermaid-svg-NNYAfAiu0AZmOPXD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NNYAfAiu0AZmOPXD p{margin:0;}#mermaid-svg-NNYAfAiu0AZmOPXD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NNYAfAiu0AZmOPXD .cluster-label text{fill:#333;}#mermaid-svg-NNYAfAiu0AZmOPXD .cluster-label span{color:#333;}#mermaid-svg-NNYAfAiu0AZmOPXD .cluster-label span p{background-color:transparent;}#mermaid-svg-NNYAfAiu0AZmOPXD .label text,#mermaid-svg-NNYAfAiu0AZmOPXD span{fill:#333;color:#333;}#mermaid-svg-NNYAfAiu0AZmOPXD .node rect,#mermaid-svg-NNYAfAiu0AZmOPXD .node circle,#mermaid-svg-NNYAfAiu0AZmOPXD .node ellipse,#mermaid-svg-NNYAfAiu0AZmOPXD .node polygon,#mermaid-svg-NNYAfAiu0AZmOPXD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NNYAfAiu0AZmOPXD .rough-node .label text,#mermaid-svg-NNYAfAiu0AZmOPXD .node .label text,#mermaid-svg-NNYAfAiu0AZmOPXD .image-shape .label,#mermaid-svg-NNYAfAiu0AZmOPXD .icon-shape .label{text-anchor:middle;}#mermaid-svg-NNYAfAiu0AZmOPXD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NNYAfAiu0AZmOPXD .rough-node .label,#mermaid-svg-NNYAfAiu0AZmOPXD .node .label,#mermaid-svg-NNYAfAiu0AZmOPXD .image-shape .label,#mermaid-svg-NNYAfAiu0AZmOPXD .icon-shape .label{text-align:center;}#mermaid-svg-NNYAfAiu0AZmOPXD .node.clickable{cursor:pointer;}#mermaid-svg-NNYAfAiu0AZmOPXD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NNYAfAiu0AZmOPXD .arrowheadPath{fill:#333333;}#mermaid-svg-NNYAfAiu0AZmOPXD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NNYAfAiu0AZmOPXD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NNYAfAiu0AZmOPXD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NNYAfAiu0AZmOPXD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NNYAfAiu0AZmOPXD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NNYAfAiu0AZmOPXD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NNYAfAiu0AZmOPXD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NNYAfAiu0AZmOPXD .cluster text{fill:#333;}#mermaid-svg-NNYAfAiu0AZmOPXD .cluster span{color:#333;}#mermaid-svg-NNYAfAiu0AZmOPXD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NNYAfAiu0AZmOPXD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NNYAfAiu0AZmOPXD rect.text{fill:none;stroke-width:0;}#mermaid-svg-NNYAfAiu0AZmOPXD .icon-shape,#mermaid-svg-NNYAfAiu0AZmOPXD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NNYAfAiu0AZmOPXD .icon-shape p,#mermaid-svg-NNYAfAiu0AZmOPXD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NNYAfAiu0AZmOPXD .icon-shape .label rect,#mermaid-svg-NNYAfAiu0AZmOPXD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NNYAfAiu0AZmOPXD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NNYAfAiu0AZmOPXD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NNYAfAiu0AZmOPXD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据区 (变长记录)
记录 0: 长度:4字节 + 内容
记录 1: 长度:4字节 + 内容
...
文件头 (stHead)
m_magic4: 固定标识 'TF\\0\\0'
m_ver4: 版本号 (1 或 2)
m_max_count8: 最大记录数
m_page_size4: 页面大小(字节)
m_length8: 文件总长度(字节)
m_first8: 第一条记录的索引
m_last8: 最后一条记录的索引
m_crypto4: 加密类型 (0=NONE, 1=AES128, 2=AES256)
- 文件头 固定长度为
sizeof(stHead),包含元信息。 - 数据区 由多条记录顺序排列,每条记录由一个 4 字节的长度字段和对应的内容数据组成。
- 记录内容为原始日志字符串(若加密则为密文),读取时会根据
m_crypto自动解密。
Pimpl 模式
cpp
struct log_reader::impl {
std::shared_ptr<tinySeqFile> seq_file; // 底层文件操作对象
std::string log_file; // 文件路径
bool opened = false; // 是否已打开
bool crypto_set = false; // 是否已设置加密
bool read_file_header(const std::string& path, uint64_t& max_count, uint32_t& page_size);
};
内部通过 std::shared_ptr<tinySeqFile> 管理文件句柄和内存映射,确保资源安全释放。
工作流程
渲染错误: Mermaid 渲染失败: Parse error on line 2: ...art TD A调用 open() --> B[读取文件头
----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'
错误处理
常见错误及原因
| 错误场景 | 返回值 | 可能原因 |
|---|---|---|
| 文件不存在 | open() 返回 false |
路径错误或无权限 |
| 文件格式无效 | open() 返回 false |
不是有效的 tinySeqFile 文件 |
| 加密密钥错误 | set_crypto() 返回 false |
密钥长度不符合要求 |
| 未打开文件 | read_*() 返回 false |
未调用 open() 或打开失败 |
错误检查示例
cpp
microLog::log_reader reader;
if (!reader.open("./nonexistent.bin")) {
std::cerr << "错误: 无法打开日志文件" << std::endl;
return 1;
}
if (!reader.is_valid()) {
std::cerr << "错误: 读取器状态无效" << std::endl;
return 1;
}
std::vector<microLog::log_reader::record> records;
if (!reader.read_all(records)) {
std::cerr << "错误: 读取数据失败" << std::endl;
return 1;
}
性能优化
内存预分配
cpp
std::vector<microLog::log_reader::record> records;
records.reserve(reader.get_total_count()); // 提前预留空间
reader.read_all(records);
按需读取(回调方式)
cpp
// 不一次性加载所有数据,内存占用保持恒定
reader.read_with_callback([](uint64_t idx, const std::string& content) {
process_record(content); // 即时处理
return true;
});
大文件建议
- 对于超大日志文件(GB 级别),推荐使用
read_with_callback()配合流式处理。 - 避免使用
read_all()将全部数据装入内存。
集成到其他项目
作为共享库使用
cmake
# 在 CMakeLists.txt 中
find_package(microLog REQUIRED)
target_link_libraries(your_app microLog::log_reader)
cpp
#include <microLog/log_reader.hpp>
int main() {
microLog::log_reader reader;
reader.open("/path/to/log.bin");
reader.read_to_stdout();
return 0;
}
静态链接
cmake
add_subdirectory(path/to/microLog)
target_link_libraries(your_app microLog::log_reader_static)
与日志写入配合使用
cpp
// 写入日志(本地环形文件)
auto log = microLog::createLog<microLog::tinyLogType::LOCAL>(
4, "./tinylog.bin", 4096, 1000
);
log->info("系统启动");
log->debug("调试信息: %d", 42);
log->warn("警告: 资源不足");
log->error("错误: %s", "连接失败");
// 读取日志
microLog::log_reader reader;
reader.open("./tinylog.bin");
reader.read_to_stdout();
常见问题
Q1: 如何判断日志文件是否加密?
log_reader 不会自动探测加密类型。你可以先尝试不带加密打开,如果读取内容出现乱码,则说明文件已加密,需通过 set_crypto() 设置正确的密钥和 IV。
Q2: 读取性能怎么样?
read_all():一次性读入内存,适合小文件(<100MB),速度快。read_with_callback():逐条处理,内存占用恒定,适合大文件,但回调开销略高。
Q3: 支持哪些平台?
支持 Windows 和 Linux。在 Linux 上使用 mmap,在 Windows 上使用 CreateFileMapping,均实现高效文件访问。
Q4: 能否读取正在被写入的日志文件?
可以。tinySeqFile 基于内存映射,读操作不阻塞写操作。但可能读到尚未完整写入的记录(部分内容),建议在读取前确保写入完成,或接受不完整的风险。
Q5: 如何处理超大日志文件(>10GB)?
建议使用回调方式逐条处理,同时结合进度显示。避免使用 read_all(),并确保输出目标(如文件)支持流式写入。
文档版本 : 1.0
最后更新 : 2026-07-04
作者: 宋炜