microLog 的本地日志读取接口 log_reader — 本地日志文件读取工具开发指南

log_reader --- 本地日志文件读取工具开发指南

概述

log_reader 是 microLog 提供的本地日志文件读取工具,用于读取 tinySeqFile 格式的日志文件。该模块采用 Pimpl 模式 封装内部实现,对外提供简洁、稳定的 API 接口,支持加密日志解密、多种输出方式以及跨平台运行。

文中代码仅仅为展示开发逻辑,并没有进行编译测试
项目仓库:https://gitee.com/galaxy_0/micro-log

支持特性

特性 说明
自动识别文件格式 读取文件头,自动获取 max_countpage_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 读取模式:terminaltext -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

作者: 宋炜

相关推荐
yoothey2 小时前
报废审批流规则引擎设计——责任链模式完整实现
linux·开发语言·bash
2501_925963382 小时前
外设的常见问题
linux
l1t2 小时前
在linux和windows中解决duckdb 1.6dev版本输出执行计划报错问题
linux·运维·数据库·windows·duckdb
无心水2 小时前
【全域智能营销实战】2、Spring AI 模块化架构深度解读:从 1.0 到 2.0 的演进与最佳实践
人工智能·spring·架构·harness·顶尖架构师·全域智能营销·harmess
HavenlonLabs3 小时前
Havenlon 对抗性完整(十七):安全不是“防住攻击”,而是控制失败方式
网络·人工智能·架构·安全威胁分析·安全架构·havenlon
柳鲲鹏3 小时前
LINUX高通平台交叉编译地图软件GDAL
linux
doiito(Do It Together)3 小时前
media_agent 进化之路:把 Gliding Horse 的 Agent 超能力注入 ComfyUI,让图片生成自己“学会”优化
人工智能·架构·rust·knowledge graph
fei_sun3 小时前
路径MTU发现
linux·运维·网络
触底反弹4 小时前
🔥 从点积到 Transformer:我终于搞懂大模型是怎么"猜"出下一个词的了
人工智能·机器学习·架构