dm-database-driver-log 0.1.1

A pluggable streaming parser for DM database driver logs
Documentation

dm-database-driver-log

CI Crates.io Documentation Rust Version License

达梦(DM)驱动日志解析库。这个 crate 只提供 Rust 依赖库 API,不包含命令行程序、Parquet 导出或数据库连接功能。

要求 Rust 1.85 或更高版本(Rust 2024 edition)。库、examples 和 benchmark 均遵循该 最低版本,CI 也会检查 MSRV 兼容性。

默认启用 jdbc feature。需要解析 DmProvider_*.log 格式时启用 dm-provider feature:

[dependencies]
dm-database-driver-log = { version = "0.1", features = ["dm-provider"] }

支持的日志格式

JDBC(jdbc)

[INFO  - 2026-09-16 17:45:19.763] tid:119 - [worker] { conn-3, pstmt-854, rs-2216 } getString(String): "value"; [PARAMS]: "name"; [USED TIME]: 0.5ms;
[INFO  - 2026-09-16 17:45:19.763] tid:119 - [worker] { conn-3, pstmt-854 } executeQuery(): rs-2216; [USED TIME]: 8.5ms; [EXEC_ID]: 19010657;
[DEBUG - 2026-09-16 17:45:19.763] tid:119 - [worker] { conn-3 } access(); CMD_EXECUTE2

DM Provider(dm-provider)

[INFO - 2026-09-12 08:37:44.696] tid:34 (IsBackground-True) { B@16900fb } access Cmd:4(); [USED TIME]: 0ns;
[SQL - 2026-09-12 08:55:30.193] tid:68 (IsBackground-True) { conn-2095 (sessId:281421579449976), command-4579 } ExecuteDbDataReader(CommandBehavior) [SQL]: SELECT ... [USED TIME]: 2ms; [EXEC_ID]: 908131301;

Provider 日志中的 SQL 可以跨物理行;使用统一的 LogParserBuilder 时会自动识别格式, 并将跨行内容合并到同一个 LogEvent::DmProvider 事件。used_time_ms 统一换算成毫秒, 原始值保存在 used_time_text。

统一 API

JDBC 和 DM Provider 共用同一套入口,解析器会根据第一条记录的日志头自动识别格式:

use dm_database_driver_log::{LogEvent, LogParserBuilder};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let parser = LogParserBuilder::new("driver.log").build()?;

    for result in parser.iter()? {
        let event = result?;
        println!(
            "format={:?} line={} method={} category={} used_time_ms={:?}",
            event.format(),
            event.line_number(),
            event.method(),
            event.category(),
            event.used_time_ms(),
        );

        match event {
            #[cfg(feature = "jdbc")]
            LogEvent::Jdbc(jdbc) => println!("jdbc thread={}", jdbc.thread),
            #[cfg(feature = "dm-provider")]
            LogEvent::DmProvider(provider) => println!("provider ids={}", provider.ids),
        }
    }
    Ok(())
}

单条记录也使用同一套入口:parse_line()、parse_bytes() 和 parse_bytes_with_encoding() 返回 LogEvent。需要访问格式专属字段时使用 event.as_jdbc() 或 event.as_dm_provider()。

扩展其他驱动日志

文件读取、编码处理、单行/跨行 framing、错误上下文和通用过滤器位于统一引擎中。新增驱动时实现 LogFormat 和 LogRecord 即可复用:

use dm_database_driver_log::advanced::{LogFormat, LogParserBuilder, LogRecord, RecordFraming};

struct OtherDriverFormat;
struct OtherDriverEvent {
    method: String,
    used_time_ms: Option<f64>,
    exec_id: Option<i64>,
}

impl LogRecord for OtherDriverEvent {
    fn method(&self) -> &str { &self.method }
    fn category(&self) -> &str { "other-driver" }
    fn used_time_ms(&self) -> Option<f64> { self.used_time_ms }
    fn exec_id(&self) -> Option<i64> { self.exec_id }
}

// 为 OtherDriverFormat 实现 LogFormat 后即可使用:
// advanced::LogParserBuilder::<OtherDriverFormat>::new(path).build()?.iter()?;

RecordFraming::Line 适合一行一条记录;RecordFraming::HeaderDelimited 适合 SQL 或调用栈跨行的日志。 高级通用引擎位于 advanced 命名空间,内置 JDBC/DM Provider 的日常调用不需要接触它。

新增一种驱动日志时按以下顺序处理:

  1. 在 src/formats/<driver>.rs 定义事件和格式解析器。
  2. 为事件实现 LogRecord,为格式实现 LogFormat,选择合适的 RecordFraming。
  3. 在 src/formats/mod.rs 增加 feature 条件模块。
  4. 在 Cargo.toml 增加该驱动 feature,并在 lib.rs 暴露格式事件和兼容 Builder 名称。
  5. 在 tests/integration_test.rs 增加从文件、跨行边界、错误、过滤和汇总结果的完整链路测试。

使用方式

加入依赖:

[dependencies]
dm-database-driver-log = "0.1"

逐行流式解析文件:

use dm_database_driver_log::LogParserBuilder;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let parser = LogParserBuilder::new("driver.log").build()?;

    for result in parser.iter()? {
        let event = result?;
        println!(
            "line={} method={} used_time_ms={:?}",
            event.line_number(), event.method(), event.used_time_ms()
        );
    }
    Ok(())
}

也可以链式筛选:

use dm_database_driver_log::LogParserBuilder;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let parser = LogParserBuilder::new("driver.log").build()?;
    let slow_queries = parser
        .iter()?
        .filter_by_method("executeQuery")
        .filter_by_used_time(10.0);

    for result in slow_queries {
        let event = result?;
        println!("exec_id={:?} used_time_ms={:?}", event.exec_id(), event.used_time_ms());
    }
    Ok(())
}

单行解析返回可独立保存的统一 LogEvent;格式专属字段通过 as_jdbc() 或 as_dm_provider() 借用访问。

文件迭代器会跳过空行,并保留物理行号。格式错误会包含行号、字节偏移和原文;可以使用 skip_errors() 忽略错误,或保留错误结果进行诊断。

API

内置驱动日志

日常使用只需要统一 API:

  • LogParserBuilder:自动识别 JDBC/DM Provider 并构建文件解析器。
  • LogParserBuilder::encoding_hint():选择 Auto、Utf8 或 Gb18030。
  • LogParser::format() / LogParser::iter():查看格式并创建统一流式迭代器。
  • LogIterator::filter_by_method() / filter_by_category():按方法或分类筛选。
  • LogIterator::filter_by_used_time() / filter_by_exec_id():按耗时或执行编号筛选。
  • LogIterator::skip_errors():忽略格式错误,只保留成功解析的事件。
  • LogEvent:通过 format()、method()、category()、used_time_ms()、exec_id() 等方法访问公共字段。
  • LogEvent::as_jdbc() / LogEvent::as_dm_provider():访问格式专属字段。
  • parse_line() / parse_bytes() / parse_bytes_with_encoding():解析单条日志。
  • FileEncodingHint / ParseError:控制输入编码并处理解析错误。

启用 jdbc feature 后可使用 JdbcEvent,启用 dm-provider feature 后可使用 DmProviderEvent 访问对应格式的专属结构。默认 feature 是 jdbc。

扩展其他驱动

advanced 命名空间提供新增驱动所需的通用引擎:

  • advanced::LogFormat:定义日志格式、解析和 framing 行为。
  • advanced::LogRecord:定义公共过滤字段。
  • advanced::LogParserBuilder<F> / advanced::LogParser<F>:构建和运行指定格式的解析器。
  • advanced::RecordFraming:选择单行或跨行记录边界。

测试覆盖率

项目将行覆盖率 90% 作为最低门禁。安装 cargo-llvm-cov 后执行:

sh scripts/coverage.sh

脚本会运行全部测试,并在行覆盖率低于 90% 时返回失败。

tests/integration_test.rs 还提供完整链路测试:从临时日志文件开始,经过 Builder、流式 Iterator、跨行记录合并、错误诊断、过滤器和结果汇总,分别覆盖 JDBC 与 dm-provider feature。

Examples 与 benchmark

examples 是独立的演示程序,不改变本 crate 的依赖库定位:

cargo run --example batch_summary -- path/to/dm-jdbc.log
cargo run --example filter_builder -- path/to/dm-jdbc.log
cargo run --example filter_slow_queries -- path/to/dm-jdbc.log 100
cargo run --features dm-provider --example provider_summary -- path/to/DmProvider.log

perf_full 会生成合成 JDBC 日志并输出迭代吞吐;默认约 50 MiB、20 次, 可用环境变量缩短本地试跑:

PERF_SIZE_MB=5 PERF_ITERS=3 cargo run --release --example perf_full

Criterion benchmark 会在仓库中的真实日志存在时优先测量真实文件,同时始终 覆盖合成的 5 MiB 单行/混合记录;启用全部内置格式可运行:

cargo bench --all-features --bench driver_benchmark

其中 JDBC 和 dm-provider 的多行样本分别验证对应 framing 的文件流式解析。

GitHub Actions

  • CI:检查格式、全部 feature 组合测试、Clippy、文档、examples/benchmark 编译、crate 打包和 90% 覆盖率门禁。
  • Release to crates.io:推送 v* tag 时校验版本、测试、构建并使用 CRATES_IO_TOKEN 发布。
  • Update Benchmark Baseline:在 Actions 页面手动触发,运行 Criterion 并更新 benchmarks/baseline.json。

本地可以用下面的脚本检查 Criterion 结果是否超过 5% 回归阈值:

bash scripts/check-regression.sh