☰
CoreDNS log 插件详解:查询日志的格式、分类过滤与 JSON 输出
2026/10/5 2:01:48 网站建设 项目流程
  • 后端
  • 网络
  • 云原生

【免费下载链接】coredns

CoreDNS is a DNS server that chains plugins

项目地址:https://gitcode.com/gh_mirrors/co/coredns
点击查看免费下载

导读

log是 CoreDNS 内置的请求(access)日志插件,负责把进入 CoreDNS 的每一个 DNS 查询及其响应摘要输出到标准输出(stdout)。本文以 plugin/log/README.md 为主体,结合 plugin/log/log.go、plugin/log/setup.go 等源码,完整讲解它的语法、响应类别(class)过滤、全部占位符(placeholder)、自定义日志格式、JSON 输出模式,以及它注入的{/log/class}、{/log/type}元数据,帮助你在生产环境中精准、低噪地记录 DNS 访问日志。

插件概述与适用场景

log插件启用后,会将所有查询(以及应答的相关部分)输出到标准输出。默认输出为 Common Log Format(通用日志格式),并可通过 Corefile 指令微调输出内容。

需要明确两点边界:

  • log插件只控制查询日志(query logging);CoreDNS 自身的其他日志(启动信息、插件加载、错误日志等)不受其开关影响,无论是否启用本插件都会照常输出。
  • 对于繁忙的 DNS 服务器,逐条记录查询日志会带来明显的性能开销(每条请求都要格式化字符串并写日志),文档明确提示这一点,生产环境应结合class过滤或按需采样使用。

插件在插件链中的位置与机制:log注册后作为一个普通 Handler 包裹后续插件(见 plugin/log/setup.go 中的AddPlugin),在ServeDNS中先匹配规则,再通过dnstest.NewRecorder记录下游插件的应答,最后按格式输出(见 plugin/log/log.go 的Logger.ServeDNS)。

语法与配置指令

无参数形式

log

不带任何参数时,对所有请求以 Common Log Format 输出一条查询日志到 stdout。从源码看(plugin/log/setup.go 的logParse),此时默认规则为:NameScope为.(匹配所有区域)、Format为DefaultLogFormat、类别为all。

带名称与格式的形式

log [NAMES...] [FORMAT]
  • NAMES:要匹配并记录日志的域名列表,支持多个域名同时给出;未指定时默认作用域为.。
  • FORMAT:日志格式,默认是 Common Log Format;{common}是 Common Log Format 的快捷写法,{combined}则在 Common Log Format 基础上追加查询 opcode{>opcode}。

解析细节:源码logParse中,若最后一个参数包含{,则被识别为格式串(支持{common}、{combined}快捷展开,见 plugin/log/setup.go),其余参数全部作为域名作用域;例如log example.org example.net {host}会生成两条规则,各自使用{host}格式。每个名称会通过dns.Fqdn规范化为带尾点的 FQDN。

响应类别过滤

log [NAMES...] [FORMAT] { class CLASSES... }
  • CLASSES:以空格分隔的响应类别列表,只记录匹配类别的响应。

在 Corefile 中可以写多条class语句,它们之间是**或(OR)**关系:源码将多条class参数合并进同一个 map(plugin/log/setup.go 中classes[cls] = struct{}{}),只要响应的类别命中其中任意一个即输出日志。setup_test.go中log { class denial }、log { class denial error }与两条class语句等价的用例印证了这一行为(plugin/log/setup_test.go)。

响应类别含义

类别含义
success成功响应
denialNXDOMAIN 或 nodata(名称存在但类型不存在)响应;nodata 响应返回码为 NOERROR
errorSERVFAIL、NOTIMP、REFUSED 等,即远端服务器不愿解析该请求的情况
all默认值。未指定类别时的行为,与任何类别混用都会导致所有消息被记录

未指定类别时默认是all(源码中len(classes) == 0时写入response.All)。类别判定映射自 plugin/pkg/response/classify.go:NoError/Delegation归为success,NameError/NoData归为denial,其余(含OtherError)归为error。底层响应类型判定在 plugin/pkg/response/typify.go 的Typify中完成。

日志格式与占位符

log同时支持请求(request)与响应(response)占位符,自定义格式可自由组合。支持的占位符如下:

占位符含义
{type}请求的 qtype
{name}请求的 qname
{class}请求的 qclass
{proto}所用协议(tcp 或 udp)
{remote}客户端 IP 地址,IPv6 地址会用方括号包裹:[::1]
{local}服务器 IP 地址,IPv6 地址同样用方括号包裹
{size}请求大小(字节)
{port}客户端端口
{duration}响应耗时
{rcode}响应 RCODE
{rsize}原始(未压缩)响应大小(客户端实际收到的可能更小)
{>rflags}响应标志,每个置位的标志都会显示,例如 "aa, tc",包含 qr 位
{>bufsize}查询中通告的 EDNS0 buffer 大小
{>do}查询中 EDNS0 DO(DNSSEC OK)位是否置位
{>id}查询 ID
{>opcode}查询 OPCODE
{common}默认的 Common Log Format
{combined}带查询 opcode 的 Common Log Format
{/LABEL}任意元数据标签:只要用{/与}包裹即可作为占位符,未定义时替换为默认值-,详见metadata插件(plugin/metadata/README.md)

占位符的求值逻辑集中在 plugin/pkg/replacer/replacer.go:{remote}、{local}对 IPv6 地址自动加方括号(appendAddrToRFC3986);{>rflags}通过appendFlags依次检查 qr/aa/tc/rd/ra/z/ad/cd 标志并逗号拼接;{rcode}优先用dns.RcodeToString输出文本(如NOERROR),未知 RCODE 回退为十进制数字;元数据标签{/...}从 context 中按metadata.ValueFunc取值,未设置则输出-。格式串会被解析为「字面量 / 标签 / 元数据」三类节点并缓存(loadFormat+sync.Map),复用同一 Replacer 并发安全,降低逐请求解析开销。

默认 Common Log Format

`{remote}:{port} - {>id} "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {>rflags} {rsize} {duration}`

该常量定义在 plugin/log/log.go 的CommonLogFormat中;CombinedLogFormat = CommonLogFormat + " {>opcode}";DefaultLogFormat = CommonLogFormat。注意格式串中-对应replacer.EmptyValue(空值占位符),因此日志里出现-表示某个字段不可用。

默认文本模式下,每条日志通过log.Info输出,典型实例如下:

[INFO] [::1]:50759 - 29008 "A IN example.org. udp 41 false 4096" NOERROR qr,rd,ra,ad 68 0.037990251s

逐字段解读:[::1]:50759为客户端地址与端口;29008为查询 ID;引号内依次是 qtype、qclass、qname、协议、请求大小、DO 位、EDNS0 buffer 大小;随后是 RCODE、置位标志、响应大小与耗时。

JSON 输出模式

启动 CoreDNS 时使用命令行标志-log-format=json即可让整个进程以 JSON 格式输出日志;这是命令行参数,不是 Corefile 指令,默认值是-log-format=text(见 coremain/run.go 中的 flag 定义,以及 plugin/pkg/log/json.go 中IsJSON()的后端判断)。log插件的名称匹配与响应类别过滤在两种模式下行为完全一致。

每个查询产生一条 JSON 记录,包含公共字段time、level、msg、plugin(查询记录恒为log),以及以下类型化字段:

字段类型含义
client_ipstring客户端地址,IPv6 地址不带方括号
client_portnumber客户端端口
qnamestring小写、完整限定的查询名,DNS 展示格式
qtype,qclassstring查询类型与类别,未知值包含数字形式
protocolstringudp或tcp,同{proto}
id,opcodenumber查询 ID 与 opcode
request_sizenumber请求字节数,同{size}
dnssec_okboolean查询的 DNSSEC OK 位
bufsizenumber有效响应 buffer 大小,同{>bufsize}
rcodestring 或 null响应 RCODE,未记录到 DNS 响应时为 null
response_sizenumber记录的响应字节数,同{rsize}
duration_secondsnumber处理耗时(秒)

与文本模式一致,响应大小描述的是记录的、未压缩的消息,未必等于真正下发给客户端的字节数。延迟错误(如 SERVFAIL)使用插件链返回后 CoreDNS 将生成的响应;被丢弃的请求rcode: null,不会记为成功响应;裸Write调用计入大小但无法提供解码后的响应 RCODE。

这些字段的实现位于 plugin/log/json.go 的logJSON:通过slog.String/slog.Int/slog.Bool输出类型化属性;rcode在rr.Msg == nil(例如被 ACL 丢弃)时输出null,否则先查dns.RcodeToString,未知 RCODE 输出数字字符串。

FORMAT仍控制msg字段(包括自定义格式与元数据占位符),但不会替换 JSON schema 或定义新的顶层字段;DNS 字段直接来自请求与响应对象,而非解析msg。例如配置log . "{name} {rcode}"时输出:

{"time":"2026-09-15T08:00:00Z","level":"INFO","msg":"example.org. NOERROR","plugin":"log","client_ip":"127.0.0.1","client_port":40212,"qname":"example.org.","qtype":"A","qclass":"IN","protocol":"udp","id":42,"opcode":0,"request_size":29,"dnssec_ok":false,"bufsize":512,"rcode":"NOERROR","response_size":29,"duration_seconds":0.001}

附加元数据:区分 NOERROR denial 与 success

log插件还会向 context 注入以下元数据,用于对「NOERROR 的 denial」与 success 消息做更细粒度的区分。映射关系来自 plugin/pkg/response/classify.go 与 plugin/pkg/response/typify.go:

  • {/log/class}:success、denial
  • {/log/type}:NODATA、NXDOMAIN、NOERROR

注入逻辑在 plugin/log/log.go 的ServeDNS中:先response.Typify(rrw.Msg, ...)得到类型(如 NODATA/NXDOMAIN/NOERROR),再response.Classify(tpe)得到类别(success/denial/error),最后通过metadata.SetValueFunc注册两个惰性求值的元数据函数。

配合自定义格式使用:

. { log . "{proto} Request: {name} {type} {/log/class} {/log/type}" }

典型输出形如udp Request: example.org. A denial NXDOMAIN,可精确区分「名称不存在的 NXDOMAIN」「名称存在但类型不存在的 NODATA」以及「正常成功的 NOERROR」三类结果。

实战示例

以下示例均来自 plugin/log/README.md,可直接放入 Corefile 使用。

将所有请求记录到 stdout:

. { log whoami }

自定义日志格式,覆盖所有区域(.):

. { log . "{proto} Request: {name} {type} {>id}" }

只记录 example.org(及以下子域)的 denial(NXDOMAIN 与 nodata):

. { log example.org { class denial } }

以 Combined Log Format 记录所有未成功解析的查询:

. { log . {combined} { class denial error } }

记录所有未出现错误的查询:

. { log . { class denial success } }

多条 class 语句的 OR 语义:上面的写法可等价改写为:

. { log . { class denial class success } }

测试用例进一步验证了这些行为:TestLoggedClassDenial证实class denial下 NXDOMAIN 请求会输出 NXDOMAIN(而class error不记录该请求),TestLoggedStatus验证默认格式包含A IN example.org. udp 29 false 512等字段,TestLoggedSynthesizesDeferredServerFailure则验证下游插件返回错误但未写响应时,log会先合成 SERVFAIL 应答再分类记录(见 plugin/log/log_test.go)。

常见问题与注意事项

  • {remote}/{local}与client_ip的差异:文本模式的{remote}对 IPv6 加方括号(如[::1]),JSON 模式的client_ip不带方括号,做日志归一时需注意。
  • rsize是未压缩大小:{rsize}与response_size描述的是记录的原始消息长度,客户端实际收到的报文可能更小(例如受 EDNS0 buffer 限制被截断)。
  • 被丢弃的请求:请求被上游插件丢弃(未产生响应)时,文本模式{rcode}显示-,JSON 模式rcode为null,不会被当作成功响应计数。
  • 性能开销:文档明确说明繁忙服务器启用查询日志会有性能损耗;建议用class过滤仅记录denial/error,或将-log-format=json与结构化采集配合以降低解析成本。
  • 配置错误会在启动时暴露:class后无参数、非法类别(如class abracadabra)或块内未知属性(如log { unknown })都会导致 Corefile 解析失败,logParse会返回错误并中止启动(见 plugin/log/setup_test.go 中的对应用例)。
  • 后端
  • 网络
  • 云原生

【免费下载链接】coredns

CoreDNS is a DNS server that chains plugins

项目地址:https://gitcode.com/gh_mirrors/co/coredns
点击查看免费下载

相关推荐

上一篇:GetQzonehistory完整教程:5分钟学会永久备份QQ空间所有历史记录
下一篇:提升Android开发效率:dagger-intellij-plugin的5个实用技巧

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询