跳到主要内容

观测与调试

这些执行器记录查询、导出指标或提供调试与测试辅助;应评估热路径开销和数据保留策略。

learn_domain

实验性插件(v1.2.0 引入)

learn_domainv1.2.0 首次发布,目前处于实验阶段:过滤条件、写入触发时机与配套 dynamic_domain_set 的接口可能在后续版本调整。生产部署前请评估学习速率与磁盘写入对持久化层的影响。

作用

观察 DNS 流水线中的请求域名,并把命中的 qname 写入指定 dynamic_domain_set。它只产生副作用,不修改请求或响应。

配置示例

- tag: learned_allow
type: dynamic_domain_set
args:
path: "/etc/oxidns/learned-allow.txt"

- tag: learn_allow
type: learn_domain
args:
provider: learned_allow
phase: after
questions: first
qtypes: ["A", "AAAA"]
success_only: true
answer_required: true
rule_kind: full
async: true
error_mode: continue
timeout: 1s

配置项

provider

  • 类型:string;必填:是
  • 作用:引用目标 dynamic_domain_set provider。
  • 约束:必须是 dynamic_domain_set,不能引用普通 domain_set

phase

  • 类型:string;必填:否;默认值:after
  • 可选值:beforeafter
  • 作用:
    • before:在后续 executor 执行前学习 request question。
    • after:先执行后续链路,再根据响应结果决定是否学习。

questions

  • 类型:string;必填:否;默认值:first
  • 可选值:firstall
  • 作用:控制学习第一个 question 或所有 question。

qtypes

  • 类型:array;必填:否;默认值:["A", "AAAA"]
  • 作用:只学习指定 DNS 查询类型。

success_only

  • 类型:bool;必填:否;默认值:true
  • 作用:仅 phase: after 生效,要求响应 RCODE 为 NOERROR

answer_required

  • 类型:bool;必填:否;默认值:true
  • 作用:仅 phase: after 生效,要求响应包含 answer。

rule_kind

  • 类型:string;必填:否;默认值:full
  • 可选值:fulldomain
  • 作用:控制学习到 dynamic_domain_set 的规则类型。

async

  • 类型:bool;必填:否;默认值:true
  • 作用:控制是否只入队后继续执行。设为 false 时会等待 provider 写入完成。

error_mode

  • 类型:string;必填:否;默认值:continue
  • 可选值:continuestopfail
  • 作用:控制学习失败后的执行结果。

timeout

  • 类型:duration;必填:否;默认值:1s
  • 作用:仅 async: false 时用于限制等待 provider 写入的时间。

行为说明

  • 学习域名会规范化为小写并移除尾部 .
  • 默认生成 full:example.com 精确规则,避免意外扩大匹配范围。
  • phase: after 默认只学习成功且有 answer 的 A / AAAA 响应。
  • phase: before 不检查响应条件。
  • v1 只学习请求 question 的 qname,不学习 CNAME target。

典型用途

  • 把成功解析过的域名加入动态放行列表。
  • 把某个分支中的域名沉淀到本地文件,供后续 qname $learned_allow 或父级 domain_set 使用。

query_summary

作用

在后续链路执行完后输出紧凑查询摘要。

配置示例

- tag: summary_main
type: query_summary
args:
# 日志标题;便于区分不同链路
msg: "main pipeline"

配置项

msg

  • 类型:string;必填:否;默认值:"query summary"
  • 作用:定义摘要日志标题。

quick setup

- exec: "query_summary main"

行为说明

  • 正向阶段记录开始时间。
  • 回程阶段输出:
    • 来源地址
    • qname
    • qtype
    • rcode
    • elapsed_ms

典型用途

  • 低成本链路追踪。
  • 分支延时对比。

query_recorder

作用

把入口 request、执行后 response 以及 sequence 路径事件持久化到 recorder 自己的 SQLite 数据库,并暴露历史查询、统计和 SSE 实时推送接口。

配置示例

- tag: query_recorder_main
type: query_recorder
args:
# SQLite 文件路径;多个 recorder 可以共用同一文件
path: "./data/query-recorder-main.sqlite"
# 热路径入队缓冲大小
queue_size: 8192
# 后台批量写入条数
batch_size: 256
# 后台批量 flush 间隔,单位毫秒
flush_interval_ms: 200
# 内存中保留多少条最近记录,供 SSE tail 回放
memory_tail: 1024
# 日志保留天数;最小 1
retention_days: 7
# 定时清理周期,单位小时;最小 1
cleanup_interval_hours: 1
# SQLite 读取侧最大并发数;最小 1
reader_concurrency: 2

配置项

path

  • 类型:string;必填:是
  • 作用:指定当前 recorder 的 SQLite 文件路径。

queue_size

  • 类型:integer;必填:否;默认值:8192
  • 作用:定义热路径到后台写线程的有界队列大小。

batch_size

  • 类型:integer;必填:否;默认值:256
  • 作用:定义后台批量写入 SQLite 的单批记录数。

flush_interval_ms

  • 类型:integer;必填:否;默认值:200
  • 作用:定义后台写线程的批量 flush 间隔。

memory_tail

  • 类型:integer;必填:否;默认值:1024
  • 作用:定义最近记录的内存 tail 长度,用于 stream?tail=n 回放。

retention_days

  • 类型:integer;必填:否;默认值:7
  • 最小值:1
  • 作用:定义日志保留天数;过期数据会被定时实际删除。

cleanup_interval_hours

  • 类型:integer;必填:否;默认值:1
  • 最小值:1
  • 作用:定义过期清理任务的执行周期。

reader_concurrency

  • 类型:integer;必填:否;默认值:2
  • 最小值:1
  • 作用:限制 WebUI / API 统计与历史查询同时运行的 SQLite reader 数量,避免突发读取在大库上占用过多阻塞线程和内存。

行为说明

  • 这是纯 executor 观察器,不会修改 server 收口逻辑。
  • 进入时抓取入口 request 的结构化快照,并启用 DnsContext.execution_path
  • next 返回后立即提交记录;成功时记录当前 response,失败时记录 error 和空 response。
  • request / response 不保存 wire 数据,而是把问题区、RR、EDNS 等字段拆成 JSON 文本列。
  • client_ip 字段来自 OxiDNS 看到的传输层来源地址;如果前面有 systemd-resolved、dnsmasq、AdGuardHome、dae、clash 等本机转发链路,记录中可能只看到 127.0.0.1
  • 每个 recorder 使用同一前缀下的版本化表:
    • qr_<safe_tag>_<fnv64hex>_v1_records
    • qr_<safe_tag>_<fnv64hex>_v1_steps
    • qr_<safe_tag>_<fnv64hex>_v1_questions
    • qr_<safe_tag>_<fnv64hex>_v1_meta
  • records 主表只保存固定字段集合;steps 表保存 sequence 路径事件,用于执行路径分析和命中率统计;questions 表是从 questions_json 派生的索引表,用于加速 qname/qtype 查询;meta 表记录已完成的派生表迁移。
  • 每个 recorder 独占自己的有界队列、SQLite 连接、后台写线程、内存 tail 和 SSE 广播器。
  • 共用同一 path 的 recorder 会按规范化文件路径协调 reader、writer 和维护操作;表和内存状态仍按 tag 隔离。
  • 定时保留期清理会删除过期记录、回收全部完整空闲页,并在最后截断 WAL。只有删除产生完整空闲页时,数据库文件才会实际变小;少量删除可能只释放页内空间供后续写入复用。
  • 旧数据库若仍是 auto_vacuum=NONE,第一次定时清理或手动清空会执行一次完整 VACUUM,迁移到 INCREMENTAL 模式。该过程可能短暂停止 recorder 的数据库读写,并需要额外临时磁盘空间,但不会阻塞 DNS 请求处理。
  • 维护失败不会终止后台 writer;定时任务会在后续周期重试,并通过结构化日志报告回收前后的页数、文件大小和失败阶段。

数据结构约定

  • questions_json 固定为 question 数组,例如:
[
{ "name": "www.example.com.", "qtype": "A", "qclass": "IN" }
]
  • answers_jsonauthorities_jsonadditionals_jsonsignature_json 固定为 RR 数组,例如:
[
{
"name": "www.example.com.",
"class": "IN",
"ttl": 300,
"rr_type": "A",
"payload_kind": "A",
"payload_text": "192.0.2.1",
"payload": { "ip": "192.0.2.1" }
}
]
  • req_edns_jsonresp_edns_json 固定为 EDNS 对象或 NULL
  • 表名中的 v1 是当前 schema 版本;后续升级会新增新版本表,不做原地改表。

API

  • GET /plugins/<tag>/records
    • created_at_ms 倒序分页返回主表记录。
    • 查询参数:
      • cursor=<created_at_ms>:<id>
      • limit=<n>,默认 100,最大 500
      • since_ms=<unix_ms>
      • until_ms=<unix_ms>
      • qname=<text>,按请求问题名包含匹配
      • client_ip=<text>,按客户端 IP 字符串包含匹配
      • qtype=<type> / rcode=<rcode> / status=all|error|has_response|no_response
  • GET /plugins/<tag>/records/<id>
    • 返回单条完整记录,并附带 steps
  • DELETE /plugins/<tag>/records
    • 清空当前 recorder 的所有历史记录和 steps,并清空内存 tail。
    • 操作会先 flush 后台写入队列,再用小批次自动提交删除记录;每批删除和每批页面回收后都会截断 WAL,避免一个覆盖整次清空的大事务导致 WAL 持续增长。返回 cleared_records 表示删除的主表记录数。
    • 若历史已删除但磁盘回收失败,接口返回 query_recorder_clear_failed,错误信息会明确说明历史已经清除;后续维护仍可重试空间回收。
  • GET /plugins/<tag>/stats/plugins
    • 返回按 matcher / executor / builtin 聚合的命中统计。
    • 支持 since_msuntil_mskind=matcher|executor|builtin|all 和 records 的过滤参数。
  • GET /plugins/<tag>/stats/top_clients / stats/top_qnames
    • 返回客户端 IP 或 QNAME 排行。
    • 支持 limit=<n>,默认 20;后端不再强制 200 上限。
    • 支持 records 的时间范围与过滤参数。
  • GET /plugins/<tag>/stats/qtype / stats/rcode
    • 返回 QTYPE 或 RCODE 分布。
    • 支持 records 的时间范围与过滤参数。
  • GET /plugins/<tag>/stats/latency
    • 返回延迟摘要、直方图和慢查询排行。
    • 支持 slow_limit=<n>limit=<n>,默认 20;后端不再强制 200 上限。
  • GET /plugins/<tag>/stats/timeseries
    • 返回按分钟或小时聚合的查询趋势。
    • 支持 bucket=minute|hourbuckets=<n>(默认 60,最大 720)。
  • GET /plugins/<tag>/stream
    • 以 SSE 实时推送新写入记录。
    • 支持 tail=<n> 回放最近 n 条内存 tail。
    • 客户端应发送 Accept: text/event-stream,并容忍 heartbeat、错误事件、空 payload 和短暂断连。

典型用途

  • 持久化审计和问题排查。
  • 分析 sequence 执行路径、插件命中率和异常分支。
  • 给控制面或外部面板提供实时查询日志流。
注意事项
  • 想要拿到完整主链路路径,推荐把 recorder 放在入口附近。
  • 如果前置分支在 recorder 之前就短路返回,该请求不会被当前 recorder 记录。
  • next 报错而 server 后续补发了兜底响应,数据库里仍只记录插件视角下的 error 和空 response。
  • 若未启用管理 API,recorder 仍会写库,但不会暴露查询与 SSE 路由。
  • 若需要真实终端 IP,请让客户端直连 OxiDNS,或在 HTTP/DoH 反向代理场景配置可信的 src_ip_header

metrics_collector

作用

收集轻量级请求计数与延时指标,并导出 Prometheus 格式。

配置示例

- tag: metrics_main
type: metrics_collector
args:
# 指标名称标签;导出到 /api/metrics 时会带上它
name: "main"

配置项

name

  • 类型:string;必填:否;默认值:"default"
  • 作用:定义当前指标收集器的名称标签。

quick setup

- exec: "metrics_collector main"

行为说明

  • 正向阶段:
    • query_total +1
    • inflight +1
    • 记录开始时间
  • 回程阶段:
    • inflight -1
    • 若无响应则 err_total +1
    • 若有响应则累计延时

API

  • GET /api/metrics
    • Prometheus 文本格式,全局唯一;其它插件的内置 metrics 也会汇总到同一接口。

典型用途

  • 主链路观测。
  • 多条策略入口延时对比。

debug_print

作用

打印请求与响应对象,便于调试。

配置示例

- tag: debug_main
type: debug_print
args:
# 日志标题;未配置时默认是 "debug print"
msg: "before forward"
  • msg
    • 可选日志标题。
    • 默认 "debug print"

配置项

msg

  • 类型:string;必填:否;默认值:"debug print"
  • 作用:定义日志输出标题。

quick setup

- exec: "debug_print cache branch"

典型用途

  • 排查 sequence 分支。
  • 验证请求和响应在插件前后是否被改写。

sleep

作用

异步延迟,用于测试和策略实验。

配置示例

- tag: sleep_100ms
type: sleep
args:
# 额外异步延迟 100ms
duration: 100

配置项

duration

  • 类型:integer;必填:否;默认值:0
  • 单位:毫秒
  • 作用:定义当前请求在该执行器上的额外异步等待时间。

quick setup

- exec: "sleep 100"

典型用途

  • 测试 fallback 阈值。
  • 人工制造慢路径验证观测链路。